keyfork mnemonic generate: userid equivalency, rename provisioner cert_output to output

This commit is contained in:
Ryan Heywood 2025-02-19 20:35:29 -05:00
parent db19b30bfe
commit 723194fdd7
Signed by: ryan
GPG Key ID: 8E401478A3FBEF72
2 changed files with 49 additions and 14 deletions

View File

@ -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=<your User ID>`. 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<ValueWithOptions<PathBuf>>,
@ -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=<your User ID>`. 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<ValueWithOptions<provision::Provisioner>>,
},
@ -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,12 +615,23 @@ 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() {
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)?;
}

View File

@ -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 => {