1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
use anyhow::{Result, bail};
use bc_components::{PrivateKeyBase, PrivateKeys, SymmetricKey};
use bc_envelope::prelude::*;
use clap::Args;
use super::{ASKPASS_HELP, ASKPASS_LONG_HELP};
use crate::{EnvelopeArgs, EnvelopeArgsLike, read_password};
/// Decrypt the envelope's subject.
///
/// The subject can be decrypted using one of the following:
/// - A symmetric key (ur:crypto-key) (the content key) that was used to
/// encrypt the subject,
/// - A password that was used to lock the content key,
/// - A recipient's private key (ur:crypto-prvkey-base or ur:crypto-prvkeys)
/// that was used to lock the content key,
/// - An SSH identity that was used to lock the content key.
#[derive(Debug, Args)]
#[group(skip)]
pub struct CommandArgs {
/// The symmetric key to use to decrypt the envelope's subject.
/// (ur:crypto-key)
///
/// May not be used with the `--password` or `--recipient` options.
#[arg(
long,
short,
conflicts_with = "recipient",
conflicts_with = "password",
conflicts_with = "ssh_id"
)]
key: Option<String>,
/// The password to derive the symmetric key.
///
/// If not provided, will be prompted. May not be used with the `--key` or
/// `--recipient` options.
#[arg(long, short, num_args(0..=1), conflicts_with = "key", conflicts_with = "recipient", conflicts_with = "ssh_id")]
password: Option<Option<String>>,
/// Use the `SSH_ASKPASS` environment variable to read the password.
///
/// This option requires the `--password` option to be set.
#[arg(long, requires = "password", help = ASKPASS_HELP, long_help = ASKPASS_LONG_HELP)]
askpass: bool,
/// The recipient to whom the envelope's subject should be decrypted.
/// (ur:crypto-prvkey-base or ur:crypto-prvkeys)
#[arg(
long,
short,
conflicts_with = "key",
conflicts_with = "password",
conflicts_with = "ssh_id"
)]
recipient: Option<String>,
/// The SSH identity to use to decrypt the envelope's subject.
///
/// - If provided, the SSH agent will be used to find the Ed25519 key
/// associated with the identity.
/// - If provided but empty, the first available Ed25519 key in the SSH
/// agent is used.
#[arg(
long,
short,
conflicts_with = "key",
conflicts_with = "password",
conflicts_with = "recipient"
)]
ssh_id: Option<String>,
#[command(flatten)]
envelope_args: EnvelopeArgs,
}
impl EnvelopeArgsLike for CommandArgs {
fn envelope(&self) -> Option<&str> { self.envelope_args.envelope() }
}
impl crate::Exec for CommandArgs {
fn exec(&self) -> Result<String> {
// Read the envelope from the specified file or stdin
let envelope = self.read_envelope()?;
if let Some(key_ur) = &self.key {
// If a content key is provided, decrypt the subject using it
let key = SymmetricKey::from_ur_string(key_ur)?;
let decrypt_subject = envelope.decrypt_subject(&key);
match decrypt_subject {
Err(_) => bail!("decrypt failed"),
Ok(subject) => Ok(subject.ur_string()),
}
} else if let Some(password) = &self.password {
// If a password is provided, unlock the subject using it
if !envelope.is_locked_with_password() {
bail!("envelope is not locked with a password");
}
let password = read_password(
"Decryption password:",
password.as_deref(),
self.askpass,
)?;
Ok(envelope.unlock_subject(password.as_bytes())?.ur_string())
} else if let Some(recipient_ur) = &self.recipient {
// If a recipient's private key is provided, decrypt the subject
// using it. Try to parse as PrivateKeys first, then PrivateKeyBase.
if let Ok(recipient) = PrivateKeys::from_ur_string(recipient_ur) {
Ok(envelope
.decrypt_subject_to_recipient(&recipient)?
.ur_string())
} else if let Ok(recipient) =
PrivateKeyBase::from_ur_string(recipient_ur)
{
Ok(envelope
.decrypt_subject_to_recipient(&recipient)?
.ur_string())
} else {
bail!(
"invalid recipient private key: must be ur:crypto-prvkeys or ur:crypto-prvkey-base"
)
}
} else if let Some(ssh_id) = &self.ssh_id {
// If an SSH identity is provided, decrypt the subject using the SSH
// agent
if !envelope.is_locked_with_ssh_agent() {
bail!("envelope is not locked with an SSH agent");
}
Ok(envelope.unlock_subject(ssh_id)?.ur_string())
} else {
bail!(
"missing unlock method: either a symmetric key, password, recipient's private key, or SSH identity must be provided"
);
}
}
}