use crate::error::PdfError;
use super::cms::{
KeyAgreeRecipientId, KeyAgreeRecipientInfo, OriginatorId, OriginatorPublicKey,
RecipientEncryptedKey,
};
use super::der::{read_oid, read_sequence, write_oid, write_sequence};
pub const OID_EC_PUBLIC_KEY: [u64; 6] = [1, 2, 840, 10045, 2, 1];
pub const OID_SECP256R1: [u64; 7] = [1, 2, 840, 10045, 3, 1, 7];
pub const OID_SECP384R1: [u64; 5] = [1, 3, 132, 0, 34];
pub const OID_SECP521R1: [u64; 5] = [1, 3, 132, 0, 35];
pub const OID_X25519: [u64; 4] = [1, 3, 101, 110];
pub const OID_X448: [u64; 4] = [1, 3, 101, 111];
pub const OID_DH_SINGLE_PASS_STDDH_SHA256_KDF: [u64; 6] = [1, 3, 132, 1, 11, 1];
pub const OID_DH_SINGLE_PASS_STDDH_SHA384_KDF: [u64; 6] = [1, 3, 132, 1, 11, 2];
pub const OID_DH_SINGLE_PASS_STDDH_SHA512_KDF: [u64; 6] = [1, 3, 132, 1, 11, 3];
pub const OID_DH_SINGLE_PASS_STDDH_HKDF_SHA256_SCHEME: [u64; 9] =
[1, 2, 840, 113549, 1, 9, 16, 3, 19];
pub const OID_DH_SINGLE_PASS_STDDH_HKDF_SHA384_SCHEME: [u64; 9] =
[1, 2, 840, 113549, 1, 9, 16, 3, 20];
pub const OID_DH_SINGLE_PASS_STDDH_HKDF_SHA512_SCHEME: [u64; 9] =
[1, 2, 840, 113549, 1, 9, 16, 3, 21];
pub const OID_AES128_WRAP: [u64; 9] = [2, 16, 840, 1, 101, 3, 4, 1, 5];
pub const OID_AES192_WRAP: [u64; 9] = [2, 16, 840, 1, 101, 3, 4, 1, 25];
pub const OID_AES256_WRAP: [u64; 9] = [2, 16, 840, 1, 101, 3, 4, 1, 45];
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum WrapAlgorithm {
Aes128,
Aes192,
Aes256,
}
impl WrapAlgorithm {
pub fn kek_len(self) -> usize {
match self {
Self::Aes128 => 16,
Self::Aes192 => 24,
Self::Aes256 => 32,
}
}
pub fn oid(self) -> &'static [u64] {
match self {
Self::Aes128 => &OID_AES128_WRAP,
Self::Aes192 => &OID_AES192_WRAP,
Self::Aes256 => &OID_AES256_WRAP,
}
}
pub fn from_oid(oid: &[u64]) -> Option<Self> {
if oid == OID_AES128_WRAP {
Some(Self::Aes128)
} else if oid == OID_AES192_WRAP {
Some(Self::Aes192)
} else if oid == OID_AES256_WRAP {
Some(Self::Aes256)
} else {
None
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum KariCurve {
P256,
P384,
P521,
X25519,
X448,
}
impl KariCurve {
pub fn algorithm_oid(self) -> &'static [u64] {
match self {
Self::P256 | Self::P384 | Self::P521 => &OID_EC_PUBLIC_KEY,
Self::X25519 => &OID_X25519,
Self::X448 => &OID_X448,
}
}
pub fn algorithm_params(self) -> Vec<u8> {
match self {
Self::P256 => write_oid(&OID_SECP256R1),
Self::P384 => write_oid(&OID_SECP384R1),
Self::P521 => write_oid(&OID_SECP521R1),
Self::X25519 | Self::X448 => Vec::new(),
}
}
pub fn kea_oid(self) -> &'static [u64] {
match self {
Self::P256 | Self::X25519 => &OID_DH_SINGLE_PASS_STDDH_SHA256_KDF,
Self::P384 => &OID_DH_SINGLE_PASS_STDDH_SHA384_KDF,
Self::P521 | Self::X448 => &OID_DH_SINGLE_PASS_STDDH_SHA512_KDF,
}
}
pub fn default_kdf(self) -> KariKdf {
match self {
Self::P256 | Self::X25519 => KariKdf::X963Sha256,
Self::P384 => KariKdf::X963Sha384,
Self::P521 | Self::X448 => KariKdf::X963Sha512,
}
}
pub fn pub_point_len(self) -> usize {
match self {
Self::P256 => 65,
Self::P384 => 97,
Self::P521 => 133,
Self::X25519 => 32,
Self::X448 => 56,
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum KariKdf {
X963Sha256,
X963Sha384,
X963Sha512,
HkdfSha256,
HkdfSha384,
HkdfSha512,
}
impl KariKdf {
pub fn kea_oid(self) -> &'static [u64] {
match self {
Self::X963Sha256 => &OID_DH_SINGLE_PASS_STDDH_SHA256_KDF,
Self::X963Sha384 => &OID_DH_SINGLE_PASS_STDDH_SHA384_KDF,
Self::X963Sha512 => &OID_DH_SINGLE_PASS_STDDH_SHA512_KDF,
Self::HkdfSha256 => &OID_DH_SINGLE_PASS_STDDH_HKDF_SHA256_SCHEME,
Self::HkdfSha384 => &OID_DH_SINGLE_PASS_STDDH_HKDF_SHA384_SCHEME,
Self::HkdfSha512 => &OID_DH_SINGLE_PASS_STDDH_HKDF_SHA512_SCHEME,
}
}
pub fn from_kea_oid(oid: &[u64]) -> Option<Self> {
if oid == OID_DH_SINGLE_PASS_STDDH_SHA256_KDF {
Some(Self::X963Sha256)
} else if oid == OID_DH_SINGLE_PASS_STDDH_SHA384_KDF {
Some(Self::X963Sha384)
} else if oid == OID_DH_SINGLE_PASS_STDDH_SHA512_KDF {
Some(Self::X963Sha512)
} else if oid == OID_DH_SINGLE_PASS_STDDH_HKDF_SHA256_SCHEME {
Some(Self::HkdfSha256)
} else if oid == OID_DH_SINGLE_PASS_STDDH_HKDF_SHA384_SCHEME {
Some(Self::HkdfSha384)
} else if oid == OID_DH_SINGLE_PASS_STDDH_HKDF_SHA512_SCHEME {
Some(Self::HkdfSha512)
} else {
None
}
}
pub fn is_valid_for(self, curve: KariCurve) -> bool {
match curve {
KariCurve::P256 => self == Self::X963Sha256,
KariCurve::P384 => self == Self::X963Sha384,
KariCurve::P521 => self == Self::X963Sha512,
KariCurve::X25519 => matches!(
self,
Self::X963Sha256 | Self::HkdfSha256 | Self::HkdfSha384 | Self::HkdfSha512
),
KariCurve::X448 => matches!(
self,
Self::X963Sha512 | Self::HkdfSha256 | Self::HkdfSha384 | Self::HkdfSha512
),
}
}
}
pub fn x963_kdf<H: sha2::Digest>(z: &[u8], shared_info: &[u8], keydatalen: usize) -> Vec<u8> {
let hashlen = <H as sha2::Digest>::output_size();
let n = keydatalen.div_ceil(hashlen);
let mut out = Vec::with_capacity(n * hashlen);
for counter in 1u32..=(n as u32) {
let mut h = H::new();
h.update(z);
h.update(counter.to_be_bytes());
h.update(shared_info);
out.extend_from_slice(&h.finalize());
}
out.truncate(keydatalen);
out
}
pub fn x963_kdf_sha256(z: &[u8], shared_info: &[u8], keydatalen: usize) -> Vec<u8> {
x963_kdf::<sha2::Sha256>(z, shared_info, keydatalen)
}
pub fn hkdf_kdf_sha256(salt: &[u8], ikm: &[u8], info: &[u8], keydatalen: usize) -> Vec<u8> {
let salt_opt = if salt.is_empty() { None } else { Some(salt) };
let hk = hkdf::Hkdf::<sha2::Sha256>::new(salt_opt, ikm);
let mut okm = vec![0u8; keydatalen];
hk.expand(info, &mut okm)
.expect("HKDF-SHA-256 keydatalen within 255 * HashLen");
okm
}
pub fn hkdf_kdf_sha384(salt: &[u8], ikm: &[u8], info: &[u8], keydatalen: usize) -> Vec<u8> {
let salt_opt = if salt.is_empty() { None } else { Some(salt) };
let hk = hkdf::Hkdf::<sha2::Sha384>::new(salt_opt, ikm);
let mut okm = vec![0u8; keydatalen];
hk.expand(info, &mut okm)
.expect("HKDF-SHA-384 keydatalen within 255 * HashLen");
okm
}
pub fn hkdf_kdf_sha512(salt: &[u8], ikm: &[u8], info: &[u8], keydatalen: usize) -> Vec<u8> {
let salt_opt = if salt.is_empty() { None } else { Some(salt) };
let hk = hkdf::Hkdf::<sha2::Sha512>::new(salt_opt, ikm);
let mut okm = vec![0u8; keydatalen];
hk.expand(info, &mut okm)
.expect("HKDF-SHA-512 keydatalen within 255 * HashLen");
okm
}
pub fn derive_kek(
kdf: KariKdf,
z: &[u8],
ukm: Option<&[u8]>,
shared_info: &[u8],
keydatalen: usize,
) -> Vec<u8> {
match kdf {
KariKdf::X963Sha256 => x963_kdf::<sha2::Sha256>(z, shared_info, keydatalen),
KariKdf::X963Sha384 => x963_kdf::<sha2::Sha384>(z, shared_info, keydatalen),
KariKdf::X963Sha512 => x963_kdf::<sha2::Sha512>(z, shared_info, keydatalen),
KariKdf::HkdfSha256 => hkdf_kdf_sha256(ukm.unwrap_or(&[]), z, shared_info, keydatalen),
KariKdf::HkdfSha384 => hkdf_kdf_sha384(ukm.unwrap_or(&[]), z, shared_info, keydatalen),
KariKdf::HkdfSha512 => hkdf_kdf_sha512(ukm.unwrap_or(&[]), z, shared_info, keydatalen),
}
}
pub fn build_ecc_cms_shared_info(
wrap_oid: &[u64],
entity_u_info: Option<&[u8]>,
key_bit_length: u32,
) -> Vec<u8> {
use super::der::{write_context_constructed, write_octet_string, write_tlv, Class};
let key_info = write_sequence(&write_oid(wrap_oid));
let mut body = key_info;
if let Some(ukm) = entity_u_info {
body.extend_from_slice(&write_context_constructed(0, &write_octet_string(ukm)));
}
let supp_pub_body = write_octet_string(&key_bit_length.to_be_bytes());
body.extend_from_slice(&write_tlv(Class::ContextSpecific, true, 2, &supp_pub_body));
write_sequence(&body)
}
#[derive(Debug, Clone)]
pub struct EcRecipient {
pub curve: KariCurve,
pub private_scalar: Vec<u8>,
pub public_point_sec1: Vec<u8>,
}
impl EcRecipient {
pub fn p256(private_scalar: Vec<u8>, public_point_sec1: Vec<u8>) -> Self {
Self {
curve: KariCurve::P256,
private_scalar,
public_point_sec1,
}
}
pub fn p384(private_scalar: Vec<u8>, public_point_sec1: Vec<u8>) -> Self {
Self {
curve: KariCurve::P384,
private_scalar,
public_point_sec1,
}
}
pub fn p521(private_scalar: Vec<u8>, public_point_sec1: Vec<u8>) -> Self {
Self {
curve: KariCurve::P521,
private_scalar,
public_point_sec1,
}
}
pub fn x25519(private_scalar: Vec<u8>, public_point_sec1: Vec<u8>) -> Self {
Self {
curve: KariCurve::X25519,
private_scalar,
public_point_sec1,
}
}
pub fn x448(private_scalar: Vec<u8>, public_point_sec1: Vec<u8>) -> Self {
Self {
curve: KariCurve::X448,
private_scalar,
public_point_sec1,
}
}
}
pub fn unwrap_kari_p256(
kari: &KeyAgreeRecipientInfo,
recipient_slot: &RecipientEncryptedKey,
recipient: &EcRecipient,
) -> Result<Vec<u8>, PdfError> {
if recipient.curve != KariCurve::P256 {
return Err(PdfError::other(format!(
"PDF pubsec KARI: unwrap_kari_p256 invoked with {:?} recipient",
recipient.curve
)));
}
unwrap_kari(kari, recipient_slot, recipient)
}
pub fn unwrap_kari(
kari: &KeyAgreeRecipientInfo,
recipient_slot: &RecipientEncryptedKey,
recipient: &EcRecipient,
) -> Result<Vec<u8>, PdfError> {
unwrap_kari_with_trust_store(kari, recipient_slot, recipient, None)
}
pub fn unwrap_kari_with_trust_store(
kari: &KeyAgreeRecipientInfo,
recipient_slot: &RecipientEncryptedKey,
recipient: &EcRecipient,
trust_store: Option<&super::TrustStore>,
) -> Result<Vec<u8>, PdfError> {
let kdf = KariKdf::from_kea_oid(&kari.key_encryption_oid).ok_or_else(|| {
PdfError::other(format!(
"PDF pubsec KARI: unsupported KEA OID {:?} (no matching KDF scheme)",
kari.key_encryption_oid
))
})?;
if !kdf.is_valid_for(recipient.curve) {
return Err(PdfError::other(format!(
"PDF pubsec KARI: KDF {:?} is not a valid binding for curve {:?} \
(RFC 5753 §7.1.4 / RFC 8418 §2)",
kdf, recipient.curve,
)));
}
let (wrap_seq, rest) = read_sequence(&kari.key_encryption_params)?;
if !rest.is_empty() {
return Err(PdfError::other(
"PDF pubsec KARI: trailing bytes after KEA wrap AlgorithmIdentifier",
));
}
let (wrap_oid, _wrap_params) = read_oid(wrap_seq)?;
let wrap = WrapAlgorithm::from_oid(&wrap_oid).ok_or_else(|| {
PdfError::other(format!(
"PDF pubsec KARI: unsupported wrap algorithm OID {wrap_oid:?}"
))
})?;
let originator_point = match &kari.originator {
OriginatorId::OriginatorKey(opk) => extract_originator_point(opk, recipient.curve)?,
OriginatorId::IssuerAndSerial(ias) => {
let store = trust_store.ok_or_else(|| {
PdfError::other(
"PDF pubsec KARI: originator is IssuerAndSerial but no TrustStore \
was supplied (use open_with_certificate_and_trust_store)",
)
})?;
let key = super::CertRef::IssuerAndSerial {
issuer_der: ias.issuer_der.clone(),
serial: ias.serial.clone(),
};
let cert = store.lookup(&key).ok_or_else(|| {
PdfError::other(
"PDF pubsec KARI: originator IssuerAndSerial not found in TrustStore",
)
})?;
originator_point_from_cert(cert, recipient.curve)?
}
OriginatorId::SubjectKeyIdentifier(ski) => {
let store = trust_store.ok_or_else(|| {
PdfError::other(
"PDF pubsec KARI: originator is SubjectKeyIdentifier but no TrustStore \
was supplied (use open_with_certificate_and_trust_store)",
)
})?;
let cert = store
.lookup(&super::CertRef::SubjectKeyIdentifier(ski.clone()))
.ok_or_else(|| {
PdfError::other(
"PDF pubsec KARI: originator SubjectKeyIdentifier not found in TrustStore",
)
})?;
originator_point_from_cert(cert, recipient.curve)?
}
};
let z = ecdh_z(
recipient.curve,
&recipient.private_scalar,
&originator_point,
)?;
let ukm = if kari.ukm.is_empty() {
None
} else {
Some(kari.ukm.as_slice())
};
let shared_info = build_ecc_cms_shared_info(wrap.oid(), ukm, (wrap.kek_len() * 8) as u32);
let kek = derive_kek(kdf, &z, ukm, &shared_info, wrap.kek_len());
aes_kw_unwrap(wrap, &kek, &recipient_slot.encrypted_key)
}
fn originator_point_from_cert(
cert: &super::x509::Certificate,
expected: KariCurve,
) -> Result<Vec<u8>, PdfError> {
let bits = cert.spki_pubkey_bits.as_deref().ok_or_else(|| {
PdfError::other(
"PDF pubsec KARI: trust-store certificate has no SPKI BIT STRING contents \
— cannot recover originator public point",
)
})?;
if bits.len() != expected.pub_point_len() {
return Err(PdfError::other(format!(
"PDF pubsec KARI: trust-store originator SPKI length {} does not match \
{:?} expected {} bytes",
bits.len(),
expected,
expected.pub_point_len()
)));
}
Ok(bits.to_vec())
}
fn ecdh_z(curve: KariCurve, scalar: &[u8], originator_point: &[u8]) -> Result<Vec<u8>, PdfError> {
match curve {
KariCurve::P256 => {
use p256::{ecdh::diffie_hellman, PublicKey, SecretKey};
let secret = SecretKey::from_slice(scalar).map_err(|e| {
PdfError::other(format!(
"PDF pubsec KARI: invalid P-256 private scalar: {e}"
))
})?;
let originator = PublicKey::from_sec1_bytes(originator_point).map_err(|e| {
PdfError::other(format!(
"PDF pubsec KARI: invalid P-256 originator SEC1 point: {e}"
))
})?;
let shared = diffie_hellman(secret.to_nonzero_scalar(), originator.as_affine());
Ok(shared.raw_secret_bytes().to_vec())
}
KariCurve::P384 => {
use p384::{ecdh::diffie_hellman, PublicKey, SecretKey};
let secret = SecretKey::from_slice(scalar).map_err(|e| {
PdfError::other(format!(
"PDF pubsec KARI: invalid P-384 private scalar: {e}"
))
})?;
let originator = PublicKey::from_sec1_bytes(originator_point).map_err(|e| {
PdfError::other(format!(
"PDF pubsec KARI: invalid P-384 originator SEC1 point: {e}"
))
})?;
let shared = diffie_hellman(secret.to_nonzero_scalar(), originator.as_affine());
Ok(shared.raw_secret_bytes().to_vec())
}
KariCurve::P521 => {
use p521::{ecdh::diffie_hellman, PublicKey, SecretKey};
let secret = SecretKey::from_slice(scalar).map_err(|e| {
PdfError::other(format!(
"PDF pubsec KARI: invalid P-521 private scalar: {e}"
))
})?;
let originator = PublicKey::from_sec1_bytes(originator_point).map_err(|e| {
PdfError::other(format!(
"PDF pubsec KARI: invalid P-521 originator SEC1 point: {e}"
))
})?;
let shared = diffie_hellman(secret.to_nonzero_scalar(), originator.as_affine());
Ok(shared.raw_secret_bytes().to_vec())
}
KariCurve::X25519 => {
use x25519_dalek::{PublicKey, StaticSecret};
if scalar.len() != 32 {
return Err(PdfError::other(format!(
"PDF pubsec KARI: X25519 scalar must be 32 bytes (got {})",
scalar.len()
)));
}
if originator_point.len() != 32 {
return Err(PdfError::other(format!(
"PDF pubsec KARI: X25519 originator point must be 32 bytes (got {})",
originator_point.len()
)));
}
let mut s_arr = [0u8; 32];
s_arr.copy_from_slice(scalar);
let secret = StaticSecret::from(s_arr);
let mut p_arr = [0u8; 32];
p_arr.copy_from_slice(originator_point);
let pub_point = PublicKey::from(p_arr);
let shared = secret.diffie_hellman(&pub_point);
if shared.as_bytes().iter().all(|b| *b == 0) {
return Err(PdfError::other(
"PDF pubsec KARI: X25519 shared secret is all-zero (RFC 8418 §3 reject)",
));
}
Ok(shared.as_bytes().to_vec())
}
KariCurve::X448 => {
use x448::{PublicKey, StaticSecret};
if scalar.len() != 56 {
return Err(PdfError::other(format!(
"PDF pubsec KARI: X448 scalar must be 56 bytes (got {})",
scalar.len()
)));
}
if originator_point.len() != 56 {
return Err(PdfError::other(format!(
"PDF pubsec KARI: X448 originator point must be 56 bytes (got {})",
originator_point.len()
)));
}
let mut s_arr = [0u8; 56];
s_arr.copy_from_slice(scalar);
let secret = StaticSecret::from(s_arr);
let pub_point = PublicKey::from_bytes(originator_point).ok_or_else(|| {
PdfError::other(
"PDF pubsec KARI: X448 originator point is low-order \
(RFC 8418 §3 reject)",
)
})?;
let shared = secret.diffie_hellman(&pub_point);
if shared.as_bytes().iter().all(|b| *b == 0) {
return Err(PdfError::other(
"PDF pubsec KARI: X448 shared secret is all-zero (RFC 8418 §3 reject)",
));
}
Ok(shared.as_bytes().to_vec())
}
}
}
fn aes_kw_unwrap(wrap: WrapAlgorithm, kek: &[u8], wrapped: &[u8]) -> Result<Vec<u8>, PdfError> {
use aes_kw::{KekAes128, KekAes192, KekAes256};
match wrap {
WrapAlgorithm::Aes128 => {
let kek_arr: [u8; 16] = kek
.try_into()
.map_err(|_| PdfError::other("PDF pubsec KARI: AES-128 KEK length mismatch"))?;
KekAes128::from(kek_arr)
.unwrap_vec(wrapped)
.map_err(|e| PdfError::other(format!("PDF pubsec KARI: AES-KW unwrap failed: {e}")))
}
WrapAlgorithm::Aes192 => {
let kek_arr: [u8; 24] = kek
.try_into()
.map_err(|_| PdfError::other("PDF pubsec KARI: AES-192 KEK length mismatch"))?;
KekAes192::from(kek_arr)
.unwrap_vec(wrapped)
.map_err(|e| PdfError::other(format!("PDF pubsec KARI: AES-KW unwrap failed: {e}")))
}
WrapAlgorithm::Aes256 => {
let kek_arr: [u8; 32] = kek
.try_into()
.map_err(|_| PdfError::other("PDF pubsec KARI: AES-256 KEK length mismatch"))?;
KekAes256::from(kek_arr)
.unwrap_vec(wrapped)
.map_err(|e| PdfError::other(format!("PDF pubsec KARI: AES-KW unwrap failed: {e}")))
}
}
}
fn aes_kw_wrap(wrap: WrapAlgorithm, kek: &[u8], cek: &[u8]) -> Result<Vec<u8>, PdfError> {
use aes_kw::{KekAes128, KekAes192, KekAes256};
match wrap {
WrapAlgorithm::Aes128 => {
let kek_arr: [u8; 16] = kek
.try_into()
.map_err(|_| PdfError::other("PDF pubsec KARI: AES-128 KEK length mismatch"))?;
KekAes128::from(kek_arr)
.wrap_vec(cek)
.map_err(|e| PdfError::other(format!("PDF pubsec KARI: AES-KW wrap failed: {e}")))
}
WrapAlgorithm::Aes192 => {
let kek_arr: [u8; 24] = kek
.try_into()
.map_err(|_| PdfError::other("PDF pubsec KARI: AES-192 KEK length mismatch"))?;
KekAes192::from(kek_arr)
.wrap_vec(cek)
.map_err(|e| PdfError::other(format!("PDF pubsec KARI: AES-KW wrap failed: {e}")))
}
WrapAlgorithm::Aes256 => {
let kek_arr: [u8; 32] = kek
.try_into()
.map_err(|_| PdfError::other("PDF pubsec KARI: AES-256 KEK length mismatch"))?;
KekAes256::from(kek_arr)
.wrap_vec(cek)
.map_err(|e| PdfError::other(format!("PDF pubsec KARI: AES-KW wrap failed: {e}")))
}
}
}
fn extract_originator_point(
opk: &OriginatorPublicKey,
expected: KariCurve,
) -> Result<Vec<u8>, PdfError> {
match expected {
KariCurve::P256 | KariCurve::P384 | KariCurve::P521 => {
if opk.algorithm_oid != OID_EC_PUBLIC_KEY {
return Err(PdfError::other(format!(
"PDF pubsec KARI: originator algorithm OID {:?} is not ecPublicKey",
opk.algorithm_oid
)));
}
let (curve_oid, _rest) = read_oid(&opk.algorithm_params)?;
let want: &[u64] = match expected {
KariCurve::P256 => &OID_SECP256R1,
KariCurve::P384 => &OID_SECP384R1,
KariCurve::P521 => &OID_SECP521R1,
KariCurve::X25519 | KariCurve::X448 => unreachable!(),
};
if curve_oid != want {
return Err(PdfError::other(format!(
"PDF pubsec KARI: originator curve OID {curve_oid:?} \
does not match expected {want:?}"
)));
}
Ok(opk.public_key.clone())
}
KariCurve::X25519 => {
if opk.algorithm_oid != OID_X25519 {
return Err(PdfError::other(format!(
"PDF pubsec KARI: originator algorithm OID {:?} is not id-X25519",
opk.algorithm_oid
)));
}
Ok(opk.public_key.clone())
}
KariCurve::X448 => {
if opk.algorithm_oid != OID_X448 {
return Err(PdfError::other(format!(
"PDF pubsec KARI: originator algorithm OID {:?} is not id-X448",
opk.algorithm_oid
)));
}
Ok(opk.public_key.clone())
}
}
}
pub fn match_kari_slot<'a>(
kari: &'a KeyAgreeRecipientInfo,
issuer_der: &[u8],
serial: &[u8],
spki_pubkey_bits: Option<&[u8]>,
) -> Option<&'a RecipientEncryptedKey> {
use sha1::Digest;
let our_ski = spki_pubkey_bits.map(|b| sha1::Sha1::digest(b).to_vec());
for slot in &kari.recipient_encrypted_keys {
match &slot.rid {
KeyAgreeRecipientId::IssuerAndSerial(ias) => {
if ias.issuer_der == issuer_der && ias.serial == serial {
return Some(slot);
}
}
KeyAgreeRecipientId::RecipientKeyIdentifier { ski, .. } => {
if let Some(our) = our_ski.as_ref() {
if ski == our {
return Some(slot);
}
}
}
}
}
None
}
#[doc(hidden)]
pub fn wrap_cek_for_p256_recipient(
ephemeral_scalar: &[u8],
recipient_pub_sec1: &[u8],
ukm: Option<&[u8]>,
cek: &[u8],
wrap: WrapAlgorithm,
) -> Result<(Vec<u8>, Vec<u8>), PdfError> {
wrap_cek_for_recipient(
KariCurve::P256,
ephemeral_scalar,
recipient_pub_sec1,
ukm,
cek,
wrap,
)
}
pub fn wrap_cek_for_recipient(
curve: KariCurve,
ephemeral_scalar: &[u8],
recipient_pub_bytes: &[u8],
ukm: Option<&[u8]>,
cek: &[u8],
wrap: WrapAlgorithm,
) -> Result<(Vec<u8>, Vec<u8>), PdfError> {
wrap_cek_for_recipient_with_kdf(
curve,
curve.default_kdf(),
ephemeral_scalar,
recipient_pub_bytes,
ukm,
cek,
wrap,
)
}
#[allow(clippy::too_many_arguments)]
pub fn wrap_cek_for_recipient_with_kdf(
curve: KariCurve,
kdf: KariKdf,
ephemeral_scalar: &[u8],
recipient_pub_bytes: &[u8],
ukm: Option<&[u8]>,
cek: &[u8],
wrap: WrapAlgorithm,
) -> Result<(Vec<u8>, Vec<u8>), PdfError> {
if !kdf.is_valid_for(curve) {
return Err(PdfError::other(format!(
"PDF pubsec KARI build: KDF {kdf:?} is not a valid binding \
for curve {curve:?} (RFC 5753 §7.1.4 / RFC 8418 §2)"
)));
}
let (originator_pub, z) = match curve {
KariCurve::P256 => {
use p256::elliptic_curve::sec1::ToEncodedPoint;
use p256::{ecdh::diffie_hellman, PublicKey, SecretKey};
let secret = SecretKey::from_slice(ephemeral_scalar).map_err(|e| {
PdfError::other(format!("PDF pubsec KARI build: bad P-256 ephemeral: {e}"))
})?;
let originator_point = secret
.public_key()
.to_encoded_point(false)
.as_bytes()
.to_vec();
let recipient_public =
PublicKey::from_sec1_bytes(recipient_pub_bytes).map_err(|e| {
PdfError::other(format!(
"PDF pubsec KARI build: bad P-256 recipient SEC1 point: {e}"
))
})?;
let shared = diffie_hellman(secret.to_nonzero_scalar(), recipient_public.as_affine());
(originator_point, shared.raw_secret_bytes().to_vec())
}
KariCurve::P384 => {
use p384::elliptic_curve::sec1::ToEncodedPoint;
use p384::{ecdh::diffie_hellman, PublicKey, SecretKey};
let secret = SecretKey::from_slice(ephemeral_scalar).map_err(|e| {
PdfError::other(format!("PDF pubsec KARI build: bad P-384 ephemeral: {e}"))
})?;
let originator_point = secret
.public_key()
.to_encoded_point(false)
.as_bytes()
.to_vec();
let recipient_public =
PublicKey::from_sec1_bytes(recipient_pub_bytes).map_err(|e| {
PdfError::other(format!(
"PDF pubsec KARI build: bad P-384 recipient SEC1 point: {e}"
))
})?;
let shared = diffie_hellman(secret.to_nonzero_scalar(), recipient_public.as_affine());
(originator_point, shared.raw_secret_bytes().to_vec())
}
KariCurve::P521 => {
use p521::elliptic_curve::sec1::ToEncodedPoint;
use p521::{ecdh::diffie_hellman, PublicKey, SecretKey};
let secret = SecretKey::from_slice(ephemeral_scalar).map_err(|e| {
PdfError::other(format!("PDF pubsec KARI build: bad P-521 ephemeral: {e}"))
})?;
let originator_point = secret
.public_key()
.to_encoded_point(false)
.as_bytes()
.to_vec();
let recipient_public =
PublicKey::from_sec1_bytes(recipient_pub_bytes).map_err(|e| {
PdfError::other(format!(
"PDF pubsec KARI build: bad P-521 recipient SEC1 point: {e}"
))
})?;
let shared = diffie_hellman(secret.to_nonzero_scalar(), recipient_public.as_affine());
(originator_point, shared.raw_secret_bytes().to_vec())
}
KariCurve::X25519 => {
use x25519_dalek::{PublicKey, StaticSecret};
if ephemeral_scalar.len() != 32 {
return Err(PdfError::other(format!(
"PDF pubsec KARI build: X25519 ephemeral must be 32 bytes (got {})",
ephemeral_scalar.len()
)));
}
if recipient_pub_bytes.len() != 32 {
return Err(PdfError::other(format!(
"PDF pubsec KARI build: X25519 recipient point must be 32 bytes (got {})",
recipient_pub_bytes.len()
)));
}
let mut s_arr = [0u8; 32];
s_arr.copy_from_slice(ephemeral_scalar);
let secret = StaticSecret::from(s_arr);
let originator_pub = PublicKey::from(&secret).as_bytes().to_vec();
let mut p_arr = [0u8; 32];
p_arr.copy_from_slice(recipient_pub_bytes);
let recipient_public = PublicKey::from(p_arr);
let shared = secret.diffie_hellman(&recipient_public);
if shared.as_bytes().iter().all(|b| *b == 0) {
return Err(PdfError::other(
"PDF pubsec KARI build: X25519 shared secret is all-zero \
(RFC 8418 §3 reject — bad recipient public key)",
));
}
(originator_pub, shared.as_bytes().to_vec())
}
KariCurve::X448 => {
use x448::{PublicKey, StaticSecret};
if ephemeral_scalar.len() != 56 {
return Err(PdfError::other(format!(
"PDF pubsec KARI build: X448 ephemeral must be 56 bytes (got {})",
ephemeral_scalar.len()
)));
}
if recipient_pub_bytes.len() != 56 {
return Err(PdfError::other(format!(
"PDF pubsec KARI build: X448 recipient point must be 56 bytes (got {})",
recipient_pub_bytes.len()
)));
}
let mut s_arr = [0u8; 56];
s_arr.copy_from_slice(ephemeral_scalar);
let secret = StaticSecret::from(s_arr);
let originator_pub = PublicKey::from(&secret).as_bytes().to_vec();
let recipient_public = PublicKey::from_bytes(recipient_pub_bytes).ok_or_else(|| {
PdfError::other(
"PDF pubsec KARI build: X448 recipient point is low-order \
(RFC 8418 §3 reject — bad recipient public key)",
)
})?;
let shared = secret.diffie_hellman(&recipient_public);
if shared.as_bytes().iter().all(|b| *b == 0) {
return Err(PdfError::other(
"PDF pubsec KARI build: X448 shared secret is all-zero \
(RFC 8418 §3 reject — bad recipient public key)",
));
}
(originator_pub, shared.as_bytes().to_vec())
}
};
let shared_info = build_ecc_cms_shared_info(wrap.oid(), ukm, (wrap.kek_len() * 8) as u32);
let kek = derive_kek(kdf, &z, ukm, &shared_info, wrap.kek_len());
let wrapped = aes_kw_wrap(wrap, &kek, cek)?;
Ok((originator_pub, wrapped))
}
#[cfg(test)]
mod tests {
use super::super::der::read_octet_string;
use super::*;
#[test]
fn x963_kdf_sha256_one_block() {
let z = [0x42u8; 32];
let shared_info = [0x99u8; 8];
let out = x963_kdf_sha256(&z, &shared_info, 32);
use sha2::Digest;
let mut h = sha2::Sha256::new();
h.update(z);
h.update(1u32.to_be_bytes());
h.update(shared_info);
let want = h.finalize().to_vec();
assert_eq!(out, want);
}
#[test]
fn x963_kdf_sha256_two_blocks_truncated() {
let z = [0x33u8; 32];
let shared_info = [0x77u8; 4];
let out = x963_kdf_sha256(&z, &shared_info, 40);
use sha2::Digest;
let mut h1 = sha2::Sha256::new();
h1.update(z);
h1.update(1u32.to_be_bytes());
h1.update(shared_info);
let k1 = h1.finalize().to_vec();
let mut h2 = sha2::Sha256::new();
h2.update(z);
h2.update(2u32.to_be_bytes());
h2.update(shared_info);
let k2 = h2.finalize().to_vec();
let mut want = k1;
want.extend_from_slice(&k2[..8]);
assert_eq!(out, want);
assert_eq!(out.len(), 40);
}
#[test]
fn shared_info_round_trip_minimal() {
let bytes = build_ecc_cms_shared_info(&OID_AES256_WRAP, None, 256);
let (body, rest) = read_sequence(&bytes).unwrap();
assert!(rest.is_empty());
let (key_info_body, after_ki) = read_sequence(body).unwrap();
let (oid, after_oid) = read_oid(key_info_body).unwrap();
assert_eq!(oid, OID_AES256_WRAP);
assert!(after_oid.is_empty());
let (tlv, _tail) = super::super::der::read_tlv(after_ki).unwrap();
assert_eq!(tlv.tag_number, 2);
let (k, _) = read_octet_string(tlv.body).unwrap();
assert_eq!(k, &[0x00, 0x00, 0x01, 0x00]); }
#[test]
fn shared_info_with_ukm() {
let bytes = build_ecc_cms_shared_info(&OID_AES128_WRAP, Some(b"UKM-bytes"), 128);
let (body, _) = read_sequence(&bytes).unwrap();
let (_ki_body, after_ki) = read_sequence(body).unwrap();
let (tlv0, after_tlv0) = super::super::der::read_tlv(after_ki).unwrap();
assert_eq!(tlv0.tag_number, 0);
let (ukm_bytes, _) = read_octet_string(tlv0.body).unwrap();
assert_eq!(ukm_bytes, b"UKM-bytes");
let (tlv2, _) = super::super::der::read_tlv(after_tlv0).unwrap();
assert_eq!(tlv2.tag_number, 2);
}
#[test]
fn wrap_oid_round_trip() {
for w in [
WrapAlgorithm::Aes128,
WrapAlgorithm::Aes192,
WrapAlgorithm::Aes256,
] {
assert_eq!(WrapAlgorithm::from_oid(w.oid()), Some(w));
}
assert_eq!(WrapAlgorithm::from_oid(&[1, 2, 3]), None);
}
#[test]
fn p256_aes256_wrap_unwrap_round_trip() {
use p256::elliptic_curve::sec1::ToEncodedPoint;
use p256::SecretKey;
let ephemeral_scalar = [
0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E,
0x0F, 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0x1A, 0x1B, 0x1C,
0x1D, 0x1E, 0x1F, 0x20,
];
let recipient_scalar = [
0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, 0x28, 0x29, 0x2A, 0x2B, 0x2C, 0x2D, 0x2E,
0x2F, 0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, 0x38, 0x39, 0x3A, 0x3B, 0x3C,
0x3D, 0x3E, 0x3F, 0x40,
];
let recipient_secret = SecretKey::from_slice(&recipient_scalar).unwrap();
let recipient_pub_sec1 = recipient_secret
.public_key()
.to_encoded_point(false)
.as_bytes()
.to_vec();
let cek = vec![0xAAu8; 32];
let ukm = b"OXIDEAV-UKM-RT-1";
let (originator_point, wrapped) = wrap_cek_for_p256_recipient(
&ephemeral_scalar,
&recipient_pub_sec1,
Some(ukm),
&cek,
WrapAlgorithm::Aes256,
)
.expect("wrap");
let kari = KeyAgreeRecipientInfo {
originator: OriginatorId::OriginatorKey(OriginatorPublicKey {
algorithm_oid: OID_EC_PUBLIC_KEY.to_vec(),
algorithm_params: write_oid(&OID_SECP256R1),
public_key: originator_point,
}),
ukm: ukm.to_vec(),
key_encryption_oid: OID_DH_SINGLE_PASS_STDDH_SHA256_KDF.to_vec(),
key_encryption_params: write_sequence(&write_oid(&OID_AES256_WRAP)),
recipient_encrypted_keys: vec![RecipientEncryptedKey {
rid: KeyAgreeRecipientId::RecipientKeyIdentifier {
ski: vec![0xCDu8; 20],
date: None,
other: None,
},
encrypted_key: wrapped,
}],
};
let recipient = EcRecipient::p256(recipient_scalar.to_vec(), recipient_pub_sec1);
let unwrapped =
unwrap_kari_p256(&kari, &kari.recipient_encrypted_keys[0], &recipient).expect("unwrap");
assert_eq!(unwrapped, cek);
}
#[test]
fn p256_aes128_wrap_unwrap_round_trip() {
use p256::elliptic_curve::sec1::ToEncodedPoint;
use p256::SecretKey;
let ephemeral_scalar = [0x77u8; 32];
let recipient_scalar = [0x55u8; 32];
let recipient_secret = SecretKey::from_slice(&recipient_scalar).unwrap();
let recipient_pub_sec1 = recipient_secret
.public_key()
.to_encoded_point(false)
.as_bytes()
.to_vec();
let cek = vec![0xBBu8; 16];
let (originator_point, wrapped) = wrap_cek_for_p256_recipient(
&ephemeral_scalar,
&recipient_pub_sec1,
None,
&cek,
WrapAlgorithm::Aes128,
)
.expect("wrap");
let kari = KeyAgreeRecipientInfo {
originator: OriginatorId::OriginatorKey(OriginatorPublicKey {
algorithm_oid: OID_EC_PUBLIC_KEY.to_vec(),
algorithm_params: write_oid(&OID_SECP256R1),
public_key: originator_point,
}),
ukm: Vec::new(),
key_encryption_oid: OID_DH_SINGLE_PASS_STDDH_SHA256_KDF.to_vec(),
key_encryption_params: write_sequence(&write_oid(&OID_AES128_WRAP)),
recipient_encrypted_keys: vec![RecipientEncryptedKey {
rid: KeyAgreeRecipientId::RecipientKeyIdentifier {
ski: vec![0xEEu8; 20],
date: None,
other: None,
},
encrypted_key: wrapped,
}],
};
let recipient = EcRecipient::p256(recipient_scalar.to_vec(), recipient_pub_sec1);
let unwrapped =
unwrap_kari_p256(&kari, &kari.recipient_encrypted_keys[0], &recipient).expect("unwrap");
assert_eq!(unwrapped, cek);
}
#[test]
fn unsupported_kea_oid_errors() {
let kari = KeyAgreeRecipientInfo {
originator: OriginatorId::OriginatorKey(OriginatorPublicKey {
algorithm_oid: OID_EC_PUBLIC_KEY.to_vec(),
algorithm_params: write_oid(&OID_SECP256R1),
public_key: vec![0x04; 65],
}),
ukm: Vec::new(),
key_encryption_oid: OID_DH_SINGLE_PASS_STDDH_SHA384_KDF.to_vec(),
key_encryption_params: write_sequence(&write_oid(&OID_AES256_WRAP)),
recipient_encrypted_keys: vec![RecipientEncryptedKey {
rid: KeyAgreeRecipientId::RecipientKeyIdentifier {
ski: vec![0xCD; 20],
date: None,
other: None,
},
encrypted_key: vec![0; 40],
}],
};
let recipient = EcRecipient::p256(vec![0x55; 32], vec![0x04; 65]);
let err =
unwrap_kari_p256(&kari, &kari.recipient_encrypted_keys[0], &recipient).unwrap_err();
let msg = format!("{err}");
assert!(
msg.contains("does not match curve")
|| msg.contains("KEA OID")
|| msg.contains("not a valid binding"),
"unexpected error: {msg}"
);
}
#[test]
fn p384_aes256_wrap_unwrap_round_trip() {
use p384::elliptic_curve::sec1::ToEncodedPoint;
use p384::SecretKey;
let ephemeral_scalar = [0x42u8; 48];
let recipient_scalar = [0x77u8; 48];
let recipient_secret = SecretKey::from_slice(&recipient_scalar).unwrap();
let recipient_pub = recipient_secret
.public_key()
.to_encoded_point(false)
.as_bytes()
.to_vec();
let cek = vec![0x9Au8; 32];
let ukm = b"OXIDEAV-UKM-P384";
let (originator_pub, wrapped) = wrap_cek_for_recipient(
KariCurve::P384,
&ephemeral_scalar,
&recipient_pub,
Some(ukm),
&cek,
WrapAlgorithm::Aes256,
)
.expect("wrap P-384");
assert_eq!(originator_pub.len(), 97);
let kari = KeyAgreeRecipientInfo {
originator: OriginatorId::OriginatorKey(OriginatorPublicKey {
algorithm_oid: OID_EC_PUBLIC_KEY.to_vec(),
algorithm_params: write_oid(&OID_SECP384R1),
public_key: originator_pub,
}),
ukm: ukm.to_vec(),
key_encryption_oid: OID_DH_SINGLE_PASS_STDDH_SHA384_KDF.to_vec(),
key_encryption_params: write_sequence(&write_oid(&OID_AES256_WRAP)),
recipient_encrypted_keys: vec![RecipientEncryptedKey {
rid: KeyAgreeRecipientId::RecipientKeyIdentifier {
ski: vec![0x33u8; 20],
date: None,
other: None,
},
encrypted_key: wrapped,
}],
};
let recipient = EcRecipient::p384(recipient_scalar.to_vec(), recipient_pub);
let unwrapped =
unwrap_kari(&kari, &kari.recipient_encrypted_keys[0], &recipient).expect("unwrap");
assert_eq!(unwrapped, cek);
}
#[test]
fn x25519_aes128_wrap_unwrap_round_trip() {
use x25519_dalek::{PublicKey, StaticSecret};
let recipient_scalar_arr = [0x44u8; 32];
let secret = StaticSecret::from(recipient_scalar_arr);
let recipient_pub = PublicKey::from(&secret).as_bytes().to_vec();
let ephemeral_scalar = [0x66u8; 32];
let cek = vec![0xC1u8; 16];
let (originator_pub, wrapped) = wrap_cek_for_recipient(
KariCurve::X25519,
&ephemeral_scalar,
&recipient_pub,
None,
&cek,
WrapAlgorithm::Aes128,
)
.expect("wrap X25519");
assert_eq!(originator_pub.len(), 32);
let kari = KeyAgreeRecipientInfo {
originator: OriginatorId::OriginatorKey(OriginatorPublicKey {
algorithm_oid: OID_X25519.to_vec(),
algorithm_params: Vec::new(),
public_key: originator_pub,
}),
ukm: Vec::new(),
key_encryption_oid: OID_DH_SINGLE_PASS_STDDH_SHA256_KDF.to_vec(),
key_encryption_params: write_sequence(&write_oid(&OID_AES128_WRAP)),
recipient_encrypted_keys: vec![RecipientEncryptedKey {
rid: KeyAgreeRecipientId::RecipientKeyIdentifier {
ski: vec![0xABu8; 20],
date: None,
other: None,
},
encrypted_key: wrapped,
}],
};
let recipient = EcRecipient::x25519(recipient_scalar_arr.to_vec(), recipient_pub);
let unwrapped =
unwrap_kari(&kari, &kari.recipient_encrypted_keys[0], &recipient).expect("unwrap");
assert_eq!(unwrapped, cek);
}
#[test]
fn x963_kdf_sha384_one_block() {
let z = [0x42u8; 48];
let shared_info = [0x99u8; 8];
let out = x963_kdf::<sha2::Sha384>(&z, &shared_info, 48);
use sha2::Digest;
let mut h = sha2::Sha384::new();
h.update(z);
h.update(1u32.to_be_bytes());
h.update(shared_info);
let want = h.finalize().to_vec();
assert_eq!(out, want);
}
#[test]
fn curve_oid_dispatch_consistency() {
assert_eq!(
KariCurve::P256.kea_oid(),
&OID_DH_SINGLE_PASS_STDDH_SHA256_KDF
);
assert_eq!(
KariCurve::P384.kea_oid(),
&OID_DH_SINGLE_PASS_STDDH_SHA384_KDF
);
assert_eq!(
KariCurve::P521.kea_oid(),
&OID_DH_SINGLE_PASS_STDDH_SHA512_KDF
);
assert_eq!(
KariCurve::X25519.kea_oid(),
&OID_DH_SINGLE_PASS_STDDH_SHA256_KDF
);
assert_eq!(KariCurve::P256.algorithm_oid(), &OID_EC_PUBLIC_KEY);
assert_eq!(KariCurve::P384.algorithm_oid(), &OID_EC_PUBLIC_KEY);
assert_eq!(KariCurve::P521.algorithm_oid(), &OID_EC_PUBLIC_KEY);
assert_eq!(KariCurve::X25519.algorithm_oid(), &OID_X25519);
assert_eq!(KariCurve::P256.default_kdf(), KariKdf::X963Sha256);
assert_eq!(KariCurve::P384.default_kdf(), KariKdf::X963Sha384);
assert_eq!(KariCurve::P521.default_kdf(), KariKdf::X963Sha512);
assert_eq!(KariCurve::X25519.default_kdf(), KariKdf::X963Sha256);
}
#[test]
fn x963_kdf_sha512_one_block() {
let z = [0x42u8; 66];
let shared_info = [0x99u8; 8];
let out = x963_kdf::<sha2::Sha512>(&z, &shared_info, 64);
use sha2::Digest;
let mut h = sha2::Sha512::new();
h.update(z);
h.update(1u32.to_be_bytes());
h.update(shared_info);
let want = h.finalize().to_vec();
assert_eq!(out, want);
}
#[test]
fn hkdf_kek_matches_rfc5869() {
let z = [0x10u8; 32];
let ukm = b"OXIDEAV-UKM-RFC8418";
let info = build_ecc_cms_shared_info(&OID_AES256_WRAP, Some(ukm), 256);
let got = derive_kek(KariKdf::HkdfSha256, &z, Some(ukm), &info, 32);
let want = {
let hk = hkdf::Hkdf::<sha2::Sha256>::new(Some(ukm), &z);
let mut okm = [0u8; 32];
hk.expand(&info, &mut okm).unwrap();
okm.to_vec()
};
assert_eq!(got, want);
let got_no_salt = derive_kek(KariKdf::HkdfSha256, &z, None, &info, 32);
let want_no_salt = {
let hk = hkdf::Hkdf::<sha2::Sha256>::new(None, &z);
let mut okm = [0u8; 32];
hk.expand(&info, &mut okm).unwrap();
okm.to_vec()
};
assert_eq!(got_no_salt, want_no_salt);
}
#[test]
fn kdf_curve_pairing_matrix() {
assert!(KariKdf::X963Sha256.is_valid_for(KariCurve::P256));
assert!(!KariKdf::X963Sha384.is_valid_for(KariCurve::P256));
assert!(!KariKdf::X963Sha512.is_valid_for(KariCurve::P256));
assert!(!KariKdf::HkdfSha256.is_valid_for(KariCurve::P256));
assert!(KariKdf::X963Sha384.is_valid_for(KariCurve::P384));
assert!(!KariKdf::X963Sha256.is_valid_for(KariCurve::P384));
assert!(!KariKdf::HkdfSha384.is_valid_for(KariCurve::P384));
assert!(KariKdf::X963Sha512.is_valid_for(KariCurve::P521));
assert!(!KariKdf::X963Sha384.is_valid_for(KariCurve::P521));
assert!(!KariKdf::HkdfSha512.is_valid_for(KariCurve::P521));
assert!(KariKdf::X963Sha256.is_valid_for(KariCurve::X25519));
assert!(KariKdf::HkdfSha256.is_valid_for(KariCurve::X25519));
assert!(KariKdf::HkdfSha384.is_valid_for(KariCurve::X25519));
assert!(KariKdf::HkdfSha512.is_valid_for(KariCurve::X25519));
assert!(!KariKdf::X963Sha384.is_valid_for(KariCurve::X25519));
assert!(!KariKdf::X963Sha512.is_valid_for(KariCurve::X25519));
}
#[test]
fn p521_aes256_wrap_unwrap_round_trip() {
use p521::elliptic_curve::sec1::ToEncodedPoint;
use p521::SecretKey;
let mut ephemeral_scalar = [0x42u8; 66];
ephemeral_scalar[0] = 0x00;
let mut recipient_scalar = [0x77u8; 66];
recipient_scalar[0] = 0x00;
let recipient_secret = SecretKey::from_slice(&recipient_scalar).unwrap();
let recipient_pub = recipient_secret
.public_key()
.to_encoded_point(false)
.as_bytes()
.to_vec();
let cek = vec![0x9Au8; 32];
let ukm = b"OXIDEAV-UKM-P521";
let (originator_pub, wrapped) = wrap_cek_for_recipient(
KariCurve::P521,
&ephemeral_scalar,
&recipient_pub,
Some(ukm),
&cek,
WrapAlgorithm::Aes256,
)
.expect("wrap P-521");
assert_eq!(originator_pub.len(), 133);
let kari = KeyAgreeRecipientInfo {
originator: OriginatorId::OriginatorKey(OriginatorPublicKey {
algorithm_oid: OID_EC_PUBLIC_KEY.to_vec(),
algorithm_params: write_oid(&OID_SECP521R1),
public_key: originator_pub,
}),
ukm: ukm.to_vec(),
key_encryption_oid: OID_DH_SINGLE_PASS_STDDH_SHA512_KDF.to_vec(),
key_encryption_params: write_sequence(&write_oid(&OID_AES256_WRAP)),
recipient_encrypted_keys: vec![RecipientEncryptedKey {
rid: KeyAgreeRecipientId::RecipientKeyIdentifier {
ski: vec![0x33u8; 20],
date: None,
other: None,
},
encrypted_key: wrapped,
}],
};
let recipient = EcRecipient::p521(recipient_scalar.to_vec(), recipient_pub);
let unwrapped =
unwrap_kari(&kari, &kari.recipient_encrypted_keys[0], &recipient).expect("unwrap");
assert_eq!(unwrapped, cek);
}
#[test]
fn x25519_hkdf_sha256_aes128_wrap_unwrap_round_trip() {
use x25519_dalek::{PublicKey, StaticSecret};
let recipient_scalar_arr = [0x44u8; 32];
let secret = StaticSecret::from(recipient_scalar_arr);
let recipient_pub = PublicKey::from(&secret).as_bytes().to_vec();
let ephemeral_scalar = [0x66u8; 32];
let cek = vec![0xC1u8; 16];
let ukm = b"OXIDEAV-UKM-RFC8418-22";
let (originator_pub, wrapped) = wrap_cek_for_recipient_with_kdf(
KariCurve::X25519,
KariKdf::HkdfSha256,
&ephemeral_scalar,
&recipient_pub,
Some(ukm),
&cek,
WrapAlgorithm::Aes128,
)
.expect("wrap X25519 HKDF");
let kari = KeyAgreeRecipientInfo {
originator: OriginatorId::OriginatorKey(OriginatorPublicKey {
algorithm_oid: OID_X25519.to_vec(),
algorithm_params: Vec::new(),
public_key: originator_pub,
}),
ukm: ukm.to_vec(),
key_encryption_oid: OID_DH_SINGLE_PASS_STDDH_HKDF_SHA256_SCHEME.to_vec(),
key_encryption_params: write_sequence(&write_oid(&OID_AES128_WRAP)),
recipient_encrypted_keys: vec![RecipientEncryptedKey {
rid: KeyAgreeRecipientId::RecipientKeyIdentifier {
ski: vec![0xABu8; 20],
date: None,
other: None,
},
encrypted_key: wrapped,
}],
};
let recipient = EcRecipient::x25519(recipient_scalar_arr.to_vec(), recipient_pub);
let unwrapped =
unwrap_kari(&kari, &kari.recipient_encrypted_keys[0], &recipient).expect("unwrap");
assert_eq!(unwrapped, cek);
}
#[test]
fn x25519_hkdf_sha384_aes256_no_ukm_round_trip() {
use x25519_dalek::{PublicKey, StaticSecret};
let recipient_scalar_arr = [0x55u8; 32];
let secret = StaticSecret::from(recipient_scalar_arr);
let recipient_pub = PublicKey::from(&secret).as_bytes().to_vec();
let ephemeral_scalar = [0xA6u8; 32];
let cek = vec![0xD2u8; 32];
let (originator_pub, wrapped) = wrap_cek_for_recipient_with_kdf(
KariCurve::X25519,
KariKdf::HkdfSha384,
&ephemeral_scalar,
&recipient_pub,
None,
&cek,
WrapAlgorithm::Aes256,
)
.expect("wrap X25519 HKDF-384");
let kari = KeyAgreeRecipientInfo {
originator: OriginatorId::OriginatorKey(OriginatorPublicKey {
algorithm_oid: OID_X25519.to_vec(),
algorithm_params: Vec::new(),
public_key: originator_pub,
}),
ukm: Vec::new(),
key_encryption_oid: OID_DH_SINGLE_PASS_STDDH_HKDF_SHA384_SCHEME.to_vec(),
key_encryption_params: write_sequence(&write_oid(&OID_AES256_WRAP)),
recipient_encrypted_keys: vec![RecipientEncryptedKey {
rid: KeyAgreeRecipientId::RecipientKeyIdentifier {
ski: vec![0xCDu8; 20],
date: None,
other: None,
},
encrypted_key: wrapped,
}],
};
let recipient = EcRecipient::x25519(recipient_scalar_arr.to_vec(), recipient_pub);
let unwrapped =
unwrap_kari(&kari, &kari.recipient_encrypted_keys[0], &recipient).expect("unwrap");
assert_eq!(unwrapped, cek);
}
#[test]
fn x25519_hkdf_sha512_aes256_wrap_unwrap_round_trip() {
use x25519_dalek::{PublicKey, StaticSecret};
let recipient_scalar_arr = [0x77u8; 32];
let secret = StaticSecret::from(recipient_scalar_arr);
let recipient_pub = PublicKey::from(&secret).as_bytes().to_vec();
let ephemeral_scalar = [0xE3u8; 32];
let cek = vec![0xF1u8; 32];
let ukm = b"UKM-512";
let (originator_pub, wrapped) = wrap_cek_for_recipient_with_kdf(
KariCurve::X25519,
KariKdf::HkdfSha512,
&ephemeral_scalar,
&recipient_pub,
Some(ukm),
&cek,
WrapAlgorithm::Aes256,
)
.expect("wrap X25519 HKDF-512");
let kari = KeyAgreeRecipientInfo {
originator: OriginatorId::OriginatorKey(OriginatorPublicKey {
algorithm_oid: OID_X25519.to_vec(),
algorithm_params: Vec::new(),
public_key: originator_pub,
}),
ukm: ukm.to_vec(),
key_encryption_oid: OID_DH_SINGLE_PASS_STDDH_HKDF_SHA512_SCHEME.to_vec(),
key_encryption_params: write_sequence(&write_oid(&OID_AES256_WRAP)),
recipient_encrypted_keys: vec![RecipientEncryptedKey {
rid: KeyAgreeRecipientId::RecipientKeyIdentifier {
ski: vec![0xEFu8; 20],
date: None,
other: None,
},
encrypted_key: wrapped,
}],
};
let recipient = EcRecipient::x25519(recipient_scalar_arr.to_vec(), recipient_pub);
let unwrapped =
unwrap_kari(&kari, &kari.recipient_encrypted_keys[0], &recipient).expect("unwrap");
assert_eq!(unwrapped, cek);
}
#[test]
fn invalid_kdf_curve_pairing_rejected_at_build() {
let err = wrap_cek_for_recipient_with_kdf(
KariCurve::X25519,
KariKdf::X963Sha512,
&[0u8; 32],
&[0u8; 32],
None,
&[0u8; 32],
WrapAlgorithm::Aes256,
)
.unwrap_err();
let msg = format!("{err}");
assert!(
msg.contains("not a valid binding"),
"unexpected error: {msg}"
);
}
#[test]
fn x448_rfc7748_alice_bob_shared_secret() {
let alice_priv: [u8; 56] = [
0x9a, 0x8f, 0x49, 0x25, 0xd1, 0x51, 0x9f, 0x57, 0x75, 0xcf, 0x46, 0xb0, 0x4b, 0x58,
0x00, 0xd4, 0xee, 0x9e, 0xe8, 0xba, 0xe8, 0xbc, 0x55, 0x65, 0xd4, 0x98, 0xc2, 0x8d,
0xd9, 0xc9, 0xba, 0xf5, 0x74, 0xa9, 0x41, 0x97, 0x44, 0x89, 0x73, 0x91, 0x00, 0x63,
0x82, 0xa6, 0xf1, 0x27, 0xab, 0x1d, 0x9a, 0xc2, 0xd8, 0xc0, 0xa5, 0x98, 0x72, 0x6b,
];
let bob_pub: [u8; 56] = [
0x3e, 0xb7, 0xa8, 0x29, 0xb0, 0xcd, 0x20, 0xf5, 0xbc, 0xfc, 0x0b, 0x59, 0x9b, 0x6f,
0xec, 0xcf, 0x6d, 0xa4, 0x62, 0x71, 0x07, 0xbd, 0xb0, 0xd4, 0xf3, 0x45, 0xb4, 0x30,
0x27, 0xd8, 0xb9, 0x72, 0xfc, 0x3e, 0x34, 0xfb, 0x42, 0x32, 0xa1, 0x3c, 0xa7, 0x06,
0xdc, 0xb5, 0x7a, 0xec, 0x3d, 0xae, 0x07, 0xbd, 0xc1, 0xc6, 0x7b, 0xf3, 0x36, 0x09,
];
let want_shared: [u8; 56] = [
0x07, 0xff, 0xf4, 0x18, 0x1a, 0xc6, 0xcc, 0x95, 0xec, 0x1c, 0x16, 0xa9, 0x4a, 0x0f,
0x74, 0xd1, 0x2d, 0xa2, 0x32, 0xce, 0x40, 0xa7, 0x75, 0x52, 0x28, 0x1d, 0x28, 0x2b,
0xb6, 0x0c, 0x0b, 0x56, 0xfd, 0x24, 0x64, 0xc3, 0x35, 0x54, 0x39, 0x36, 0x52, 0x1c,
0x24, 0x40, 0x30, 0x85, 0xd5, 0x9a, 0x44, 0x9a, 0x50, 0x37, 0x51, 0x4a, 0x87, 0x9d,
];
let got = ecdh_z(KariCurve::X448, &alice_priv, &bob_pub).expect("ECDH");
assert_eq!(got.len(), 56);
assert_eq!(got, want_shared.to_vec(), "RFC 7748 §6.2 vector mismatch");
}
#[test]
fn x448_aes256_wrap_unwrap_round_trip() {
use x448::{PublicKey, StaticSecret};
let recipient_scalar_arr = [0x44u8; 56];
let secret = StaticSecret::from(recipient_scalar_arr);
let recipient_pub = PublicKey::from(&secret).as_bytes().to_vec();
let ephemeral_scalar = [0x66u8; 56];
let cek = vec![0xC1u8; 32];
let ukm = b"OXIDEAV-UKM-X448-963-512";
let (originator_pub, wrapped) = wrap_cek_for_recipient(
KariCurve::X448,
&ephemeral_scalar,
&recipient_pub,
Some(ukm),
&cek,
WrapAlgorithm::Aes256,
)
.expect("wrap X448");
assert_eq!(originator_pub.len(), 56);
let kari = KeyAgreeRecipientInfo {
originator: OriginatorId::OriginatorKey(OriginatorPublicKey {
algorithm_oid: OID_X448.to_vec(),
algorithm_params: Vec::new(),
public_key: originator_pub,
}),
ukm: ukm.to_vec(),
key_encryption_oid: OID_DH_SINGLE_PASS_STDDH_SHA512_KDF.to_vec(),
key_encryption_params: write_sequence(&write_oid(&OID_AES256_WRAP)),
recipient_encrypted_keys: vec![RecipientEncryptedKey {
rid: KeyAgreeRecipientId::RecipientKeyIdentifier {
ski: vec![0xABu8; 20],
date: None,
other: None,
},
encrypted_key: wrapped,
}],
};
let recipient = EcRecipient::x448(recipient_scalar_arr.to_vec(), recipient_pub);
let unwrapped =
unwrap_kari(&kari, &kari.recipient_encrypted_keys[0], &recipient).expect("unwrap");
assert_eq!(unwrapped, cek);
}
#[test]
fn x448_hkdf_sha512_aes256_wrap_unwrap_round_trip() {
use x448::{PublicKey, StaticSecret};
let recipient_scalar_arr = [0x77u8; 56];
let secret = StaticSecret::from(recipient_scalar_arr);
let recipient_pub = PublicKey::from(&secret).as_bytes().to_vec();
let ephemeral_scalar = [0xE3u8; 56];
let cek = vec![0xF1u8; 32];
let ukm = b"X448-HKDF-512-UKM";
let (originator_pub, wrapped) = wrap_cek_for_recipient_with_kdf(
KariCurve::X448,
KariKdf::HkdfSha512,
&ephemeral_scalar,
&recipient_pub,
Some(ukm),
&cek,
WrapAlgorithm::Aes256,
)
.expect("wrap X448 HKDF-512");
let kari = KeyAgreeRecipientInfo {
originator: OriginatorId::OriginatorKey(OriginatorPublicKey {
algorithm_oid: OID_X448.to_vec(),
algorithm_params: Vec::new(),
public_key: originator_pub,
}),
ukm: ukm.to_vec(),
key_encryption_oid: OID_DH_SINGLE_PASS_STDDH_HKDF_SHA512_SCHEME.to_vec(),
key_encryption_params: write_sequence(&write_oid(&OID_AES256_WRAP)),
recipient_encrypted_keys: vec![RecipientEncryptedKey {
rid: KeyAgreeRecipientId::RecipientKeyIdentifier {
ski: vec![0xEFu8; 20],
date: None,
other: None,
},
encrypted_key: wrapped,
}],
};
let recipient = EcRecipient::x448(recipient_scalar_arr.to_vec(), recipient_pub);
let unwrapped =
unwrap_kari(&kari, &kari.recipient_encrypted_keys[0], &recipient).expect("unwrap");
assert_eq!(unwrapped, cek);
}
#[test]
fn x448_curve_dispatch_consistency() {
assert_eq!(KariCurve::X448.algorithm_oid(), &OID_X448);
assert!(KariCurve::X448.algorithm_params().is_empty()); assert_eq!(
KariCurve::X448.kea_oid(),
&OID_DH_SINGLE_PASS_STDDH_SHA512_KDF
);
assert_eq!(KariCurve::X448.default_kdf(), KariKdf::X963Sha512);
assert_eq!(KariCurve::X448.pub_point_len(), 56);
}
#[test]
fn x448_kdf_curve_pairing_matrix() {
assert!(KariKdf::X963Sha512.is_valid_for(KariCurve::X448));
assert!(KariKdf::HkdfSha256.is_valid_for(KariCurve::X448));
assert!(KariKdf::HkdfSha384.is_valid_for(KariCurve::X448));
assert!(KariKdf::HkdfSha512.is_valid_for(KariCurve::X448));
assert!(!KariKdf::X963Sha256.is_valid_for(KariCurve::X448));
assert!(!KariKdf::X963Sha384.is_valid_for(KariCurve::X448));
}
#[test]
fn x448_invalid_kdf_pairing_rejected_at_build() {
let err = wrap_cek_for_recipient_with_kdf(
KariCurve::X448,
KariKdf::X963Sha256,
&[0u8; 56],
&[0u8; 56],
None,
&[0u8; 32],
WrapAlgorithm::Aes256,
)
.unwrap_err();
let msg = format!("{err}");
assert!(
msg.contains("not a valid binding"),
"unexpected error: {msg}"
);
}
#[test]
fn x448_low_order_originator_rejected() {
let recipient_scalar = [0x55u8; 56];
let zero_originator = [0u8; 56];
let err = ecdh_z(KariCurve::X448, &recipient_scalar, &zero_originator).unwrap_err();
let msg = format!("{err}");
assert!(
msg.contains("low-order") || msg.contains("all-zero"),
"unexpected error: {msg}"
);
}
}