use alloc::boxed::Box;
use alloc::vec::Vec;
use core::fmt::{self, Debug, Formatter};
use aws_lc_rs::aead::{
self, Aad, BoundKey, Nonce, NonceSequence, OpeningKey, SealingKey, UnboundKey, NONCE_LEN,
};
use aws_lc_rs::agreement;
use aws_lc_rs::cipher::{AES_128_KEY_LEN, AES_256_KEY_LEN};
use aws_lc_rs::digest::{SHA256_OUTPUT_LEN, SHA384_OUTPUT_LEN, SHA512_OUTPUT_LEN};
use aws_lc_rs::encoding::{AsBigEndian, Curve25519SeedBin, EcPrivateKeyBin};
use zeroize::Zeroize;
use crate::crypto::aws_lc_rs::hmac::{HMAC_SHA256, HMAC_SHA384, HMAC_SHA512};
use crate::crypto::aws_lc_rs::unspecified_err;
use crate::crypto::hpke::{
EncapsulatedSecret, Hpke, HpkeOpener, HpkePrivateKey, HpkePublicKey, HpkeSealer, HpkeSuite,
};
use crate::crypto::tls13::{expand, HkdfExpander, HkdfPrkExtract, HkdfUsingHmac};
use crate::msgs::enums::{HpkeAead, HpkeKdf, HpkeKem};
use crate::msgs::handshake::HpkeSymmetricCipherSuite;
#[cfg(feature = "std")]
use crate::sync::Arc;
use crate::{Error, OtherError};
pub static ALL_SUPPORTED_SUITES: &[&dyn Hpke] = &[
DH_KEM_P256_HKDF_SHA256_AES_128,
DH_KEM_P256_HKDF_SHA256_AES_256,
#[cfg(not(feature = "fips"))]
DH_KEM_P256_HKDF_SHA256_CHACHA20_POLY1305,
DH_KEM_P384_HKDF_SHA384_AES_128,
DH_KEM_P384_HKDF_SHA384_AES_256,
#[cfg(not(feature = "fips"))]
DH_KEM_P384_HKDF_SHA384_CHACHA20_POLY1305,
DH_KEM_P521_HKDF_SHA512_AES_128,
DH_KEM_P521_HKDF_SHA512_AES_256,
#[cfg(not(feature = "fips"))]
DH_KEM_P521_HKDF_SHA512_CHACHA20_POLY1305,
#[cfg(not(feature = "fips"))]
DH_KEM_X25519_HKDF_SHA256_AES_128,
#[cfg(not(feature = "fips"))]
DH_KEM_X25519_HKDF_SHA256_AES_256,
#[cfg(not(feature = "fips"))]
DH_KEM_X25519_HKDF_SHA256_CHACHA20_POLY1305,
];
pub static DH_KEM_P256_HKDF_SHA256_AES_128: &HpkeAwsLcRs<AES_128_KEY_LEN, SHA256_OUTPUT_LEN> =
&HpkeAwsLcRs {
suite: HpkeSuite {
kem: HpkeKem::DHKEM_P256_HKDF_SHA256,
sym: HpkeSymmetricCipherSuite {
kdf_id: HpkeKdf::HKDF_SHA256,
aead_id: HpkeAead::AES_128_GCM,
},
},
dh_kem: DH_KEM_P256_HKDF_SHA256,
hkdf: RING_HKDF_HMAC_SHA256,
aead: &aead::AES_128_GCM,
};
pub static DH_KEM_P256_HKDF_SHA256_AES_256: &HpkeAwsLcRs<AES_256_KEY_LEN, SHA256_OUTPUT_LEN> =
&HpkeAwsLcRs {
suite: HpkeSuite {
kem: HpkeKem::DHKEM_P256_HKDF_SHA256,
sym: HpkeSymmetricCipherSuite {
kdf_id: HpkeKdf::HKDF_SHA256,
aead_id: HpkeAead::AES_256_GCM,
},
},
dh_kem: DH_KEM_P256_HKDF_SHA256,
hkdf: RING_HKDF_HMAC_SHA256,
aead: &aead::AES_256_GCM,
};
pub static DH_KEM_P256_HKDF_SHA256_CHACHA20_POLY1305: &HpkeAwsLcRs<
CHACHA_KEY_LEN,
SHA256_OUTPUT_LEN,
> = &HpkeAwsLcRs {
suite: HpkeSuite {
kem: HpkeKem::DHKEM_P256_HKDF_SHA256,
sym: HpkeSymmetricCipherSuite {
kdf_id: HpkeKdf::HKDF_SHA256,
aead_id: HpkeAead::CHACHA20_POLY_1305,
},
},
dh_kem: DH_KEM_P256_HKDF_SHA256,
hkdf: RING_HKDF_HMAC_SHA256,
aead: &aead::CHACHA20_POLY1305,
};
pub static DH_KEM_P384_HKDF_SHA384_AES_128: &HpkeAwsLcRs<AES_128_KEY_LEN, SHA384_OUTPUT_LEN> =
&HpkeAwsLcRs {
suite: HpkeSuite {
kem: HpkeKem::DHKEM_P384_HKDF_SHA384,
sym: HpkeSymmetricCipherSuite {
kdf_id: HpkeKdf::HKDF_SHA384,
aead_id: HpkeAead::AES_128_GCM,
},
},
dh_kem: DH_KEM_P384_HKDF_SHA384,
hkdf: RING_HKDF_HMAC_SHA384,
aead: &aead::AES_128_GCM,
};
pub static DH_KEM_P384_HKDF_SHA384_AES_256: &HpkeAwsLcRs<AES_256_KEY_LEN, SHA384_OUTPUT_LEN> =
&HpkeAwsLcRs {
suite: HpkeSuite {
kem: HpkeKem::DHKEM_P384_HKDF_SHA384,
sym: HpkeSymmetricCipherSuite {
kdf_id: HpkeKdf::HKDF_SHA384,
aead_id: HpkeAead::AES_256_GCM,
},
},
dh_kem: DH_KEM_P384_HKDF_SHA384,
hkdf: RING_HKDF_HMAC_SHA384,
aead: &aead::AES_256_GCM,
};
pub static DH_KEM_P384_HKDF_SHA384_CHACHA20_POLY1305: &HpkeAwsLcRs<
CHACHA_KEY_LEN,
SHA384_OUTPUT_LEN,
> = &HpkeAwsLcRs {
suite: HpkeSuite {
kem: HpkeKem::DHKEM_P384_HKDF_SHA384,
sym: HpkeSymmetricCipherSuite {
kdf_id: HpkeKdf::HKDF_SHA384,
aead_id: HpkeAead::CHACHA20_POLY_1305,
},
},
dh_kem: DH_KEM_P384_HKDF_SHA384,
hkdf: RING_HKDF_HMAC_SHA384,
aead: &aead::CHACHA20_POLY1305,
};
pub static DH_KEM_P521_HKDF_SHA512_AES_128: &HpkeAwsLcRs<AES_128_KEY_LEN, SHA512_OUTPUT_LEN> =
&HpkeAwsLcRs {
suite: HpkeSuite {
kem: HpkeKem::DHKEM_P521_HKDF_SHA512,
sym: HpkeSymmetricCipherSuite {
kdf_id: HpkeKdf::HKDF_SHA512,
aead_id: HpkeAead::AES_128_GCM,
},
},
dh_kem: DH_KEM_P521_HKDF_SHA512,
hkdf: RING_HKDF_HMAC_SHA512,
aead: &aead::AES_128_GCM,
};
pub static DH_KEM_P521_HKDF_SHA512_AES_256: &HpkeAwsLcRs<AES_256_KEY_LEN, SHA512_OUTPUT_LEN> =
&HpkeAwsLcRs {
suite: HpkeSuite {
kem: HpkeKem::DHKEM_P521_HKDF_SHA512,
sym: HpkeSymmetricCipherSuite {
kdf_id: HpkeKdf::HKDF_SHA512,
aead_id: HpkeAead::AES_256_GCM,
},
},
dh_kem: DH_KEM_P521_HKDF_SHA512,
hkdf: RING_HKDF_HMAC_SHA512,
aead: &aead::AES_256_GCM,
};
pub static DH_KEM_P521_HKDF_SHA512_CHACHA20_POLY1305: &HpkeAwsLcRs<
CHACHA_KEY_LEN,
SHA512_OUTPUT_LEN,
> = &HpkeAwsLcRs {
suite: HpkeSuite {
kem: HpkeKem::DHKEM_P521_HKDF_SHA512,
sym: HpkeSymmetricCipherSuite {
kdf_id: HpkeKdf::HKDF_SHA512,
aead_id: HpkeAead::CHACHA20_POLY_1305,
},
},
dh_kem: DH_KEM_P521_HKDF_SHA512,
hkdf: RING_HKDF_HMAC_SHA512,
aead: &aead::CHACHA20_POLY1305,
};
pub static DH_KEM_X25519_HKDF_SHA256_AES_128: &HpkeAwsLcRs<AES_128_KEY_LEN, SHA256_OUTPUT_LEN> =
&HpkeAwsLcRs {
suite: HpkeSuite {
kem: HpkeKem::DHKEM_X25519_HKDF_SHA256,
sym: HpkeSymmetricCipherSuite {
kdf_id: HpkeKdf::HKDF_SHA256,
aead_id: HpkeAead::AES_128_GCM,
},
},
dh_kem: DH_KEM_X25519_HKDF_SHA256,
hkdf: RING_HKDF_HMAC_SHA256,
aead: &aead::AES_128_GCM,
};
pub static DH_KEM_X25519_HKDF_SHA256_AES_256: &HpkeAwsLcRs<AES_256_KEY_LEN, SHA256_OUTPUT_LEN> =
&HpkeAwsLcRs {
suite: HpkeSuite {
kem: HpkeKem::DHKEM_X25519_HKDF_SHA256,
sym: HpkeSymmetricCipherSuite {
kdf_id: HpkeKdf::HKDF_SHA256,
aead_id: HpkeAead::AES_256_GCM,
},
},
dh_kem: DH_KEM_X25519_HKDF_SHA256,
hkdf: RING_HKDF_HMAC_SHA256,
aead: &aead::AES_256_GCM,
};
pub static DH_KEM_X25519_HKDF_SHA256_CHACHA20_POLY1305: &HpkeAwsLcRs<
CHACHA_KEY_LEN,
SHA256_OUTPUT_LEN,
> = &HpkeAwsLcRs {
suite: HpkeSuite {
kem: HpkeKem::DHKEM_X25519_HKDF_SHA256,
sym: HpkeSymmetricCipherSuite {
kdf_id: HpkeKdf::HKDF_SHA256,
aead_id: HpkeAead::CHACHA20_POLY_1305,
},
},
dh_kem: DH_KEM_X25519_HKDF_SHA256,
hkdf: RING_HKDF_HMAC_SHA256,
aead: &aead::CHACHA20_POLY1305,
};
pub struct HpkeAwsLcRs<const KEY_SIZE: usize, const KDF_SIZE: usize> {
suite: HpkeSuite,
dh_kem: &'static DhKem<KDF_SIZE>,
hkdf: &'static dyn HkdfPrkExtract,
aead: &'static aead::Algorithm,
}
impl<const KEY_SIZE: usize, const KDF_SIZE: usize> HpkeAwsLcRs<KEY_SIZE, KDF_SIZE> {
fn key_schedule(
&self,
shared_secret: KemSharedSecret<KDF_SIZE>,
info: &[u8],
) -> Result<KeySchedule<KEY_SIZE>, Error> {
let suite_id = LabeledSuiteId::Hpke(self.suite);
let psk_id_hash = labeled_extract_for_prk(self.hkdf, suite_id, None, Label::PskIdHash, &[]);
let info_hash = labeled_extract_for_prk(self.hkdf, suite_id, None, Label::InfoHash, info);
let key_schedule_context = [
&[0][..], &psk_id_hash,
&info_hash,
]
.concat();
let key = AeadKey(self.key_schedule_labeled_expand::<KEY_SIZE>(
&shared_secret,
&key_schedule_context,
Label::Key,
));
let base_nonce = self.key_schedule_labeled_expand::<NONCE_LEN>(
&shared_secret,
&key_schedule_context,
Label::BaseNonce,
);
Ok(KeySchedule {
aead: self.aead,
key,
base_nonce,
seq_num: 0,
})
}
fn key_schedule_labeled_expand<const L: usize>(
&self,
shared_secret: &KemSharedSecret<KDF_SIZE>,
key_schedule_context: &[u8],
label: Label,
) -> [u8; L] {
let suite_id = LabeledSuiteId::Hpke(self.suite);
labeled_expand::<L>(
suite_id,
labeled_extract_for_expand(
self.hkdf,
suite_id,
Some(&shared_secret.0),
Label::Secret,
&[],
),
label,
key_schedule_context,
)
}
}
impl<const KEY_SIZE: usize, const KDF_SIZE: usize> Hpke for HpkeAwsLcRs<KEY_SIZE, KDF_SIZE> {
fn seal(
&self,
info: &[u8],
aad: &[u8],
plaintext: &[u8],
pub_key: &HpkePublicKey,
) -> Result<(EncapsulatedSecret, Vec<u8>), Error> {
let (encap, mut sealer) = self.setup_sealer(info, pub_key)?;
Ok((encap, sealer.seal(aad, plaintext)?))
}
fn setup_sealer(
&self,
info: &[u8],
pub_key: &HpkePublicKey,
) -> Result<(EncapsulatedSecret, Box<dyn HpkeSealer + 'static>), Error> {
let (encap, sealer) = Sealer::new(self, info, pub_key)?;
Ok((encap, Box::new(sealer)))
}
fn open(
&self,
enc: &EncapsulatedSecret,
info: &[u8],
aad: &[u8],
ciphertext: &[u8],
secret_key: &HpkePrivateKey,
) -> Result<Vec<u8>, Error> {
self.setup_opener(enc, info, secret_key)?
.open(aad, ciphertext)
}
fn setup_opener(
&self,
enc: &EncapsulatedSecret,
info: &[u8],
secret_key: &HpkePrivateKey,
) -> Result<Box<dyn HpkeOpener + 'static>, Error> {
Ok(Box::new(Opener::new(self, enc, info, secret_key)?))
}
fn fips(&self) -> bool {
matches!(
(self.suite.kem, self.suite.sym.aead_id),
(
HpkeKem::DHKEM_P256_HKDF_SHA256
| HpkeKem::DHKEM_P384_HKDF_SHA384
| HpkeKem::DHKEM_P521_HKDF_SHA512,
HpkeAead::AES_128_GCM | HpkeAead::AES_256_GCM,
)
)
}
fn generate_key_pair(&self) -> Result<(HpkePublicKey, HpkePrivateKey), Error> {
(self.dh_kem.key_generator)()
}
fn suite(&self) -> HpkeSuite {
self.suite
}
}
impl<const KEY_SIZE: usize, const KDF_SIZE: usize> Debug for HpkeAwsLcRs<KEY_SIZE, KDF_SIZE> {
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
self.suite.fmt(f)
}
}
struct Sealer<const KEY_SIZE: usize, const KDF_SIZE: usize> {
key_schedule: KeySchedule<KEY_SIZE>,
}
impl<const KEY_SIZE: usize, const KDF_SIZE: usize> Sealer<KEY_SIZE, KDF_SIZE> {
fn new(
suite: &HpkeAwsLcRs<KEY_SIZE, KDF_SIZE>,
info: &[u8],
pub_key: &HpkePublicKey,
) -> Result<(EncapsulatedSecret, Self), Error> {
let (shared_secret, enc) = suite.dh_kem.encap(pub_key)?;
let key_schedule = suite.key_schedule(shared_secret, info)?;
Ok((enc, Self { key_schedule }))
}
#[cfg(test)]
fn test_only_new(
suite: &HpkeAwsLcRs<KEY_SIZE, KDF_SIZE>,
info: &[u8],
pub_key: &HpkePublicKey,
sk_e: &[u8],
) -> Result<(EncapsulatedSecret, Self), Error> {
let (shared_secret, enc) = suite
.dh_kem
.test_only_encap(pub_key, sk_e)?;
let key_schedule = suite.key_schedule(shared_secret, info)?;
Ok((enc, Self { key_schedule }))
}
}
impl<const KEY_SIZE: usize, const KDF_SIZE: usize> HpkeSealer for Sealer<KEY_SIZE, KDF_SIZE> {
fn seal(&mut self, aad: &[u8], plaintext: &[u8]) -> Result<Vec<u8>, Error> {
let key = UnboundKey::new(self.key_schedule.aead, &self.key_schedule.key.0)
.map_err(unspecified_err)?;
let mut sealing_key = SealingKey::new(key, &mut self.key_schedule);
let mut in_out_buffer = Vec::from(plaintext);
sealing_key
.seal_in_place_append_tag(Aad::from(aad), &mut in_out_buffer)
.map_err(unspecified_err)?;
Ok(in_out_buffer)
}
}
impl<const KEY_SIZE: usize, const KDF_SIZE: usize> Debug for Sealer<KEY_SIZE, KDF_SIZE> {
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
f.debug_struct("Sealer").finish()
}
}
struct Opener<const KEY_SIZE: usize, const KDF_SIZE: usize> {
key_schedule: KeySchedule<KEY_SIZE>,
}
impl<const KEY_SIZE: usize, const KDF_SIZE: usize> Opener<KEY_SIZE, KDF_SIZE> {
fn new(
suite: &HpkeAwsLcRs<KEY_SIZE, KDF_SIZE>,
enc: &EncapsulatedSecret,
info: &[u8],
secret_key: &HpkePrivateKey,
) -> Result<Self, Error> {
Ok(Self {
key_schedule: suite.key_schedule(suite.dh_kem.decap(enc, secret_key)?, info)?,
})
}
}
impl<const KEY_SIZE: usize, const KDF_SIZE: usize> HpkeOpener for Opener<KEY_SIZE, KDF_SIZE> {
fn open(&mut self, aad: &[u8], ciphertext: &[u8]) -> Result<Vec<u8>, Error> {
let key = UnboundKey::new(self.key_schedule.aead, &self.key_schedule.key.0)
.map_err(unspecified_err)?;
let mut opening_key = OpeningKey::new(key, &mut self.key_schedule);
let mut in_out_buffer = Vec::from(ciphertext);
let plaintext = opening_key
.open_in_place(Aad::from(aad), &mut in_out_buffer)
.map_err(unspecified_err)?;
Ok(plaintext.to_vec())
}
}
impl<const KEY_SIZE: usize, const KDF_SIZE: usize> Debug for Opener<KEY_SIZE, KDF_SIZE> {
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
f.debug_struct("Opener").finish()
}
}
struct DhKem<const KDF_SIZE: usize> {
id: HpkeKem,
agreement_algorithm: &'static agreement::Algorithm,
key_generator:
&'static (dyn Fn() -> Result<(HpkePublicKey, HpkePrivateKey), Error> + Send + Sync),
hkdf: &'static dyn HkdfPrkExtract,
}
impl<const KDF_SIZE: usize> DhKem<KDF_SIZE> {
fn encap(
&self,
recipient: &HpkePublicKey,
) -> Result<(KemSharedSecret<KDF_SIZE>, EncapsulatedSecret), Error> {
let sk_e =
agreement::PrivateKey::generate(self.agreement_algorithm).map_err(unspecified_err)?;
self.encap_impl(recipient, sk_e)
}
#[cfg(test)]
fn test_only_encap(
&self,
recipient: &HpkePublicKey,
test_only_ske: &[u8],
) -> Result<(KemSharedSecret<KDF_SIZE>, EncapsulatedSecret), Error> {
let sk_e = agreement::PrivateKey::from_private_key(self.agreement_algorithm, test_only_ske)
.map_err(key_rejected_err)?;
self.encap_impl(recipient, sk_e)
}
fn encap_impl(
&self,
recipient: &HpkePublicKey,
sk_e: agreement::PrivateKey,
) -> Result<(KemSharedSecret<KDF_SIZE>, EncapsulatedSecret), Error> {
let enc = sk_e
.compute_public_key()
.map_err(unspecified_err)?;
let pk_r = agreement::UnparsedPublicKey::new(self.agreement_algorithm, &recipient.0);
let kem_context = [enc.as_ref(), pk_r.bytes()].concat();
let shared_secret = agreement::agree(&sk_e, &pk_r, aws_lc_rs::error::Unspecified, |dh| {
Ok(self.extract_and_expand(dh, &kem_context))
})
.map_err(unspecified_err)?;
Ok((
KemSharedSecret(shared_secret),
EncapsulatedSecret(enc.as_ref().into()),
))
}
fn decap(
&self,
enc: &EncapsulatedSecret,
recipient: &HpkePrivateKey,
) -> Result<KemSharedSecret<KDF_SIZE>, Error> {
let pk_e = agreement::UnparsedPublicKey::new(self.agreement_algorithm, &enc.0);
let sk_r = agreement::PrivateKey::from_private_key(
self.agreement_algorithm,
recipient.secret_bytes(),
)
.map_err(key_rejected_err)?;
let pk_rm = sk_r
.compute_public_key()
.map_err(unspecified_err)?;
let kem_context = [&enc.0, pk_rm.as_ref()].concat();
let shared_secret = agreement::agree(&sk_r, &pk_e, aws_lc_rs::error::Unspecified, |dh| {
Ok(self.extract_and_expand(dh, &kem_context))
})
.map_err(unspecified_err)?;
Ok(KemSharedSecret(shared_secret))
}
fn extract_and_expand(&self, dh: &[u8], kem_context: &[u8]) -> [u8; KDF_SIZE] {
let suite_id = LabeledSuiteId::Kem(self.id);
labeled_expand(
suite_id,
labeled_extract_for_expand(self.hkdf, suite_id, None, Label::EaePrk, dh),
Label::SharedSecret,
kem_context,
)
}
}
static DH_KEM_P256_HKDF_SHA256: &DhKem<SHA256_OUTPUT_LEN> = &DhKem {
id: HpkeKem::DHKEM_P256_HKDF_SHA256,
agreement_algorithm: &agreement::ECDH_P256,
key_generator: &|| generate_p_curve_key_pair(&agreement::ECDH_P256),
hkdf: RING_HKDF_HMAC_SHA256,
};
static DH_KEM_P384_HKDF_SHA384: &DhKem<SHA384_OUTPUT_LEN> = &DhKem {
id: HpkeKem::DHKEM_P384_HKDF_SHA384,
agreement_algorithm: &agreement::ECDH_P384,
key_generator: &|| generate_p_curve_key_pair(&agreement::ECDH_P384),
hkdf: RING_HKDF_HMAC_SHA384,
};
static DH_KEM_P521_HKDF_SHA512: &DhKem<SHA512_OUTPUT_LEN> = &DhKem {
id: HpkeKem::DHKEM_P521_HKDF_SHA512,
agreement_algorithm: &agreement::ECDH_P521,
key_generator: &|| generate_p_curve_key_pair(&agreement::ECDH_P521),
hkdf: RING_HKDF_HMAC_SHA512,
};
static DH_KEM_X25519_HKDF_SHA256: &DhKem<SHA256_OUTPUT_LEN> = &DhKem {
id: HpkeKem::DHKEM_X25519_HKDF_SHA256,
agreement_algorithm: &agreement::X25519,
key_generator: &generate_x25519_key_pair,
hkdf: RING_HKDF_HMAC_SHA256,
};
fn generate_p_curve_key_pair(
alg: &'static agreement::Algorithm,
) -> Result<(HpkePublicKey, HpkePrivateKey), Error> {
debug_assert_ne!(alg, &agreement::X25519);
let (public_key, private_key) = generate_key_pair(alg)?;
let raw_private_key: EcPrivateKeyBin<'_> = private_key
.as_be_bytes()
.map_err(unspecified_err)?;
Ok((
public_key,
HpkePrivateKey::from(raw_private_key.as_ref().to_vec()),
))
}
fn generate_x25519_key_pair() -> Result<(HpkePublicKey, HpkePrivateKey), Error> {
let (public_key, private_key) = generate_key_pair(&agreement::X25519)?;
let raw_private_key: Curve25519SeedBin<'_> = private_key
.as_be_bytes()
.map_err(unspecified_err)?;
Ok((
public_key,
HpkePrivateKey::from(raw_private_key.as_ref().to_vec()),
))
}
fn generate_key_pair(
alg: &'static agreement::Algorithm,
) -> Result<(HpkePublicKey, agreement::PrivateKey), Error> {
let private_key = agreement::PrivateKey::generate(alg).map_err(unspecified_err)?;
let public_key = HpkePublicKey(
private_key
.compute_public_key()
.map_err(unspecified_err)?
.as_ref()
.to_vec(),
);
Ok((public_key, private_key))
}
struct KeySchedule<const KEY_SIZE: usize> {
aead: &'static aead::Algorithm,
key: AeadKey<KEY_SIZE>,
base_nonce: [u8; NONCE_LEN],
seq_num: u32,
}
impl<const KEY_SIZE: usize> KeySchedule<KEY_SIZE> {
fn compute_nonce(&self) -> [u8; NONCE_LEN] {
let mut nonce = [0; NONCE_LEN];
let seq_bytes = self.seq_num.to_be_bytes();
nonce[NONCE_LEN - seq_bytes.len()..].copy_from_slice(&seq_bytes);
for (n, &b) in nonce.iter_mut().zip(&self.base_nonce) {
*n ^= b;
}
nonce
}
fn increment_seq_num(&mut self) -> Result<(), aws_lc_rs::error::Unspecified> {
let max_seq_num = (1u128 << (NONCE_LEN * 8)) - 1;
if u128::from(self.seq_num) >= max_seq_num {
return Err(aws_lc_rs::error::Unspecified);
}
self.seq_num += 1;
Ok(())
}
}
impl<const KEY_SIZE: usize> NonceSequence for &mut KeySchedule<KEY_SIZE> {
fn advance(&mut self) -> Result<Nonce, aws_lc_rs::error::Unspecified> {
let nonce = self.compute_nonce();
self.increment_seq_num()?;
Nonce::try_assume_unique_for_key(&nonce)
}
}
fn labeled_extract_for_expand(
hkdf: &'static dyn HkdfPrkExtract,
suite_id: LabeledSuiteId,
salt: Option<&[u8]>,
label: Label,
ikm: &[u8],
) -> Box<dyn HkdfExpander> {
let labeled_ikm = [&b"HPKE-v1"[..], &suite_id.encoded(), label.as_ref(), ikm].concat();
hkdf.extract_from_secret(salt, &labeled_ikm)
}
fn labeled_extract_for_prk(
hkdf: &'static dyn HkdfPrkExtract,
suite_id: LabeledSuiteId,
salt: Option<&[u8]>,
label: Label,
ikm: &[u8],
) -> Vec<u8> {
let labeled_ikm = [&b"HPKE-v1"[..], &suite_id.encoded(), label.as_ref(), ikm].concat();
hkdf.extract_prk_from_secret(salt, &labeled_ikm)
}
fn labeled_expand<const L: usize>(
suite_id: LabeledSuiteId,
expander: Box<dyn HkdfExpander>,
label: Label,
kem_context: &[u8],
) -> [u8; L] {
let output_len = u16::to_be_bytes(L as u16);
let info = &[
&output_len[..],
b"HPKE-v1",
&suite_id.encoded(),
label.as_ref(),
kem_context,
];
expand(&*expander, info)
}
#[derive(Debug)]
enum Label {
PskIdHash,
InfoHash,
Secret,
Key,
BaseNonce,
EaePrk,
SharedSecret,
}
impl AsRef<[u8]> for Label {
fn as_ref(&self) -> &[u8] {
match self {
Self::PskIdHash => b"psk_id_hash",
Self::InfoHash => b"info_hash",
Self::Secret => b"secret",
Self::Key => b"key",
Self::BaseNonce => b"base_nonce",
Self::EaePrk => b"eae_prk",
Self::SharedSecret => b"shared_secret",
}
}
}
#[derive(Debug, Copy, Clone)]
enum LabeledSuiteId {
Hpke(HpkeSuite),
Kem(HpkeKem),
}
impl LabeledSuiteId {
fn encoded(&self) -> Vec<u8> {
match self {
Self::Hpke(suite) => [
&b"HPKE"[..],
&u16::from(suite.kem).to_be_bytes(),
&u16::from(suite.sym.kdf_id).to_be_bytes(),
&u16::from(suite.sym.aead_id).to_be_bytes(),
]
.concat(),
Self::Kem(kem) => [&b"KEM"[..], &u16::from(*kem).to_be_bytes()].concat(),
}
}
}
struct AeadKey<const KEY_LEN: usize>([u8; KEY_LEN]);
impl<const KEY_LEN: usize> Drop for AeadKey<KEY_LEN> {
fn drop(&mut self) {
self.0.zeroize()
}
}
struct KemSharedSecret<const KDF_LEN: usize>([u8; KDF_LEN]);
impl<const KDF_LEN: usize> Drop for KemSharedSecret<KDF_LEN> {
fn drop(&mut self) {
self.0.zeroize();
}
}
fn key_rejected_err(_e: aws_lc_rs::error::KeyRejected) -> Error {
#[cfg(feature = "std")]
{
Error::Other(OtherError(Arc::new(_e)))
}
#[cfg(not(feature = "std"))]
{
Error::Other(OtherError())
}
}
const CHACHA_KEY_LEN: usize = 32;
static RING_HKDF_HMAC_SHA256: &HkdfUsingHmac<'static> = &HkdfUsingHmac(&HMAC_SHA256);
static RING_HKDF_HMAC_SHA384: &HkdfUsingHmac<'static> = &HkdfUsingHmac(&HMAC_SHA384);
static RING_HKDF_HMAC_SHA512: &HkdfUsingHmac<'static> = &HkdfUsingHmac(&HMAC_SHA512);
#[cfg(test)]
mod tests {
use alloc::{format, vec};
use super::*;
#[test]
fn smoke_test() {
for suite in ALL_SUPPORTED_SUITES {
_ = format!("{suite:?}"); let (pk, sk) = suite.generate_key_pair().unwrap();
let info = &[
0x4f, 0x64, 0x65, 0x20, 0x6f, 0x6e, 0x20, 0x61, 0x20, 0x47, 0x72, 0x65, 0x63, 0x69,
0x61, 0x6e, 0x20, 0x55, 0x72, 0x6e,
][..];
let (enc, mut sealer) = suite.setup_sealer(info, &pk).unwrap();
_ = format!("{sealer:?}"); let bad_setup_res = suite.setup_sealer(info, &HpkePublicKey(vec![]));
assert!(matches!(bad_setup_res.unwrap_err(), Error::Other(_)));
let aad = &[0xC0, 0xFF, 0xEE];
let pt = &[0xF0, 0x0D];
let ct = sealer.seal(aad, pt).unwrap();
let mut opener = suite
.setup_opener(&enc, info, &sk)
.unwrap();
_ = format!("{opener:?}"); let bad_key_res = suite.setup_opener(&enc, info, &HpkePrivateKey::from(vec![]));
assert!(matches!(bad_key_res.unwrap_err(), Error::Other(_)));
let pt_prime = opener.open(aad, &ct).unwrap();
assert_eq!(pt_prime, pt);
let open_res = opener.open(&[0x0], &ct);
assert!(matches!(open_res.unwrap_err(), Error::Other(_)));
let mut sk_rm_prime = sk.secret_bytes().to_vec();
sk_rm_prime[10] ^= 0xFF; let mut opener_two = suite
.setup_opener(&enc, info, &HpkePrivateKey::from(sk_rm_prime))
.unwrap();
let open_res = opener_two.open(aad, &ct);
assert!(matches!(open_res.unwrap_err(), Error::Other(_)));
}
}
#[cfg(not(feature = "fips"))] #[test]
fn test_fips() {
let testcases: &[(&dyn Hpke, bool)] = &[
(DH_KEM_P256_HKDF_SHA256_AES_128, true),
(DH_KEM_P256_HKDF_SHA256_AES_256, true),
(DH_KEM_P384_HKDF_SHA384_AES_128, true),
(DH_KEM_P384_HKDF_SHA384_AES_256, true),
(DH_KEM_P521_HKDF_SHA512_AES_128, true),
(DH_KEM_P521_HKDF_SHA512_AES_256, true),
(DH_KEM_P256_HKDF_SHA256_CHACHA20_POLY1305, false),
(DH_KEM_P384_HKDF_SHA384_CHACHA20_POLY1305, false),
(DH_KEM_P521_HKDF_SHA512_CHACHA20_POLY1305, false),
(DH_KEM_X25519_HKDF_SHA256_AES_128, false),
(DH_KEM_X25519_HKDF_SHA256_AES_256, false),
(DH_KEM_X25519_HKDF_SHA256_CHACHA20_POLY1305, false),
];
for (suite, expected) in testcases {
assert_eq!(suite.fips(), *expected);
}
}
}
#[cfg(test)]
mod rfc_tests {
use alloc::string::String;
use std::fs::File;
use std::println;
use serde::Deserialize;
use super::*;
#[test]
fn check_test_vectors() {
for (idx, vec) in test_vectors().into_iter().enumerate() {
let Some(hpke) = vec.applicable() else {
println!("skipping inapplicable vector {idx}");
continue;
};
println!("testing vector {idx}");
let pk_r = HpkePublicKey(hex::decode(vec.pk_rm).unwrap());
let sk_r = HpkePrivateKey::from(hex::decode(vec.sk_rm).unwrap());
let sk_em = hex::decode(vec.sk_em).unwrap();
let info = hex::decode(vec.info).unwrap();
let expected_enc = hex::decode(vec.enc).unwrap();
let (enc, mut sealer) = hpke
.setup_test_sealer(&info, &pk_r, &sk_em)
.unwrap();
assert_eq!(enc.0, expected_enc);
let mut opener = hpke
.setup_opener(&enc, &info, &sk_r)
.unwrap();
for test_encryption in vec.encryptions {
let aad = hex::decode(test_encryption.aad).unwrap();
let pt = hex::decode(test_encryption.pt).unwrap();
let expected_ct = hex::decode(test_encryption.ct).unwrap();
let ciphertext = sealer.seal(&aad, &pt).unwrap();
assert_eq!(ciphertext, expected_ct);
let plaintext = opener.open(&aad, &ciphertext).unwrap();
assert_eq!(plaintext, pt);
}
}
}
trait TestHpke: Hpke {
fn setup_test_sealer(
&self,
info: &[u8],
pub_key: &HpkePublicKey,
sk_em: &[u8],
) -> Result<(EncapsulatedSecret, Box<dyn HpkeSealer + 'static>), Error>;
}
impl<const KEY_SIZE: usize, const KDF_SIZE: usize> TestHpke for HpkeAwsLcRs<KEY_SIZE, KDF_SIZE> {
fn setup_test_sealer(
&self,
info: &[u8],
pub_key: &HpkePublicKey,
sk_em: &[u8],
) -> Result<(EncapsulatedSecret, Box<dyn HpkeSealer + 'static>), Error> {
let (encap, sealer) = Sealer::test_only_new(self, info, pub_key, sk_em)?;
Ok((encap, Box::new(sealer)))
}
}
static TEST_SUITES: &[&dyn TestHpke] = &[
DH_KEM_P256_HKDF_SHA256_AES_128,
DH_KEM_P256_HKDF_SHA256_AES_256,
#[cfg(not(feature = "fips"))]
DH_KEM_P256_HKDF_SHA256_CHACHA20_POLY1305,
DH_KEM_P384_HKDF_SHA384_AES_128,
DH_KEM_P384_HKDF_SHA384_AES_256,
#[cfg(not(feature = "fips"))]
DH_KEM_P384_HKDF_SHA384_CHACHA20_POLY1305,
DH_KEM_P521_HKDF_SHA512_AES_128,
DH_KEM_P521_HKDF_SHA512_AES_256,
#[cfg(not(feature = "fips"))]
DH_KEM_P521_HKDF_SHA512_CHACHA20_POLY1305,
#[cfg(not(feature = "fips"))]
DH_KEM_X25519_HKDF_SHA256_AES_128,
#[cfg(not(feature = "fips"))]
DH_KEM_X25519_HKDF_SHA256_AES_256,
#[cfg(not(feature = "fips"))]
DH_KEM_X25519_HKDF_SHA256_CHACHA20_POLY1305,
];
#[derive(Deserialize, Debug)]
struct TestVector {
mode: u8,
kem_id: u16,
kdf_id: u16,
aead_id: u16,
info: String,
#[serde(rename(deserialize = "pkRm"))]
pk_rm: String,
#[serde(rename(deserialize = "skRm"))]
sk_rm: String,
#[serde(rename(deserialize = "skEm"))]
sk_em: String,
enc: String,
encryptions: Vec<TestEncryption>,
}
#[derive(Deserialize, Debug)]
struct TestEncryption {
aad: String,
pt: String,
ct: String,
}
impl TestVector {
fn suite(&self) -> HpkeSuite {
HpkeSuite {
kem: HpkeKem::from(self.kem_id),
sym: HpkeSymmetricCipherSuite {
kdf_id: HpkeKdf::from(self.kdf_id),
aead_id: HpkeAead::from(self.aead_id),
},
}
}
fn applicable(&self) -> Option<&'static dyn TestHpke> {
if self.mode != 0 {
return None;
}
Self::lookup_suite(self.suite(), TEST_SUITES)
}
fn lookup_suite(
suite: HpkeSuite,
supported: &[&'static dyn TestHpke],
) -> Option<&'static dyn TestHpke> {
supported
.iter()
.find(|s| s.suite() == suite)
.copied()
}
}
fn test_vectors() -> Vec<TestVector> {
serde_json::from_reader(
&mut File::open("../rustls-provider-test/tests/rfc-9180-test-vectors.json")
.expect("failed to open test vectors data file"),
)
.expect("failed to deserialize test vectors")
}
}