diff --git a/openpgp-card-sequoia/src/card.rs b/openpgp-card-sequoia/src/card.rs index ab48355..fc27421 100644 --- a/openpgp-card-sequoia/src/card.rs +++ b/openpgp-card-sequoia/src/card.rs @@ -419,6 +419,18 @@ impl<'a> Card> { self.state.opt.attestation_certificate() } + pub fn attestation_key_fingerprint(&mut self) -> Result, Error> { + self.state.ard.attestation_key_fingerprint() + } + + pub fn attestation_key_algorithm_attributes(&mut self) -> Result, Error> { + self.state.ard.attestation_key_algorithm_attributes() + } + + pub fn attestation_key_generation_time(&mut self) -> Result, Error> { + self.state.ard.attestation_key_generation_time() + } + /// Firmware Version, YubiKey specific (?) pub fn firmware_version(&mut self) -> Result, Error> { self.state.opt.firmware_version() diff --git a/openpgp-card/src/card_do.rs b/openpgp-card/src/card_do.rs index b46e1a0..9fc1ce4 100644 --- a/openpgp-card/src/card_do.rs +++ b/openpgp-card/src/card_do.rs @@ -168,7 +168,7 @@ impl ApplicationRelatedData { } /// Generation dates/times of key pairs - pub fn key_generation_times(&self) -> Result, crate::Error> { + pub fn key_generation_times(&self) -> Result, Error> { let kg = self.0.find(Tags::GenerationTimes); if let Some(kg) = kg { @@ -219,6 +219,47 @@ impl ApplicationRelatedData { } } + /// Get Attestation key fingerprint. + pub fn attestation_key_fingerprint(&self) -> Result, Error> { + match self.0.find(Tags::FingerprintAttestation) { + None => Ok(None), + Some(data) => { + // FIXME: move conversion logic to Fingerprint + if data.serialize().iter().any(|&b| b != 0) { + Ok(Some(Fingerprint::try_from(data.serialize().as_slice())?)) + } else { + Ok(None) + } + } + } + } + + /// Get Attestation key algorithm attributes. + pub fn attestation_key_algorithm_attributes(&mut self) -> Result, Error> { + match self.0.find(Tags::AlgorithmAttributesAttestation) { + None => Ok(None), + Some(data) => Ok(Some(Algo::try_from(data.serialize().as_slice())?)), + } + } + + /// Get Attestation key generation time. + pub fn attestation_key_generation_time(&mut self) -> Result, Error> { + match self.0.find(Tags::GenerationTimeAttestation) { + None => Ok(None), + Some(data) => { + // FIXME: move conversion logic to KeyGenerationTime + + // Generation time of key, binary. 4 bytes, Big Endian. + // Value shall be seconds since Jan 1, 1970. Default value is 00000000 (not specified). + assert_eq!(data.serialize().len(), 4); + match u32::from_be_bytes(data.serialize().try_into().unwrap()) { + 0 => Ok(None), + kgt => Ok(Some(kgt.into())), + } + } + } + } + pub fn uif_attestation(&self) -> Result, Error> { let uif = self.0.find(Tags::UifAttestation); diff --git a/tools/src/bin/opgpcard/commands/status.rs b/tools/src/bin/opgpcard/commands/status.rs index 19d3863..d55b7f0 100644 --- a/tools/src/bin/opgpcard/commands/status.rs +++ b/tools/src/bin/opgpcard/commands/status.rs @@ -165,22 +165,40 @@ pub fn print_status( } output.authentication_key(authentication_key); - // technical details about the card's state + let mut attestation_key = output::KeySlotInfo::default(); + if let Ok(Some(fp)) = card.attestation_key_fingerprint() { + attestation_key.fingerprint(fp.to_spaced_hex()); + } + if let Ok(Some(algo)) = card.attestation_key_algorithm_attributes() { + attestation_key.algorithm(format!("{}", algo)); + } + if let Ok(Some(kgt)) = card.attestation_key_generation_time() { + attestation_key.created(format!("{}", kgt.to_datetime())); + } + if let Some(uif) = card.uif_attestation()? { + attestation_key.touch_policy(format!("{}", uif.touch_policy())); + attestation_key.touch_features(format!("{}", uif.features())); + } + // TODO: get public key data for the attestation key from the card + // if command.pkm { + // if let Ok(pkm) = card.public_key(KeyType::Attestation) { + // attestation_key.public_key_material(pkm.to_string()); + // } + // } + + // TODO: clarify how to reliably map `card.key_information()` output into this field (see below) + // if let Some(ks) = ki.as_ref().map(|ki| ki.aut_status()) { + // attestation_key.status(format!("{}", ks)); + // } + + output.attestation_key(attestation_key); + + // technical details about the card's state output.user_pin_remaining_attempts(pws.err_count_pw1()); output.admin_pin_remaining_attempts(pws.err_count_pw3()); output.reset_code_remaining_attempts(pws.err_count_rc()); - // FIXME: Handle attestation key information as a separate - // KeySlotInfo! Attestation touch information should go into its - // own `Option`, and (if any information about the - // attestation key exists at all, which is not the case for most - // cards) it should be printed as a fourth KeySlot block. - if let Some(uif) = card.uif_attestation()? { - output.card_touch_policy(uif.touch_policy().to_string()); - output.card_touch_features(uif.features().to_string()); - } - if let Some(ki) = ki { let num = ki.num_additional(); for i in 0..num { diff --git a/tools/src/bin/opgpcard/output/status.rs b/tools/src/bin/opgpcard/output/status.rs index 4e462ad..6085c77 100644 --- a/tools/src/bin/opgpcard/output/status.rs +++ b/tools/src/bin/opgpcard/output/status.rs @@ -18,11 +18,10 @@ pub struct Status { signature_count: u32, decryption_key: KeySlotInfo, authentication_key: KeySlotInfo, + attestation_key: Option, user_pin_remaining_attempts: u8, admin_pin_remaining_attempts: u8, reset_code_remaining_attempts: u8, - card_touch_policy: String, - card_touch_features: String, key_statuses: Vec<(u8, String)>, ca_fingerprints: Vec, } @@ -68,6 +67,10 @@ impl Status { self.authentication_key = key; } + pub fn attestation_key(&mut self, key: KeySlotInfo) { + self.attestation_key = Some(key); + } + pub fn user_pin_remaining_attempts(&mut self, count: u8) { self.user_pin_remaining_attempts = count; } @@ -80,14 +83,6 @@ impl Status { self.reset_code_remaining_attempts = count; } - pub fn card_touch_policy(&mut self, policy: String) { - self.card_touch_policy = policy; - } - - pub fn card_touch_features(&mut self, features: String) { - self.card_touch_features = features; - } - pub fn key_status(&mut self, keyref: u8, status: String) { self.key_statuses.push((keyref, status)); } @@ -150,6 +145,18 @@ impl Status { } s.push('\n'); + if self.verbose { + if let Some(attestation_key) = &self.attestation_key { + if attestation_key.touch_policy.is_some() || attestation_key.algorithm.is_some() { + s.push_str("Attestation key:\n"); + for line in attestation_key.format(self.verbose) { + s.push_str(&format!(" {}\n", line)); + } + s.push('\n'); + } + } + } + s.push_str(&format!( "Remaining PIN attempts: User: {}, Admin: {}, Reset Code: {}\n", self.user_pin_remaining_attempts, @@ -158,11 +165,6 @@ impl Status { )); if self.verbose { - s.push_str(&format!( - "Touch policy attestation: {}\n", - self.card_touch_policy - )); - for (keyref, status) in self.key_statuses.iter() { s.push_str(&format!("Key status (#{}): {}\n", keyref, status)); } @@ -183,11 +185,10 @@ impl Status { signature_count: self.signature_count, decryption_key: self.decryption_key.clone(), authentication_key: self.authentication_key.clone(), + attestation_key: self.attestation_key.clone(), user_pin_remaining_attempts: self.user_pin_remaining_attempts, admin_pin_remaining_attempts: self.admin_pin_remaining_attempts, reset_code_remaining_attempts: self.reset_code_remaining_attempts, - card_touch_policy: self.card_touch_policy.clone(), - card_touch_features: self.card_touch_features.clone(), key_statuses: self.key_statuses.clone(), ca_fingerprints: self.ca_fingerprints.clone(), }) @@ -232,11 +233,10 @@ pub struct StatusV0 { signature_count: u32, decryption_key: KeySlotInfo, authentication_key: KeySlotInfo, + attestation_key: Option, user_pin_remaining_attempts: u8, admin_pin_remaining_attempts: u8, reset_code_remaining_attempts: u8, - card_touch_policy: String, - card_touch_features: String, key_statuses: Vec<(u8, String)>, ca_fingerprints: Vec, }