diff --git a/crates/keyfork/src/cli/mnemonic.rs b/crates/keyfork/src/cli/mnemonic.rs index b545694..d680dbe 100644 --- a/crates/keyfork/src/cli/mnemonic.rs +++ b/crates/keyfork/src/cli/mnemonic.rs @@ -224,14 +224,18 @@ pub enum MnemonicSubcommands { /// of the encryption will be written to `encrypted.asc`. Otherwise, the /// default behavior is to write the certificate to a file named after the certificate's /// fingerprint. If either output file already exists, it will not be overwritten, and the - /// command will exit unsuccessfully. + /// command will exit unsuccessfully. This functionality must happen regardless if a + /// provisioner output is specified, as the certificate is then used to encrypt the + /// mnemonic. /// /// Additionally, when given the `account=` option (which must match the `account=` option /// of the relevant provisioner), the given account will be used instead of the default /// account of 0. /// - /// Because a new OpenPGP key needs to be created, a User ID can also be supplied, using + /// Because a new OpenPGP cert needs to be created, a User ID can also be supplied, using /// the option `userid=`. It can contain any characters that are not a comma. + /// If any other operation generating an OpenPGP key has a `userid=` field, and this + /// operation doesn't, that User ID will be used instead. #[arg(long)] encrypt_to_self: Option>, @@ -241,6 +245,17 @@ pub enum MnemonicSubcommands { /// Additional arguments, such as the amount of hardware to provision and the /// account to use when deriving, can be specified by using (for example) /// `--provision openpgp-card,count=2,account=1`. + /// + /// Provisioners may output their public key, if necessary. The file path may be chosen + /// based on the provided `output` field, or automatically determined based on the content + /// of the key, such as an OpenPGP fingerprint or a public key hash. If automatically + /// generated, the filename will be printed. + /// + /// If the OpenPGP Card provisioner is selected, because a new OpenPGP cert needs to be + /// created, a User ID can also be supplied, using the option `userid=`. It + /// can contain any characters that are not a comma. If any other operation generating an + /// OpenPGP key has a `userid=` field, and this operation doesn't, that User ID will be + /// used instead. #[arg(long)] provision: Option>, }, @@ -578,7 +593,18 @@ impl MnemonicSubcommands { } if let Some(encrypt_to_self) = encrypt_to_self { - do_encrypt_to_self(&mnemonic, &encrypt_to_self.inner, &encrypt_to_self.values)?; + let mut values = encrypt_to_self.values.clone(); + // If we have a userid from `provision` but not one here, use that one. + if let Some(provision) = provision { + if matches!(&provision.inner, provision::Provisioner::OpenPGPCard(_)) + && !values.contains_key("userid") + { + if let Some(userid) = provision.values.get("userid") { + values.insert(String::from("userid"), userid.clone()); + } + } + } + do_encrypt_to_self(&mnemonic, &encrypt_to_self.inner, &values)?; } if let Some(provisioner) = provision { @@ -589,11 +615,22 @@ impl MnemonicSubcommands { // key output. // // We use the atypical `_skip_cert_output` field here to denote an automatic - // marking to skip the cert output. However, the `cert_output` field will take + // marking to skip the cert output. However, the `output` field will take // priority, since it can only be manually set by the user. let mut values = provisioner.values.clone(); - if encrypt_to_self.is_some() { - values.insert(String::from("_skip_cert_output"), String::from("1")); + if let Some(encrypt_to_self) = encrypt_to_self { + if !values.contains_key("output") { + values.insert(String::from("_skip_cert_output"), String::from("1")); + } + // If we have a userid from `encrypt_to_self` but not one here, use that + // one. + if matches!(&provisioner.inner, provision::Provisioner::OpenPGPCard(_)) + && !values.contains_key("userid") + { + if let Some(userid) = encrypt_to_self.values.get("userid") { + values.insert(String::from("userid"), userid.clone()); + } + } } do_provision(&mnemonic, &provisioner.inner, &values)?; } diff --git a/crates/keyfork/src/cli/provision/openpgp.rs b/crates/keyfork/src/cli/provision/openpgp.rs index dd9850e..a3c6d26 100644 --- a/crates/keyfork/src/cli/provision/openpgp.rs +++ b/crates/keyfork/src/cli/provision/openpgp.rs @@ -85,23 +85,21 @@ impl ProvisionExec for OpenPGPCard { KeyFlags::empty().set_authentication(), ]; - // NOTE: This User ID doesn't have meaningful context on the card. - // To give it a reasonable name, use `keyfork derive openpgp` or some other system that - // generates the OpenPGP certificate. - let userid = UserID::from("Keyfork-Provisioned Key"); + let userid = match provisioner.metadata.as_ref().and_then(|m| m.get("userid")) { + Some(userid) => UserID::from(userid.as_str()), + None => UserID::from("Keyfork-Provisioned Key"), + }; let cert = keyfork_derive_openpgp::derive(xprv.clone(), &subkeys, &userid)?; - // cert_output is never automatically set, but _skip_cert_output is, so we bypass the - // automatically generated _skip_cert_output if !provisioner .metadata .as_ref() - .is_some_and(|m| m.contains_key("_skip_cert_output") && !m.contains_key("cert_output")) + .is_some_and(|m| m.contains_key("_skip_cert_output")) { let cert_output = match provisioner .metadata .as_ref() - .and_then(|m| m.get("cert_output")) + .and_then(|m| m.get("output")) { Some(cert_output) => PathBuf::from(cert_output), None => {