#[cfg(not(feature = "std"))]
use alloc::vec::Vec;
use crate::crypto::kdf::KdfFunction;
use crate::crypto::profiles::{CryptoProvider, SecurityProfile};
use crate::crypto::secret::SecretSlice;
use crate::crypto::sign::elliptic_curve::sec1::{FromEncodedPoint, ModulusSize, ToEncodedPoint};
use crate::crypto::sign::elliptic_curve::subtle::ConstantTimeEq;
use crate::crypto::sign::elliptic_curve::{AffinePoint, Curve, CurveArithmetic, PublicKey, SecretKey};
use crate::der::asn1::ObjectIdentifier;
use crate::oids::{AES_128_WRAP, AES_192_WRAP, AES_256_WRAP};
use crate::transport::handshake::error::HandshakeError;
use crate::zeroize::Zeroizing;
#[cfg(feature = "ecdh")]
use crate::crypto::sign::elliptic_curve::ecdh::diffie_hellman;
fn derive_shared_secret<C>(priv_key: &SecretKey<C>, peer_pub: &PublicKey<C>) -> Result<SecretSlice<u8>, HandshakeError>
where
C: Curve + CurveArithmetic,
<C as Curve>::FieldBytesSize: ModulusSize,
AffinePoint<C>: FromEncodedPoint<C> + ToEncodedPoint<C>,
{
let shared = diffie_hellman(priv_key.to_nonzero_scalar(), peer_pub.as_affine());
Ok(SecretSlice::from(shared.raw_secret_bytes().as_ref().to_vec()))
}
fn key_wrap_key_size_from_oid(oid: ObjectIdentifier) -> Result<usize, HandshakeError> {
if oid == AES_128_WRAP {
Ok(16)
} else if oid == AES_192_WRAP {
Ok(24)
} else if oid == AES_256_WRAP {
Ok(32)
} else {
Err(HandshakeError::UnsupportedKeyWrapAlgorithm)
}
}
pub(crate) fn key_wrap_key_size<P: CryptoProvider>() -> Result<usize, HandshakeError> {
let oid = <P::Profile as SecurityProfile>::KEY_WRAP_OID.ok_or(HandshakeError::MissingKeyWrapAlgorithm)?;
key_wrap_key_size_from_oid(oid)
}
pub(crate) fn derive_kek<P>(
shared_secret: &SecretSlice<u8>,
ukm: &[u8],
kdf_info: &[u8],
key_size: usize,
) -> Result<Zeroizing<Vec<u8>>, HandshakeError>
where
P: CryptoProvider,
{
if ukm.is_empty() {
return Err(HandshakeError::MissingUkm);
}
let kek =
shared_secret.with(|ss| <P::Kdf as KdfFunction>::derive_dynamic_key(ss, kdf_info, Some(ukm), key_size))??;
Ok(kek)
}
macro_rules! dispatch_aes_kw {
($provider:expr, $kek:expr, $data:expr, $op16:ident, $op24:ident, $op32:ident) => {{
match $kek.len() {
16 => {
let kek: &[u8; 16] = $kek.try_into().map_err(|_| HandshakeError::UnsupportedKeyWrapAlgorithm)?;
($provider.$op16::<HandshakeError>())($data, kek)
}
24 => {
let kek: &[u8; 24] = $kek.try_into().map_err(|_| HandshakeError::UnsupportedKeyWrapAlgorithm)?;
($provider.$op24::<HandshakeError>())($data, kek)
}
32 => {
let kek: &[u8; 32] = $kek.try_into().map_err(|_| HandshakeError::UnsupportedKeyWrapAlgorithm)?;
($provider.$op32::<HandshakeError>())($data, kek)
}
_ => Err(HandshakeError::UnsupportedKeyWrapAlgorithm),
}
}};
}
pub(crate) fn wrap_with_kek<P: CryptoProvider>(
provider: &P,
kek: &[u8],
cek: &[u8],
) -> Result<Vec<u8>, HandshakeError> {
dispatch_aes_kw!(provider, kek, cek, as_key_wrapper_16, as_key_wrapper_24, as_key_wrapper_32)
}
pub(crate) fn unwrap_with_kek<P: CryptoProvider>(
provider: &P,
kek: &[u8],
wrapped: &[u8],
) -> Result<Vec<u8>, HandshakeError> {
dispatch_aes_kw!(
provider,
kek,
wrapped,
as_key_unwrapper_16,
as_key_unwrapper_24,
as_key_unwrapper_32
)
}
pub fn kari_wrap<P, C>(
provider: &P,
sender_priv: &SecretKey<C>,
recipient_pub: &PublicKey<C>,
ukm: &[u8],
kdf_info: &[u8],
cek: &[u8],
) -> Result<Vec<u8>, HandshakeError>
where
P: CryptoProvider,
C: Curve + CurveArithmetic,
<C as Curve>::FieldBytesSize: ModulusSize,
AffinePoint<C>: FromEncodedPoint<C> + ToEncodedPoint<C>,
{
let shared_secret = derive_shared_secret(sender_priv, recipient_pub)?;
let key_size = key_wrap_key_size::<P>()?;
let kek = derive_kek::<P>(&shared_secret, ukm, kdf_info, key_size)?;
wrap_with_kek(provider, kek.as_slice(), cek)
}
pub fn kari_unwrap<P, C>(
provider: &P,
recipient_priv: &SecretKey<C>,
originator_pub: &PublicKey<C>,
ukm: &[u8],
kdf_info: &[u8],
wrapped: &[u8],
) -> Result<Vec<u8>, HandshakeError>
where
P: CryptoProvider,
C: Curve + CurveArithmetic,
<C as Curve>::FieldBytesSize: ModulusSize,
AffinePoint<C>: FromEncodedPoint<C> + ToEncodedPoint<C>,
{
let shared_secret = derive_shared_secret(recipient_priv, originator_pub)?;
let key_size = key_wrap_key_size::<P>()?;
let kek = derive_kek::<P>(&shared_secret, ukm, kdf_info, key_size)?;
let cek = unwrap_with_kek(provider, kek.as_slice(), wrapped)?;
let rewrapped = wrap_with_kek(provider, kek.as_slice(), &cek)?;
core::sync::atomic::compiler_fence(core::sync::atomic::Ordering::SeqCst);
let valid: bool = rewrapped.as_slice().ct_eq(wrapped).into();
core::sync::atomic::compiler_fence(core::sync::atomic::Ordering::SeqCst);
if !valid {
return Err(HandshakeError::AesKeyWrap(
crate::crypto::aead::aes_kw::Error::IntegrityCheckFailed,
));
}
Ok(cek)
}
#[cfg(all(feature = "kem", feature = "unstable-pqxdh"))]
pub fn kari_wrap_hybrid<P, C>(
provider: &P,
sender_ec_priv: &SecretKey<C>,
recipient_ec_pub: &PublicKey<C>,
kem_shared_secret: &[u8],
ukm: &[u8],
kdf_info: &[u8],
cek: &[u8],
) -> Result<Vec<u8>, HandshakeError>
where
P: CryptoProvider,
C: Curve + CurveArithmetic,
<C as Curve>::FieldBytesSize: ModulusSize,
AffinePoint<C>: FromEncodedPoint<C> + ToEncodedPoint<C>,
{
use crate::transport::handshake::primitives::multi_input_kdf;
let ecdh_secret = derive_shared_secret(sender_ec_priv, recipient_ec_pub)?;
let key_size = key_wrap_key_size::<P>()?;
let combined_key =
ecdh_secret.with(|ecdh| multi_input_kdf::<P>(&[ecdh, kem_shared_secret], ukm, kdf_info, key_size))??;
wrap_with_kek(provider, combined_key.as_slice(), cek)
}
#[cfg(all(feature = "kem", feature = "unstable-pqxdh"))]
pub fn kari_unwrap_hybrid<P, C>(
provider: &P,
recipient_ec_priv: &SecretKey<C>,
originator_ec_pub: &PublicKey<C>,
kem_shared_secret: &[u8],
ukm: &[u8],
kdf_info: &[u8],
wrapped: &[u8],
) -> Result<Vec<u8>, HandshakeError>
where
P: CryptoProvider,
C: Curve + CurveArithmetic,
<C as Curve>::FieldBytesSize: ModulusSize,
AffinePoint<C>: FromEncodedPoint<C> + ToEncodedPoint<C>,
{
use crate::transport::handshake::primitives::multi_input_kdf;
let ecdh_secret = derive_shared_secret(recipient_ec_priv, originator_ec_pub)?;
let key_size = key_wrap_key_size::<P>()?;
let combined_key =
ecdh_secret.with(|ecdh| multi_input_kdf::<P>(&[ecdh, kem_shared_secret], ukm, kdf_info, key_size))??;
let cek = unwrap_with_kek(provider, combined_key.as_slice(), wrapped)?;
let rewrapped = wrap_with_kek(provider, combined_key.as_slice(), &cek)?;
core::sync::atomic::compiler_fence(core::sync::atomic::Ordering::SeqCst);
let valid: bool = rewrapped.as_slice().ct_eq(wrapped).into();
core::sync::atomic::compiler_fence(core::sync::atomic::Ordering::SeqCst);
if !valid {
return Err(HandshakeError::HybridKariIntegrityCheckFailed);
}
Ok(cek)
}
#[cfg(test)]
mod tests {
use super::*;
use crate::constants::TIGHTBEAM_KARI_KDF_INFO;
use crate::crypto::profiles::DefaultCryptoProvider;
use crate::crypto::sign::ecdsa::k256::SecretKey as K256SecretKey;
use crate::random::OsRng;
#[test]
fn wrap_unwrap_roundtrip() -> Result<(), Box<dyn std::error::Error>> {
let provider = DefaultCryptoProvider::default();
let sender = K256SecretKey::random(&mut OsRng);
let recipient = K256SecretKey::random(&mut OsRng);
let recipient_pub = recipient.public_key();
let sender_pub = sender.public_key();
let ukm = [0x55u8; 64];
let cek = [0x42u8; 32];
let wrapped = kari_wrap(&provider, &sender, &recipient_pub, &ukm, TIGHTBEAM_KARI_KDF_INFO, &cek)?;
assert!(wrapped.len() > cek.len());
let unwrapped = kari_unwrap(&provider, &recipient, &sender_pub, &ukm, TIGHTBEAM_KARI_KDF_INFO, &wrapped)?;
assert_eq!(unwrapped, cek);
Ok(())
}
#[test]
fn unwrap_fail_with_wrong_key() -> Result<(), Box<dyn std::error::Error>> {
let provider = DefaultCryptoProvider::default();
let sender = K256SecretKey::random(&mut OsRng);
let recipient = K256SecretKey::random(&mut OsRng);
let wrong_recipient = K256SecretKey::random(&mut OsRng);
let recipient_pub = recipient.public_key();
let sender_pub = sender.public_key();
let ukm = [0x33u8; 64];
let cek = [0xABu8; 32];
let wrapped = kari_wrap(
&provider,
&sender,
&recipient_pub,
&ukm,
crate::constants::TIGHTBEAM_KARI_KDF_INFO,
&cek,
)?;
let bad = kari_unwrap(
&provider,
&wrong_recipient,
&sender_pub,
&ukm,
crate::constants::TIGHTBEAM_KARI_KDF_INFO,
&wrapped,
);
assert!(bad.is_err());
Ok(())
}
#[test]
fn key_wrap_oid_maps_to_kek_size() -> Result<(), Box<dyn std::error::Error>> {
let cases = [
(crate::oids::AES_128_WRAP, 16usize),
(crate::oids::AES_192_WRAP, 24),
(crate::oids::AES_256_WRAP, 32),
];
for (oid, expected) in cases {
assert_eq!(key_wrap_key_size_from_oid(oid)?, expected);
}
Ok(())
}
#[test]
fn key_wrap_oid_rejects_non_wrap_oid() {
let result = key_wrap_key_size_from_oid(crate::oids::AES_256_GCM);
assert!(matches!(result, Err(HandshakeError::UnsupportedKeyWrapAlgorithm)));
}
#[test]
fn wrap_unwrap_roundtrip_all_kek_sizes() -> Result<(), Box<dyn std::error::Error>> {
let provider = DefaultCryptoProvider::default();
let cek = [0x42u8; 32];
for size in [16usize, 24, 32] {
let kek = vec![0x11u8; size];
let wrapped = wrap_with_kek(&provider, &kek, &cek)?;
let unwrapped = unwrap_with_kek(&provider, &kek, &wrapped)?;
assert_eq!(unwrapped, cek);
}
Ok(())
}
}