diff options
author | Heiko Schaefer <heiko@schaefer.name> | 2022-07-04 18:04:10 +0200 |
---|---|---|
committer | Heiko Schaefer <heiko@schaefer.name> | 2022-07-08 23:39:37 +0200 |
commit | ed813eded4604ee306c7a48040eba00414b1b716 (patch) | |
tree | 7a0ae8b025c8ef435afe83572cd995792720c066 | |
parent | 0f4b13505f7257aab32150864aa36ef3ffbccb6d (diff) |
sq: Implement 'key userid strip'.heiko/workwork
-rw-r--r-- | sq/sq-subplot.md | 12 | ||||
-rw-r--r-- | sq/src/commands/key.rs | 51 | ||||
-rw-r--r-- | sq/src/sq-usage.rs | 59 | ||||
-rw-r--r-- | sq/src/sq_cli.rs | 62 |
4 files changed, 182 insertions, 2 deletions
diff --git a/sq/sq-subplot.md b/sq/sq-subplot.md index 56e8442b..d75a8e74 100644 --- a/sq/sq-subplot.md +++ b/sq/sq-subplot.md @@ -486,6 +486,18 @@ when I run sq inspect new.pgp then stdout contains "UserID: Juliet" ~~~ +### Update a key by removing a User ID + +_Requirement: We must be able to generate a key with a User ID, and then strip the User ID._ + +~~~scenario +given an installed sq +when I run sq key generate --userid "<juliet@example.org>" --export key.pgp +when I run sq key userid strip --userid "<juliet@example.org>" --output new.pgp key.pgp +when I run sq inspect new.pgp +then stdout doesn't contain "UserID:" +~~~ + ## Certificate extraction: `sq key extract-cert` diff --git a/sq/src/commands/key.rs b/sq/src/commands/key.rs index f29e8f8a..b132adf4 100644 --- a/sq/src/commands/key.rs +++ b/sq/src/commands/key.rs @@ -29,6 +29,7 @@ use crate::sq_cli::KeyGenerateCommand; use crate::sq_cli::KeyPasswordCommand; use crate::sq_cli::KeyUseridCommand; use crate::sq_cli::KeyUseridAddCommand; +use crate::sq_cli::KeyUseridStripCommand; use crate::sq_cli::KeyExtractCertCommand; use crate::sq_cli::KeyAdoptCommand; use crate::sq_cli::KeyAttestCertificationsCommand; @@ -311,6 +312,7 @@ fn extract_cert(config: Config, command: KeyExtractCertCommand) -> Result<()> { fn userid(config: Config, command: KeyUseridCommand) -> Result<()> { match command { KeyUseridCommand::Add(c) => userid_add(config, c)?, + KeyUseridCommand::Strip(c) => userid_strip(config, c)?, } Ok(()) @@ -460,6 +462,55 @@ fn userid_add(config: Config, command: KeyUseridAddCommand) -> Result<()> { Ok(()) } +fn userid_strip(config: Config, command: KeyUseridStripCommand) -> Result<()> { + let input = open_or_stdin(command.io.input.as_deref())?; + let key = Cert::from_reader(input)?; + + let orig_cert_valid = key.with_policy(&config.policy, None).is_ok(); + + let strip: Vec<_> = command.userid; + + // Make sure that each User ID that the user requested to remove exists in + // `key`, and *can* be removed. + let key_userids: Vec<_> = + key.userids().map(|u| u.userid().value()).collect(); + + let missing: Vec<_> = strip.iter() + .filter(|s| !key_userids.contains(&s.as_bytes())) + .collect(); + if ! missing.is_empty() { + return Err(anyhow::anyhow!( + "The certificate doesn't contain the User ID(s) {}.", + missing.iter().map(|s| format!("{:?}", s)).join(", "))); + } + + let cert = key.retain_userids(|uid| { + // Don't keep User IDs that were selected for removal + ! strip.iter().any(|rm| rm.as_bytes() == uid.userid().value()) + }); + + if orig_cert_valid { + if let Err(err) = cert.with_policy(&config.policy, None) { + eprintln!( +"Removing the User ID(s) has resulted in a invalid key: +{} + +You could create a direct key signature or update the self +signatures on other User IDs to make the key valid again.", + err + ); + } + } + + let mut sink = config.create_or_stdout_safe(command.io.output.as_deref())?; + if command.binary { + cert.as_tsk().serialize(&mut sink)?; + } else { + cert.as_tsk().armored().serialize(&mut sink)?; + } + Ok(()) +} + fn adopt(config: Config, command: KeyAdoptCommand) -> Result<()> { let input = open_or_stdin(command.certificate.as_deref())?; let cert = Cert::from_reader(input)?; diff --git a/sq/src/sq-usage.rs b/sq/src/sq-usage.rs index 96bcc65b..8afcd1e2 100644 --- a/sq/src/sq-usage.rs +++ b/sq/src/sq-usage.rs @@ -540,7 +540,7 @@ //! ```text //! Manages User IDs //! -//! Add User IDs to a key. +//! Add User IDs to, or strip User IDs from a key. //! //! USAGE: //! sq key userid <SUBCOMMAND> @@ -552,6 +552,8 @@ //! SUBCOMMANDS: //! add //! Adds a User ID +//! strip +//! Strips a User ID //! help //! Print this message or the help of the given subcommand(s) //! ``` @@ -614,6 +616,61 @@ //! --output juliet-new.key.pgp //! ``` //! +//! #### Subcommand key userid strip +//! +//! ```text +//! Strips a User ID +//! +//! Note that this operation does not reliably remove User IDs from a +//! certificate that has already been disseminated! (OpenPGP software +//! typically appends new information it receives about a certificate +//! to its local copy of that certificate. Systems that have obtained +//! a copy of your certificate with the User ID that you are trying to +//! strip will not drop that User ID from their copy.) +//! +//! In most cases, you will want to use the 'sq revoke userid' operation +//! instead. That issues a revocation for a User ID, which can be used to mark +//! the User ID as invalidated. +//! +//! However, this operation can be useful in very specific cases, in particular: +//! to remove a mistakenly added User ID before it has been uploaded to key +//! servers or otherwise shared. +//! +//! Stripping a User ID may change how a certificate is interpreted. This +//! is because information about the certificate like algorithm preferences, +//! the primary key's key flags, etc. is stored in the User ID's binding +//! signature. +//! +//! USAGE: +//! sq key userid strip [OPTIONS] [FILE] +//! +//! ARGS: +//! <FILE> +//! Reads from FILE or stdin if omitted +//! +//! OPTIONS: +//! -B, --binary +//! Emits binary data +//! +//! -h, --help +//! Print help information +//! +//! -o, --output <FILE> +//! Writes to FILE or stdout if omitted +//! +//! -u, --userid <USERID> +//! The User IDs to strip. Values must exactly match a User ID. +//! +//! EXAMPLES: +//! +//! # First, this generates a key +//! $ sq key generate --userid "<juliet@example.org>" --export juliet.key.pgp +//! +//! # Then, this strips a User ID +//! $ sq key userid strip --userid "<juliet@example.org>" \ +//! --output juliet-new.key.pgp juliet.key.pgp +//! ``` +//! //! ### Subcommand key extract-cert //! //! ```text diff --git a/sq/src/sq_cli.rs b/sq/src/sq_cli.rs index e7141919..0e745570 100644 --- a/sq/src/sq_cli.rs +++ b/sq/src/sq_cli.rs @@ -1988,13 +1988,14 @@ pub struct KeyExtractCertCommand { long_about = "Manages User IDs -Add User IDs to a key. +Add User IDs to, or strip User IDs from a key. ", subcommand_required = true, arg_required_else_help = true, )] pub enum KeyUseridCommand { Add(KeyUseridAddCommand), + Strip(KeyUseridStripCommand), } #[derive(Debug, Args)] @@ -2063,6 +2064,65 @@ $ sq key userid add --userid \"Juliet\" --creation-time 20210628T1137+0200 \\ pub binary: bool, } + +#[derive(Debug, Args)] +#[clap( + display_order = 20, + about = "Strips a User ID", + long_about = +"Strips a User ID + +Note that this operation does not reliably remove User IDs from a +certificate that has already been disseminated! (OpenPGP software +typically appends new information it receives about a certificate +to its local copy of that certificate. Systems that have obtained +a copy of your certificate with the User ID that you are trying to +strip will not drop that User ID from their copy.) + +In most cases, you will want to use the 'sq revoke userid' operation +instead. That issues a revocation for a User ID, which can be used to mark +the User ID as invalidated. + +However, this operation can be useful in very specific cases, in particular: +to remove a mistakenly added User ID before it has been uploaded to key +servers or otherwise shared. + +Stripping a User ID may change how a certificate is interpreted. This +is because information about the certificate like algorithm preferences, +the primary key's key flags, etc. is stored in the User ID's binding +signature. +", + after_help = +"EXAMPLES: + +# First, this generates a key +$ sq key generate --userid \"<juliet@example.org>\" --export juliet.key.pgp + +# Then, this strips a User ID +$ sq key userid strip --userid \"<juliet@example.org>\" \\ + --output juliet-new.key.pgp juliet.key.pgp +", +)] +pub struct KeyUseridStripCommand { + #[clap(flatten)] + pub io: IoArgs, + #[clap( + value_name = "USERID", + short, + long, + help = "User IDs to strip", + long_help = "The User IDs to strip. Values must exactly match a \ +User ID." + )] + pub userid: Vec<String>, + #[clap( + short = 'B', + long, + help = "Emits binary data", + )] + pub binary: bool, +} + #[derive(Debug, Args)] #[clap( name = "adopt", |