Change recipient type for identity encryption to p256tag

Encrypting to an identity requires the plugin binary, and there is a
reasonable expectation that the same (or a later) plugin binary version
will be used to decrypt, so we can assume support for the preferred
recipient type.
This commit is contained in:
Jack Grigg
2025-12-21 12:21:49 +00:00
parent 0057a1825e
commit 971d63957c
4 changed files with 51 additions and 12 deletions
+2
View File
@@ -16,6 +16,8 @@ to 0.3.0 are beta releases.
### Changed ### Changed
- MSRV is now 1.70.0. - 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 ## [0.5.0] - 2024-08-04
### Fixed ### Fixed
+8 -4
View File
@@ -20,7 +20,9 @@ use yubikey::{
use crate::{ use crate::{
error::Error, error::Error,
fl, piv_p256, fl,
native::p256tag,
piv_p256,
recipient::TAG_BYTES, recipient::TAG_BYTES,
util::{otp_serial_prefix, Metadata}, util::{otp_serial_prefix, Metadata},
Recipient, IDENTITY_PREFIX, Recipient, IDENTITY_PREFIX,
@@ -592,9 +594,10 @@ impl Stub {
let (cert, pk) = match Certificate::read(&mut yubikey, SlotId::Retired(self.slot)) let (cert, pk) = match Certificate::read(&mut yubikey, SlotId::Retired(self.slot))
.ok() .ok()
.and_then(|cert| { .and_then(|cert| {
piv_p256::Recipient::from_certificate(&cert) // Parse as the preferred recipient for each identity type.
.filter(|pk| pk.tag() == self.tag) p256tag::Recipient::from_certificate(&cert)
.map(|pk| (cert, Recipient::PivP256(pk))) .filter(|pk| pk.static_tag() == self.tag)
.map(|pk| (cert, Recipient::P256Tag(pk)))
}) { }) {
Some(pk) => pk, Some(pk) => pk,
None => { None => {
@@ -628,6 +631,7 @@ pub(crate) struct Connection {
} }
impl Connection { impl Connection {
/// Returns the preferred recipient for encrypting to this identity.
pub(crate) fn recipient(&self) -> &Recipient { pub(crate) fn recipient(&self) -> &Recipient {
&self.pk &self.pk
} }
+28
View File
@@ -13,6 +13,7 @@ use p256::{
EncodedPoint, EncodedPoint,
}; };
use rand::rngs::OsRng; use rand::rngs::OsRng;
use yubikey::{certificate::PublicKeyInfo, Certificate};
use super::{stanza_tag, YubiKeyKemPrivateKey}; use super::{stanza_tag, YubiKeyKemPrivateKey};
use crate::{ use crate::{
@@ -84,6 +85,33 @@ impl Recipient {
}) })
} }
pub(crate) fn from_certificate(cert: &Certificate) -> Option<Self> {
Self::from_spki(cert.subject_pki())
}
pub(crate) fn from_spki(spki: &PublicKeyInfo) -> Option<Self> {
let encoded = match spki {
PublicKeyInfo::EcP256(pubkey) => Some(pubkey),
_ => None,
}?;
// Check that the certificate encoding is uncompressed.
let pk_recip = <Kem as hpke::Kem>::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] { pub(crate) fn static_tag(&self) -> [u8; TAG_BYTES] {
static_tag(self.compressed.as_bytes()) static_tag(self.compressed.as_bytes())
} }
+13 -8
View File
@@ -111,7 +111,7 @@ impl Recipient {
let shared_secret = esk.diffie_hellman(self.public_key()); 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 enc_key = {
let mut okm = [0; 32]; let mut okm = [0; 32];
@@ -138,13 +138,17 @@ impl Recipient {
impl RecipientLine { impl RecipientLine {
pub(crate) fn unwrap_file_key(&self, conn: &mut Connection) -> Result<FileKey, ()> { pub(crate) fn unwrap_file_key(&self, conn: &mut Connection) -> Result<FileKey, ()> {
let recipient = match conn.recipient() { let (static_tag, pk) = match conn.recipient() {
crate::recipient::Recipient::PivP256(recipient) => recipient, crate::recipient::Recipient::PivP256(recipient) => {
_ => panic!("should have been filtered out earlier"), (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 // The YubiKey API for performing scalar multiplication takes the point in its
// uncompressed SEC-1 encoding. // uncompressed SEC-1 encoding.
@@ -165,9 +169,10 @@ impl RecipientLine {
} }
} }
fn salt(epk_bytes: &EphemeralKeyBytes, pk: &Recipient) -> Vec<u8> { fn salt(epk_bytes: &EphemeralKeyBytes, pk: p256::EncodedPoint) -> Vec<u8> {
assert!(pk.is_compressed());
let mut salt = vec![]; let mut salt = vec![];
salt.extend_from_slice(epk_bytes.as_bytes()); 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 salt
} }