diff --git a/CHANGELOG.md b/CHANGELOG.md index c2979f2..4728cfc 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -16,6 +16,8 @@ to 0.3.0 are beta releases. ### Changed - MSRV is now 1.70.0. +- Encryption to an identity now uses the preferred recipient type supported for + that identity. ## [0.5.0] - 2024-08-04 ### Fixed diff --git a/src/key.rs b/src/key.rs index 4e1375e..1714532 100644 --- a/src/key.rs +++ b/src/key.rs @@ -20,7 +20,9 @@ use yubikey::{ use crate::{ error::Error, - fl, piv_p256, + fl, + native::p256tag, + piv_p256, recipient::TAG_BYTES, util::{otp_serial_prefix, Metadata}, Recipient, IDENTITY_PREFIX, @@ -592,9 +594,10 @@ impl Stub { let (cert, pk) = match Certificate::read(&mut yubikey, SlotId::Retired(self.slot)) .ok() .and_then(|cert| { - piv_p256::Recipient::from_certificate(&cert) - .filter(|pk| pk.tag() == self.tag) - .map(|pk| (cert, Recipient::PivP256(pk))) + // Parse as the preferred recipient for each identity type. + p256tag::Recipient::from_certificate(&cert) + .filter(|pk| pk.static_tag() == self.tag) + .map(|pk| (cert, Recipient::P256Tag(pk))) }) { Some(pk) => pk, None => { @@ -628,6 +631,7 @@ pub(crate) struct Connection { } impl Connection { + /// Returns the preferred recipient for encrypting to this identity. pub(crate) fn recipient(&self) -> &Recipient { &self.pk } diff --git a/src/native/p256tag.rs b/src/native/p256tag.rs index beb3cde..ad94675 100644 --- a/src/native/p256tag.rs +++ b/src/native/p256tag.rs @@ -13,6 +13,7 @@ use p256::{ EncodedPoint, }; use rand::rngs::OsRng; +use yubikey::{certificate::PublicKeyInfo, Certificate}; use super::{stanza_tag, YubiKeyKemPrivateKey}; use crate::{ @@ -84,6 +85,33 @@ impl Recipient { }) } + pub(crate) fn from_certificate(cert: &Certificate) -> Option { + Self::from_spki(cert.subject_pki()) + } + + pub(crate) fn from_spki(spki: &PublicKeyInfo) -> Option { + let encoded = match spki { + PublicKeyInfo::EcP256(pubkey) => Some(pubkey), + _ => None, + }?; + + // Check that the certificate encoding is uncompressed. + let pk_recip = ::PublicKey::from_bytes(encoded.as_bytes()).ok()?; + + let point = p256::PublicKey::from_encoded_point(encoded).into_option()?; + let compressed = point.to_encoded_point(true); + + Some(Self { + compressed, + pk_recip, + }) + } + + /// Returns the compressed SEC-1 encoding of this recipient. + pub(crate) fn to_compressed(&self) -> p256::EncodedPoint { + self.compressed + } + pub(crate) fn static_tag(&self) -> [u8; TAG_BYTES] { static_tag(self.compressed.as_bytes()) } diff --git a/src/piv_p256.rs b/src/piv_p256.rs index 17e05c8..26873d4 100644 --- a/src/piv_p256.rs +++ b/src/piv_p256.rs @@ -111,7 +111,7 @@ impl Recipient { let shared_secret = esk.diffie_hellman(self.public_key()); - let salt = salt(&epk_bytes, self); + let salt = salt(&epk_bytes, self.to_encoded()); let enc_key = { let mut okm = [0; 32]; @@ -138,13 +138,17 @@ impl Recipient { impl RecipientLine { pub(crate) fn unwrap_file_key(&self, conn: &mut Connection) -> Result { - let recipient = match conn.recipient() { - crate::recipient::Recipient::PivP256(recipient) => recipient, - _ => panic!("should have been filtered out earlier"), + let (static_tag, pk) = match conn.recipient() { + crate::recipient::Recipient::PivP256(recipient) => { + (recipient.tag(), recipient.to_encoded()) + } + crate::recipient::Recipient::P256Tag(recipient) => { + (recipient.static_tag(), recipient.to_compressed()) + } }; - assert_eq!(self.tag, recipient.tag()); + assert_eq!(self.tag, static_tag); - let salt = salt(&self.epk_bytes, recipient); + let salt = salt(&self.epk_bytes, pk); // The YubiKey API for performing scalar multiplication takes the point in its // uncompressed SEC-1 encoding. @@ -165,9 +169,10 @@ impl RecipientLine { } } -fn salt(epk_bytes: &EphemeralKeyBytes, pk: &Recipient) -> Vec { +fn salt(epk_bytes: &EphemeralKeyBytes, pk: p256::EncodedPoint) -> Vec { + assert!(pk.is_compressed()); let mut salt = vec![]; salt.extend_from_slice(epk_bytes.as_bytes()); - salt.extend_from_slice(pk.to_encoded().as_bytes()); + salt.extend_from_slice(pk.as_bytes()); salt }