diff --git a/src/builder.rs b/src/builder.rs index aa694e2..f01f131 100644 --- a/src/builder.rs +++ b/src/builder.rs @@ -1,7 +1,7 @@ use rand::{rngs::OsRng, RngCore}; use x509::RelativeDistinguishedName; use yubikey::{ - certificate::{Certificate, PublicKeyInfo}, + certificate::Certificate, piv::{generate as yubikey_generate, AlgorithmId, RetiredSlotId, SlotId}, Key, PinPolicy, TouchPolicy, YubiKey, }; @@ -106,12 +106,7 @@ impl IdentityBuilder { touch_policy, )?; - let recipient = match &generated { - PublicKeyInfo::EcP256(pubkey) => { - Recipient::from_encoded(pubkey).expect("YubiKey generates a valid pubkey") - } - _ => unreachable!(), - }; + let recipient = Recipient::from_spki(&generated).expect("YubiKey generates a valid pubkey"); let stub = Stub::new(yubikey.serial(), slot, &recipient); // Pick a random serial for the new self-signed certificate. diff --git a/src/error.rs b/src/error.rs index 99a9863..5b4372c 100644 --- a/src/error.rs +++ b/src/error.rs @@ -1,4 +1,3 @@ -use i18n_embed_fl::fl; use std::fmt; use std::io; use yubikey::{piv::RetiredSlotId, Serial}; @@ -9,6 +8,9 @@ macro_rules! wlnfl { ($f:ident, $message_id:literal) => { writeln!($f, "{}", $crate::fl!($message_id)) }; + ($f:ident, $message_id:literal, $($kwarg:expr),* $(,)*) => {{ + writeln!($f, "{}", $crate::fl!($message_id, $($kwarg,)*)) + }}; } pub enum Error { @@ -52,105 +54,44 @@ impl fmt::Debug for Error { wlnfl!(f, "err-custom-mgmt-key")?; let cmd = "ykman piv access change-management-key --protect"; let url = "https://developers.yubico.com/yubikey-manager/"; - writeln!( - f, - "{}", - fl!( - crate::LANGUAGE_LOADER, - "rec-custom-mgmt-key", - cmd = cmd, - url = url, - ), - )?; + wlnfl!(f, "rec-custom-mgmt-key", cmd = cmd, url = url)?; } - Error::InvalidFlagCommand(flag, command) => writeln!( + Error::InvalidFlagCommand(flag, command) => wlnfl!( f, - "{}", - fl!( - crate::LANGUAGE_LOADER, - "err-invalid-flag-command", - flag = flag.as_str(), - command = command.as_str(), - ), - )?, - Error::InvalidFlagTui(flag) => writeln!( - f, - "{}", - fl!( - crate::LANGUAGE_LOADER, - "err-invalid-flag-tui", - flag = flag.as_str(), - ), + "err-invalid-flag-command", + flag = flag.as_str(), + command = command.as_str(), )?, + Error::InvalidFlagTui(flag) => wlnfl!(f, "err-invalid-flag-tui", flag = flag.as_str())?, Error::InvalidPinLength => wlnfl!(f, "err-invalid-pin-length")?, - Error::InvalidPinPolicy(s) => writeln!( + Error::InvalidPinPolicy(s) => wlnfl!( f, - "{}", - fl!( - crate::LANGUAGE_LOADER, - "err-invalid-pin-policy", - policy = s.as_str(), - expected = "always, once, never", - ), + "err-invalid-pin-policy", + policy = s.as_str(), + expected = "always, once, never", )?, - Error::InvalidSlot(slot) => writeln!( + Error::InvalidSlot(slot) => wlnfl!(f, "err-invalid-slot", slot = slot)?, + Error::InvalidTouchPolicy(s) => wlnfl!( f, - "{}", - fl!(crate::LANGUAGE_LOADER, "err-invalid-slot", slot = slot), - )?, - Error::InvalidTouchPolicy(s) => writeln!( - f, - "{}", - fl!( - crate::LANGUAGE_LOADER, - "err-invalid-touch-policy", - policy = s.as_str(), - expected = "always, cached, never", - ), - )?, - Error::Io(e) => writeln!( - f, - "{}", - fl!(crate::LANGUAGE_LOADER, "err-io", err = e.to_string()), + "err-invalid-touch-policy", + policy = s.as_str(), + expected = "always, cached, never", )?, + Error::Io(e) => wlnfl!(f, "err-io", err = e.to_string())?, Error::MultipleCommands => wlnfl!(f, "err-multiple-commands")?, Error::MultipleYubiKeys => wlnfl!(f, "err-multiple-yubikeys")?, - Error::NoEmptySlots(serial) => writeln!( - f, - "{}", - fl!( - crate::LANGUAGE_LOADER, - "err-no-empty-slots", - serial = serial.to_string(), - ), - )?, - Error::NoMatchingSerial(serial) => writeln!( - f, - "{}", - fl!( - crate::LANGUAGE_LOADER, - "err-no-matching-serial", - serial = serial.to_string(), - ), - )?, - Error::SlotHasNoIdentity(slot) => writeln!( - f, - "{}", - fl!( - crate::LANGUAGE_LOADER, - "err-slot-has-no-identity", - slot = slot_to_ui(slot), - ), - )?, - Error::SlotIsNotEmpty(slot) => writeln!( - f, - "{}", - fl!( - crate::LANGUAGE_LOADER, - "err-slot-is-not-empty", - slot = slot_to_ui(slot), - ), - )?, + Error::NoEmptySlots(serial) => { + wlnfl!(f, "err-no-empty-slots", serial = serial.to_string())? + } + Error::NoMatchingSerial(serial) => { + wlnfl!(f, "err-no-matching-serial", serial = serial.to_string())? + } + Error::SlotHasNoIdentity(slot) => { + wlnfl!(f, "err-slot-has-no-identity", slot = slot_to_ui(slot))? + } + Error::SlotIsNotEmpty(slot) => { + wlnfl!(f, "err-slot-is-not-empty", slot = slot_to_ui(slot))? + } Error::TimedOut => wlnfl!(f, "err-timed-out")?, Error::UseListForSingleSlot => wlnfl!(f, "err-use-list-for-single")?, Error::YubiKey(e) => match e { @@ -161,55 +102,23 @@ impl fmt::Debug for Error { if cfg!(windows) { wlnfl!(f, "err-yk-no-service-win")?; let url = "https://learn.microsoft.com/en-us/windows/security/identity-protection/smart-cards/smart-card-debugging-information#smart-card-service"; - writeln!( - f, - "{}", - fl!(crate::LANGUAGE_LOADER, "rec-yk-no-service-win", url = url), - )?; + wlnfl!(f, "rec-yk-no-service-win", url = url)?; } else if cfg!(target_os = "macos") { wlnfl!(f, "err-yk-no-service-macos")?; let url = "https://apple.stackexchange.com/a/438198"; - writeln!( - f, - "{}", - fl!(crate::LANGUAGE_LOADER, "rec-yk-no-service-macos", url = url), - )?; + wlnfl!(f, "rec-yk-no-service-macos", url = url)?; } else { wlnfl!(f, "err-yk-no-service-pcscd")?; let apt = "sudo apt-get install pcscd"; - writeln!( - f, - "{}", - fl!(crate::LANGUAGE_LOADER, "rec-yk-no-service-pcscd", apt = apt), - )?; + wlnfl!(f, "rec-yk-no-service-pcscd", apt = apt)?; } } - yubikey::Error::WrongPin { tries } => writeln!( - f, - "{}", - fl!(crate::LANGUAGE_LOADER, "err-yk-wrong-pin", tries = tries), - )?, + yubikey::Error::WrongPin { tries } => wlnfl!(f, "err-yk-wrong-pin", tries = tries)?, e => { - writeln!( - f, - "{}", - fl!( - crate::LANGUAGE_LOADER, - "err-yk-general", - err = e.to_string(), - ), - )?; + wlnfl!(f, "err-yk-general", err = e.to_string())?; use std::error::Error; if let Some(inner) = e.source() { - writeln!( - f, - "{}", - fl!( - crate::LANGUAGE_LOADER, - "err-yk-general-cause", - inner_err = inner.to_string(), - ), - )?; + wlnfl!(f, "err-yk-general-cause", inner_err = inner.to_string())?; } } }, diff --git a/src/key.rs b/src/key.rs index aa7bab6..b645205 100644 --- a/src/key.rs +++ b/src/key.rs @@ -15,7 +15,7 @@ use std::iter; use std::thread::sleep; use std::time::{Duration, Instant, SystemTime}; use yubikey::{ - certificate::{Certificate, PublicKeyInfo}, + certificate::Certificate, piv::{decrypt_data, AlgorithmId, RetiredSlotId, SlotId}, reader::{Context, Reader}, MgmKey, PinPolicy, Serial, TouchPolicy, YubiKey, @@ -47,11 +47,7 @@ pub(crate) fn filter_connected(reader: &Reader) -> bool { { warn!( "{}", - i18n_embed_fl::fl!( - crate::LANGUAGE_LOADER, - "warn-yk-not-connected", - yubikey_name = reader.name(), - ) + fl!("warn-yk-not-connected", yubikey_name = reader.name()) ); false } else { @@ -147,11 +143,7 @@ pub(crate) fn open(serial: Option) -> Result { if let Some(serial) = serial { eprintln!( "{}", - i18n_embed_fl::fl!( - crate::LANGUAGE_LOADER, - "open-yk-with-serial", - yubikey_serial = serial.to_string(), - ) + fl!("open-yk-with-serial", yubikey_serial = serial.to_string()) ); } else { eprintln!("{}", fl!("open-yk-without-serial")); @@ -196,8 +188,7 @@ pub(crate) fn manage(yubikey: &mut YubiKey) -> Result<(), Error> { eprintln!(); let pin = Password::new() - .with_prompt(i18n_embed_fl::fl!( - crate::LANGUAGE_LOADER, + .with_prompt(fl!( "mgr-enter-pin", yubikey_serial = yubikey.serial().to_string(), default_pin = DEFAULT_PIN, @@ -211,11 +202,7 @@ pub(crate) fn manage(yubikey: &mut YubiKey) -> Result<(), Error> { eprintln!("{}", fl!("mgr-change-default-pin")); eprintln!(); let current_puk = Password::new() - .with_prompt(i18n_embed_fl::fl!( - crate::LANGUAGE_LOADER, - "mgr-enter-current-puk", - default_puk = DEFAULT_PUK, - )) + .with_prompt(fl!("mgr-enter-current-puk", default_puk = DEFAULT_PUK)) .interact()?; let new_pin = Password::new() .with_prompt(fl!("mgr-choose-new-pin")) @@ -244,8 +231,7 @@ pub(crate) fn manage(yubikey: &mut YubiKey) -> Result<(), Error> { mgm_key.set_protected(yubikey).map_err(|e| { eprintln!( "{}", - i18n_embed_fl::fl!( - crate::LANGUAGE_LOADER, + fl!( "mgr-changing-mgmt-key-error", management_key = hex::encode(mgm_key.as_ref()), ) @@ -340,11 +326,7 @@ impl Stub { let mut yubikey = match open_by_serial(self.serial) { Ok(yk) => yk, Err(yubikey::Error::NotFound) => { - let mut message = i18n_embed_fl::fl!( - crate::LANGUAGE_LOADER, - "plugin-insert-yk", - yubikey_serial = self.serial.to_string(), - ); + let mut message = fl!("plugin-insert-yk", yubikey_serial = self.serial.to_string()); // If the `confirm` command is available, we loop until either the YubiKey // we want is inserted, or the used explicitly skips. @@ -365,8 +347,7 @@ impl Stub { Err(_) => { return Ok(Err(identity::Error::Identity { index: self.identity_index, - message: i18n_embed_fl::fl!( - crate::LANGUAGE_LOADER, + message: fl!( "plugin-err-yk-opening", yubikey_serial = self.serial.to_string(), ), @@ -377,8 +358,7 @@ impl Stub { Err(age_core::plugin::Error::Fail) => { return Ok(Err(identity::Error::Identity { index: self.identity_index, - message: i18n_embed_fl::fl!( - crate::LANGUAGE_LOADER, + message: fl!( "plugin-err-yk-opening", yubikey_serial = self.serial.to_string(), ), @@ -388,8 +368,7 @@ impl Stub { // We're going to loop around, meaning that the first attempt failed. // Change the message to indicate this to the user. - message = i18n_embed_fl::fl!( - crate::LANGUAGE_LOADER, + message = fl!( "plugin-insert-yk-retry", yubikey_serial = self.serial.to_string(), ); @@ -402,8 +381,7 @@ impl Stub { if callbacks.message(&message)?.is_err() { return Ok(Err(identity::Error::Identity { index: self.identity_index, - message: i18n_embed_fl::fl!( - crate::LANGUAGE_LOADER, + message: fl!( "plugin-err-yk-not-found", yubikey_serial = self.serial.to_string(), ), @@ -419,8 +397,7 @@ impl Stub { Err(_) => { return Ok(Err(identity::Error::Identity { index: self.identity_index, - message: i18n_embed_fl::fl!( - crate::LANGUAGE_LOADER, + message: fl!( "plugin-err-yk-opening", yubikey_serial = self.serial.to_string(), ), @@ -432,8 +409,7 @@ impl Stub { Ok(end) if end >= FIFTEEN_SECONDS => { return Ok(Err(identity::Error::Identity { index: self.identity_index, - message: i18n_embed_fl::fl!( - crate::LANGUAGE_LOADER, + message: fl!( "plugin-err-yk-timed-out", yubikey_serial = self.serial.to_string(), ), @@ -447,8 +423,7 @@ impl Stub { Err(_) => { return Ok(Err(identity::Error::Identity { index: self.identity_index, - message: i18n_embed_fl::fl!( - crate::LANGUAGE_LOADER, + message: fl!( "plugin-err-yk-opening", yubikey_serial = self.serial.to_string(), ), @@ -459,11 +434,10 @@ impl Stub { // Read the pubkey from the YubiKey slot and check it still matches. let (cert, pk) = match Certificate::read(&mut yubikey, SlotId::Retired(self.slot)) .ok() - .and_then(|cert| match cert.subject_pki() { - PublicKeyInfo::EcP256(pubkey) => Recipient::from_encoded(pubkey) + .and_then(|cert| { + Recipient::from_certificate(&cert) .filter(|pk| pk.tag() == self.tag) - .map(|pk| (cert, pk)), - _ => None, + .map(|pk| (cert, pk)) }) { Some(pk) => pk, None => { @@ -528,8 +502,7 @@ impl Connection { // The policy requires a PIN, so request it. // Note that we can't distinguish between PinPolicy::Once and PinPolicy::Always // because this plugin is ephemeral, so we always request the PIN. - let enter_pin_msg = i18n_embed_fl::fl!( - crate::LANGUAGE_LOADER, + let enter_pin_msg = fl!( "plugin-enter-pin", yubikey_serial = self.yubikey.serial().to_string(), ); @@ -555,8 +528,7 @@ impl Connection { Err(_) => { return Ok(Err(identity::Error::Identity { index: self.identity_index, - message: i18n_embed_fl::fl!( - crate::LANGUAGE_LOADER, + message: fl!( "plugin-err-pin-required", yubikey_serial = self.yubikey.serial().to_string(), ), diff --git a/src/main.rs b/src/main.rs index 9a0d03c..491fa9d 100644 --- a/src/main.rs +++ b/src/main.rs @@ -13,7 +13,6 @@ use i18n_embed::{ use lazy_static::lazy_static; use rust_embed::RustEmbed; use yubikey::{ - certificate::PublicKeyInfo, piv::{RetiredSlotId, SlotId}, reader::Context, Key, PinPolicy, Serial, TouchPolicy, @@ -73,6 +72,9 @@ macro_rules! fl { ($message_id:literal) => {{ i18n_embed_fl::fl!($crate::LANGUAGE_LOADER, $message_id) }}; + ($message_id:literal, $($kwarg:expr),* $(,)*) => {{ + i18n_embed_fl::fl!($crate::LANGUAGE_LOADER, $message_id, $($kwarg,)*) + }}; } #[derive(Debug, Options)] @@ -196,9 +198,9 @@ fn print_single( let mut keys = Key::list(&mut yubikey)?.into_iter().filter_map(|key| { // - We only use the retired slots. // - Only P-256 keys are compatible with us. - match (key.slot(), key.certificate().subject_pki()) { - (SlotId::Retired(slot), PublicKeyInfo::EcP256(pubkey)) => { - p256::Recipient::from_encoded(pubkey).map(|r| (key, slot, r)) + match key.slot() { + SlotId::Retired(slot) => { + p256::Recipient::from_certificate(key.certificate()).map(|r| (key, slot, r)) } _ => None, } @@ -244,12 +246,9 @@ fn print_multiple( }; // Only P-256 keys are compatible with us. - let recipient = match key.certificate().subject_pki() { - PublicKeyInfo::EcP256(pubkey) => match p256::Recipient::from_encoded(pubkey) { - Some(recipient) => recipient, - None => continue, - }, - _ => continue, + let recipient = match p256::Recipient::from_certificate(key.certificate()) { + Some(recipient) => recipient, + None => continue, }; let stub = key::Stub::new(yubikey.serial(), slot, &recipient); @@ -268,15 +267,7 @@ fn print_multiple( println!(); } if printed > 1 { - eprintln!( - "{}", - i18n_embed_fl::fl!( - LANGUAGE_LOADER, - "printed-multiple", - kind = kind, - count = printed, - ) - ); + eprintln!("{}", fl!("printed-multiple", kind = kind, count = printed)); } Ok(()) @@ -382,8 +373,7 @@ fn main() -> Result<(), Error> { eprintln!( "{}", - i18n_embed_fl::fl!( - LANGUAGE_LOADER, + fl!( "cli-setup-intro", generate_usage = "age-plugin-yubikey --generate", ) @@ -402,8 +392,7 @@ fn main() -> Result<(), Error> { .iter() .map(|reader| { key::open_connection(reader).map(|yk| { - i18n_embed_fl::fl!( - LANGUAGE_LOADER, + fl!( "cli-setup-yk-name", yubikey_name = reader.name(), yubikey_serial = yk.serial().to_string(), @@ -429,20 +418,17 @@ fn main() -> Result<(), Error> { .map(|&slot| { keys.iter() .find(|key| key.slot() == SlotId::Retired(slot)) - .map(|key| match key.certificate().subject_pki() { - PublicKeyInfo::EcP256(pubkey) => { - p256::Recipient::from_encoded(pubkey).map(|_| { - // Cache the details we need to display to the user. - let (_, cert) = - x509_parser::parse_x509_certificate(key.certificate().as_ref()) - .unwrap(); - let (name, _) = util::extract_name(&cert, true).unwrap(); - let created = cert.validity().not_before.to_rfc2822(); + .map(|key| { + p256::Recipient::from_certificate(key.certificate()).map(|_| { + // Cache the details we need to display to the user. + let (_, cert) = + x509_parser::parse_x509_certificate(key.certificate().as_ref()) + .unwrap(); + let (name, _) = util::extract_name(&cert, true).unwrap(); + let created = cert.validity().not_before.to_rfc2822(); - format!("{}, created: {}", name, created) - }) - } - _ => None, + format!("{}, created: {}", name, created) + }) }) }) .collect(); @@ -455,20 +441,13 @@ fn main() -> Result<(), Error> { let i = i + 1; match occupied { - Some(Some(name)) => i18n_embed_fl::fl!( - LANGUAGE_LOADER, + Some(Some(name)) => fl!( "cli-setup-slot-usable", slot_index = i, slot_name = name.as_str(), ), - Some(None) => i18n_embed_fl::fl!( - LANGUAGE_LOADER, - "cli-setup-slot-unusable", - slot_index = i, - ), - None => { - i18n_embed_fl::fl!(LANGUAGE_LOADER, "cli-setup-slot-empty", slot_index = i) - } + Some(None) => fl!("cli-setup-slot-unusable", slot_index = i), + None => fl!("cli-setup-slot-empty", slot_index = i), } }) .collect(); @@ -492,19 +471,11 @@ fn main() -> Result<(), Error> { }; if let Some(key) = keys.iter().find(|key| key.slot() == SlotId::Retired(slot)) { - let recipient = match key.certificate().subject_pki() { - PublicKeyInfo::EcP256(pubkey) => { - p256::Recipient::from_encoded(pubkey).expect("We checked this above") - } - _ => unreachable!(), - }; + let recipient = p256::Recipient::from_certificate(key.certificate()) + .expect("We checked this above"); if Confirm::new() - .with_prompt(i18n_embed_fl::fl!( - LANGUAGE_LOADER, - "cli-setup-use-existing", - slot_index = slot_index, - )) + .with_prompt(fl!("cli-setup-use-existing", slot_index = slot_index)) .interact()? { let stub = key::Stub::new(yubikey.serial(), slot, &recipient); @@ -576,11 +547,7 @@ fn main() -> Result<(), Error> { }; if Confirm::new() - .with_prompt(i18n_embed_fl::fl!( - LANGUAGE_LOADER, - "cli-setup-generate-new", - slot_index = slot_index, - )) + .with_prompt(fl!("cli-setup-generate-new", slot_index = slot_index)) .interact()? { eprintln!(); @@ -632,8 +599,7 @@ fn main() -> Result<(), Error> { writeln!( file, "{}", - i18n_embed_fl::fl!( - LANGUAGE_LOADER, + fl!( "yubikey-identity", yubikey_metadata = metadata.to_string(), recipient = recipient.to_string(), @@ -668,8 +634,7 @@ fn main() -> Result<(), Error> { eprintln!(); eprintln!( "{}", - i18n_embed_fl::fl!( - LANGUAGE_LOADER, + fl!( "cli-setup-finished", is_new = if is_new { "true" } else { "false" }, recipient = recipient.to_string(), diff --git a/src/p256.rs b/src/p256.rs index a85bb47..1eeecad 100644 --- a/src/p256.rs +++ b/src/p256.rs @@ -1,6 +1,8 @@ use bech32::{ToBase32, Variant}; use p256::elliptic_curve::sec1::{FromEncodedPoint, ToEncodedPoint}; use sha2::{Digest, Sha256}; +use yubikey::{certificate::PublicKeyInfo, Certificate}; + use std::fmt; use crate::RECIPIENT_PREFIX; @@ -42,11 +44,22 @@ impl Recipient { } } + pub(crate) fn from_certificate(cert: &Certificate) -> Option { + Self::from_spki(cert.subject_pki()) + } + + pub(crate) fn from_spki(spki: &PublicKeyInfo) -> Option { + match spki { + PublicKeyInfo::EcP256(pubkey) => Self::from_encoded(pubkey), + _ => None, + } + } + /// Attempts to parse a valid YubiKey recipient from its SEC-1 encoding. /// /// This accepts both compressed (as used by the plugin) and uncompressed (as used in /// the YubiKey certificate) encodings. - pub(crate) fn from_encoded(encoded: &p256::EncodedPoint) -> Option { + fn from_encoded(encoded: &p256::EncodedPoint) -> Option { p256::PublicKey::from_encoded_point(encoded).map(Recipient) } diff --git a/src/plugin.rs b/src/plugin.rs index 2ccb3f8..e29edf4 100644 --- a/src/plugin.rs +++ b/src/plugin.rs @@ -71,8 +71,7 @@ impl RecipientPluginV1 for RecipientPlugin { Ok(Some(conn)) => yk_recipients.push(conn.recipient().clone()), Ok(None) => yk_errors.push(recipient::Error::Identity { index: stub.identity_index, - message: i18n_embed_fl::fl!( - crate::LANGUAGE_LOADER, + message: fl!( "plugin-err-yk-opening", yubikey_serial = stub.serial.to_string(), ), diff --git a/src/util.rs b/src/util.rs index 5e04f6b..ce6460d 100644 --- a/src/util.rs +++ b/src/util.rs @@ -180,8 +180,7 @@ impl fmt::Display for Metadata { write!( f, "{}", - i18n_embed_fl::fl!( - crate::LANGUAGE_LOADER, + fl!( "yubikey-metadata", serial = self.serial.to_string(), slot = slot_to_ui(&self.slot), @@ -197,20 +196,13 @@ impl fmt::Display for Metadata { pub(crate) fn print_identity(stub: Stub, recipient: Recipient, metadata: Metadata) { let recipient = recipient.to_string(); if !console::user_attended() { - eprintln!( - "{}", - i18n_embed_fl::fl!( - crate::LANGUAGE_LOADER, - "print-recipient", - recipient = recipient.as_str(), - ) - ); + let recipient = recipient.as_str(); + eprintln!("{}", fl!("print-recipient", recipient = recipient)); } println!( "{}", - i18n_embed_fl::fl!( - crate::LANGUAGE_LOADER, + fl!( "yubikey-identity", yubikey_metadata = metadata.to_string(), recipient = recipient,