diff options
author | Justus Winter <justus@sequoia-pgp.org> | 2021-04-28 10:34:53 +0200 |
---|---|---|
committer | Justus Winter <justus@sequoia-pgp.org> | 2021-04-28 12:10:23 +0200 |
commit | d29b0918cc60e26c3eea097e1b7fc566530b87f0 (patch) | |
tree | 695994310da9fa127ce89e8d3b2c3fd6a30e28ca /sq | |
parent | d1202ff498b234dd4da0c1ff76769e68d53bf8b4 (diff) |
sq: Implement sq key password.
Diffstat (limited to 'sq')
-rw-r--r-- | sq/src/commands/key.rs | 68 | ||||
-rw-r--r-- | sq/src/sq-usage.rs | 51 | ||||
-rw-r--r-- | sq/src/sq_cli.rs | 38 |
3 files changed, 157 insertions, 0 deletions
diff --git a/sq/src/commands/key.rs b/sq/src/commands/key.rs index d6f646e7..c48d574d 100644 --- a/sq/src/commands/key.rs +++ b/sq/src/commands/key.rs @@ -27,6 +27,7 @@ use crate::decrypt_key; pub fn dispatch(config: Config, m: &clap::ArgMatches) -> Result<()> { match m.subcommand() { ("generate", Some(m)) => generate(config, m)?, + ("password", Some(m)) => password(config, m)?, ("extract-cert", Some(m)) => extract_cert(config, m)?, ("adopt", Some(m)) => adopt(config, m)?, ("attest-certifications", Some(m)) => @@ -205,6 +206,73 @@ fn generate(config: Config, m: &ArgMatches) -> Result<()> { Ok(()) } +fn password(config: Config, m: &ArgMatches) -> Result<()> { + let input = open_or_stdin(m.value_of("certificate"))?; + let key = Cert::from_reader(input)?; + + if ! key.is_tsk() { + return Err(anyhow::anyhow!("Certificate has no secrets")); + } + + // First, decrypt all secrets. + let passwords = &mut Vec::new(); + let mut decrypted: Vec<Packet> = Vec::new(); + decrypted.push(decrypt_key( + key.primary_key().key().clone().parts_into_secret()?, + passwords)?.into()); + for ka in key.keys().subkeys().secret() { + decrypted.push(decrypt_key( + ka.key().clone().parts_into_secret()?, + passwords)?.into()); + } + let mut key = key.insert_packets(decrypted)?; + assert_eq!(key.keys().secret().count(), + key.keys().unencrypted_secret().count()); + + let new_password = if m.is_present("clear") { + None + } else { + let prompt_0 = + rpassword::read_password_from_tty(Some("New password: ")) + .context("Error reading password")?; + let prompt_1 = + rpassword::read_password_from_tty(Some("Repeat new password: ")) + .context("Error reading password")?; + + if prompt_0 != prompt_1 { + return Err(anyhow::anyhow!("Passwords do not match")); + } + + if prompt_0.len() == 0 { + // Empty password means no password. + None + } else { + Some(prompt_0.into()) + } + }; + + if let Some(new) = new_password { + let mut encrypted: Vec<Packet> = Vec::new(); + encrypted.push( + key.primary_key().key().clone().parts_into_secret()? + .encrypt_secret(&new)?.into()); + for ka in key.keys().subkeys().unencrypted_secret() { + encrypted.push( + ka.key().clone().parts_into_secret()? + .encrypt_secret(&new)?.into()); + } + key = key.insert_packets(encrypted)?; + } + + let mut output = config.create_or_stdout_safe(m.value_of("output"))?; + if m.is_present("binary") { + key.as_tsk().serialize(&mut output)?; + } else { + key.as_tsk().armored().serialize(&mut output)?; + } + Ok(()) +} + fn extract_cert(config: Config, m: &ArgMatches) -> Result<()> { let input = open_or_stdin(m.value_of("input"))?; let mut output = config.create_or_stdout_safe(m.value_of("output"))?; diff --git a/sq/src/sq-usage.rs b/sq/src/sq-usage.rs index 94938f4e..54da6705 100644 --- a/sq/src/sq-usage.rs +++ b/sq/src/sq-usage.rs @@ -348,6 +348,7 @@ //! //! SUBCOMMANDS: //! generate Generates a new key +//! password Changes password protecting secrets //! extract-cert Converts a key to a cert //! attest-certifications Attests to third-party certifications //! adopt Binds keys from one certificate to another @@ -436,6 +437,56 @@ //! $ sq key generate --userid "<juliet@example.org>" --userid "Juliet Capulet" //! ``` //! +//! ### Subcommand key password +//! +//! ```text +//! Changes password protecting secrets +//! +//! Secret key material in keys can be protected by a password. This +//! subcommand changes or clears this encryption password. +//! +//! To emit the key with unencrypted secrets, either use `--clear` or +//! supply a zero-length password when prompted for the new password. +//! +//! USAGE: +//! sq key password [FLAGS] [OPTIONS] [FILE] +//! +//! FLAGS: +//! -B, --binary +//! Emits binary data +//! +//! --clear +//! Emit a key with unencrypted secrets +//! +//! -h, --help +//! Prints help information +//! +//! -V, --version +//! Prints version information +//! +//! +//! OPTIONS: +//! -o, --output <FILE> +//! Writes to FILE or stdout if omitted +//! +//! +//! ARGS: +//! <FILE> +//! Reads from FILE or stdin if omitted +//! +//! +//! EXAMPLES: +//! +//! # First, generate a key +//! $ sq key generate --userid "<juliet@example.org>" --export juliet.key.pgp +//! +//! # Then, encrypt the secrets in the key with a password. +//! $ sq key password < juliet.key.pgp > juliet.encrypted_key.pgp +//! +//! # And remove the password again. +//! $ sq key password --clear < juliet.encrypted_key.pgp > juliet.decrypted_key.pgp +//! ``` +//! //! ### Subcommand key extract-cert //! //! ```text diff --git a/sq/src/sq_cli.rs b/sq/src/sq_cli.rs index 0948363c..b0ce93d7 100644 --- a/sq/src/sq_cli.rs +++ b/sq/src/sq_cli.rs @@ -590,6 +590,44 @@ $ sq key generate --userid \"<juliet@example.org>\" --userid \"Juliet Capulet\" mandatory if OUTFILE is \"-\". \ [default: <OUTFILE>.rev]")) ) + .subcommand( + SubCommand::with_name("password") + .display_order(105) + .about("Changes password protecting secrets") + .long_about( +"Changes password protecting secrets + +Secret key material in keys can be protected by a password. This +subcommand changes or clears this encryption password. + +To emit the key with unencrypted secrets, either use `--clear` or +supply a zero-length password when prompted for the new password. +") + .after_help( +"EXAMPLES: + +# First, generate a key +$ sq key generate --userid \"<juliet@example.org>\" --export juliet.key.pgp + +# Then, encrypt the secrets in the key with a password. +$ sq key password < juliet.key.pgp > juliet.encrypted_key.pgp + +# And remove the password again. +$ sq key password --clear < juliet.encrypted_key.pgp > juliet.decrypted_key.pgp +") + .arg(Arg::with_name("clear") + .long("clear") + .help("Emit a key with unencrypted secrets")) + .arg(Arg::with_name("output") + .short("o").long("output").value_name("FILE") + .help("Writes to FILE or stdout if omitted")) + .arg(Arg::with_name("binary") + .short("B").long("binary") + .help("Emits binary data")) + .arg(Arg::with_name("key") + .value_name("FILE") + .help("Reads from FILE or stdin if omitted")) + ) .subcommand(SubCommand::with_name("extract-cert") .display_order(110) .about("Converts a key to a cert") |