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
- 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
+8 -4
View File
@@ -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
}
+28
View File
@@ -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> {
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] {
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 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<FileKey, ()> {
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<u8> {
fn salt(epk_bytes: &EphemeralKeyBytes, pk: p256::EncodedPoint) -> Vec<u8> {
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
}