use crate::{
HpkeError,
aead::{Aead, AeadCtxR, AeadCtxS},
kdf::{DigestArray, Kdf as KdfTrait},
kem::Kem as KemTrait,
op_mode::{OpModeR, OpModeS},
};
#[cfg(feature = "getrandom")]
use getrandom::SysRng;
use rand_core::CryptoRng;
#[cfg(feature = "getrandom")]
use rand_core::UnwrapErr;
use zeroize::Zeroize;
#[doc(hidden)]
pub struct ExporterSecret<K: KdfTrait>(pub DigestArray<K>);
impl<K: KdfTrait> Default for ExporterSecret<K> {
fn default() -> ExporterSecret<K> {
ExporterSecret(DigestArray::<K>::default())
}
}
#[cfg(test)]
impl<K: KdfTrait> Clone for ExporterSecret<K> {
fn clone(&self) -> ExporterSecret<K> {
ExporterSecret(self.0.clone())
}
}
impl<K: KdfTrait> Zeroize for ExporterSecret<K> {
fn zeroize(&mut self) {
self.0.zeroize();
}
}
impl<K: KdfTrait> Drop for ExporterSecret<K> {
fn drop(&mut self) {
self.0.zeroize();
}
}
#[cfg(feature = "getrandom")]
pub fn setup_sender<A, Kdf, Kem>(
mode: &OpModeS<Kem>,
pk_recip: &Kem::PublicKey,
info: &[u8],
) -> Result<(Kem::EncappedKey, AeadCtxS<A, Kdf, Kem>), HpkeError>
where
A: Aead,
Kdf: KdfTrait,
Kem: KemTrait,
{
setup_sender_with_rng::<A, Kdf, Kem>(mode, pk_recip, info, &mut UnwrapErr(SysRng))
}
pub fn setup_sender_with_rng<A, Kdf, Kem>(
mode: &OpModeS<Kem>,
pk_recip: &Kem::PublicKey,
info: &[u8],
csprng: &mut impl CryptoRng,
) -> Result<(Kem::EncappedKey, AeadCtxS<A, Kdf, Kem>), HpkeError>
where
A: Aead,
Kdf: KdfTrait,
Kem: KemTrait,
{
let sender_id_keypair = mode.get_sender_id_keypair();
let (shared_secret, encapped_key) = Kem::encap_with_rng(pk_recip, sender_id_keypair, csprng)?;
let enc_ctx = Kdf::combine_secrets(mode, shared_secret, info);
Ok((encapped_key, enc_ctx.into()))
}
pub fn setup_receiver<A, Kdf, Kem>(
mode: &OpModeR<Kem>,
sk_recip: &Kem::PrivateKey,
encapped_key: &Kem::EncappedKey,
info: &[u8],
) -> Result<AeadCtxR<A, Kdf, Kem>, HpkeError>
where
A: Aead,
Kdf: KdfTrait,
Kem: KemTrait,
{
let pk_sender_id: Option<&Kem::PublicKey> = mode.get_pk_sender_id();
let shared_secret = Kem::decap(sk_recip, pk_sender_id, encapped_key)?;
let enc_ctx = Kdf::combine_secrets(mode, shared_secret, info);
Ok(enc_ctx.into())
}
#[cfg(test)]
mod test {
use super::{setup_receiver, setup_sender_with_rng};
#[cfg(feature = "hkdfsha2")]
use crate::kdf::HkdfSha256;
use crate::kem::Kem as KemTrait;
use crate::test_util::{OpModeKind, aead_ctx_eq, gen_rand_buf, new_op_mode_pair};
#[cfg(feature = "chacha")]
use crate::aead::ChaCha20Poly1305;
#[cfg(feature = "chacha")]
macro_rules! test_setup_correctness {
($test_name:ident, $aead_ty:ty, $kdf_ty:ty, $kem_ty:ty, $use_auth:expr) => {
#[test]
fn $test_name() {
type A = $aead_ty;
type Kdf = $kdf_ty;
type Kem = $kem_ty;
let mut csprng = rand::rng();
let info = b"why would you think in a million years that that would actually work";
let (sk_recip, pk_recip) = Kem::gen_keypair_with_rng(&mut csprng);
let op_mode_kinds: &[OpModeKind] = if $use_auth {
&[
OpModeKind::Base,
OpModeKind::Psk,
OpModeKind::Auth,
OpModeKind::AuthPsk,
]
} else {
&[OpModeKind::Base, OpModeKind::Psk]
};
for op_mode_kind in op_mode_kinds {
let (psk, psk_id) = (gen_rand_buf(), gen_rand_buf());
let (sender_mode, receiver_mode) =
new_op_mode_pair::<Kem>(*op_mode_kind, &psk, &psk_id);
let (encapped_key, mut aead_ctx1) = setup_sender_with_rng::<A, Kdf, Kem>(
&sender_mode,
&pk_recip,
&info[..],
&mut csprng,
)
.unwrap();
let mut aead_ctx2 = setup_receiver::<A, Kdf, Kem>(
&receiver_mode,
&sk_recip,
&encapped_key,
&info[..],
)
.unwrap();
assert!(aead_ctx_eq(&mut aead_ctx1, &mut aead_ctx2));
}
}
};
}
#[cfg(feature = "chacha")]
macro_rules! test_setup_soundness {
($test_name:ident, $aead:ty, $kdf:ty, $kem:ty, $use_auth:expr) => {
#[test]
fn $test_name() {
type A = $aead;
type Kdf = $kdf;
type Kem = $kem;
let mut csprng = rand::rng();
let info = b"why would you think in a million years that that would actually work";
let (sk_recip, pk_recip) = Kem::gen_keypair_with_rng(&mut csprng);
let (psk, psk_id) = (gen_rand_buf(), gen_rand_buf());
let (sender_mode, receiver_mode) =
new_op_mode_pair::<Kem>(OpModeKind::Base, &psk, &psk_id);
let (encapped_key, sender_ctx) = setup_sender_with_rng::<A, Kdf, Kem>(
&sender_mode,
&pk_recip,
&info[..],
&mut csprng,
)
.unwrap();
let bad_info = b"something else";
let mut receiver_ctx = setup_receiver::<_, _, Kem>(
&receiver_mode,
&sk_recip,
&encapped_key,
&bad_info[..],
)
.unwrap();
assert!(!aead_ctx_eq(&mut sender_ctx.clone(), &mut receiver_ctx));
let (bad_sk, _) = Kem::gen_keypair_with_rng(&mut csprng);
let mut aead_ctx2 =
setup_receiver::<_, _, Kem>(&receiver_mode, &bad_sk, &encapped_key, &info[..])
.unwrap();
assert!(!aead_ctx_eq(&mut sender_ctx.clone(), &mut aead_ctx2));
let (bad_encapped_key, _) = setup_sender_with_rng::<A, Kdf, Kem>(
&sender_mode,
&pk_recip,
&info[..],
&mut csprng,
)
.unwrap();
let mut aead_ctx2 = setup_receiver::<_, _, Kem>(
&receiver_mode,
&sk_recip,
&bad_encapped_key,
&info[..],
)
.unwrap();
assert!(!aead_ctx_eq(&mut sender_ctx.clone(), &mut aead_ctx2));
let mut aead_ctx2 = setup_receiver::<_, _, Kem>(
&receiver_mode,
&sk_recip,
&encapped_key,
&info[..],
)
.unwrap();
assert!(aead_ctx_eq(&mut sender_ctx.clone(), &mut aead_ctx2));
}
};
}
#[cfg(all(feature = "x25519", feature = "chacha"))]
mod x25519_tests {
use super::*;
use crate::kem::*;
test_setup_correctness!(
test_setup_correctness_x25519,
ChaCha20Poly1305,
HkdfSha256,
X25519HkdfSha256,
true
);
test_setup_soundness!(
test_setup_soundness_x25519,
ChaCha20Poly1305,
HkdfSha256,
X25519HkdfSha256,
true
);
}
#[cfg(all(feature = "nistp", feature = "chacha"))]
mod nistp_tests {
use super::*;
use crate::{
kdf::{HkdfSha384, HkdfSha512},
kem::*,
};
test_setup_correctness!(
test_setup_correctness_p256,
ChaCha20Poly1305,
HkdfSha256,
DhP256HkdfSha256,
true
);
test_setup_soundness!(
test_setup_soundness_p256,
ChaCha20Poly1305,
HkdfSha256,
DhP256HkdfSha256,
true
);
test_setup_correctness!(
test_setup_correctness_p384,
ChaCha20Poly1305,
HkdfSha384,
DhP384HkdfSha384,
true
);
test_setup_soundness!(
test_setup_soundness_p384,
ChaCha20Poly1305,
HkdfSha384,
DhP384HkdfSha384,
true
);
test_setup_correctness!(
test_setup_correctness_p521,
ChaCha20Poly1305,
HkdfSha512,
DhP521HkdfSha512,
true
);
test_setup_soundness!(
test_setup_soundness_p521,
ChaCha20Poly1305,
HkdfSha512,
DhP521HkdfSha512,
true
);
}
#[cfg(all(feature = "mlkem", feature = "chacha"))]
mod mlkem_tests {
use super::*;
use crate::{
kdf::{KdfShake128, KdfShake256},
kem::*,
};
test_setup_correctness!(
test_setup_correctness_mlkem768,
ChaCha20Poly1305,
KdfShake128,
MlKem768,
false
);
test_setup_soundness!(
test_setup_soundness_mlkem768,
ChaCha20Poly1305,
KdfShake128,
MlKem768,
false
);
test_setup_correctness!(
test_setup_correctness_mlkem1024,
ChaCha20Poly1305,
KdfShake256,
MlKem1024,
false
);
test_setup_soundness!(
test_setup_soundness_mlkem1024,
ChaCha20Poly1305,
KdfShake256,
MlKem1024,
false
);
}
#[cfg(all(feature = "mlkem", feature = "nistp", feature = "chacha"))]
mod mlkem_nistp_tests {
use super::*;
use crate::{
kdf::{KdfShake128, KdfShake256},
kem::*,
};
test_setup_correctness!(
test_setup_correctness_mlkem768p256,
ChaCha20Poly1305,
KdfShake128,
MlKem768P256,
false
);
test_setup_soundness!(
test_setup_soundness_mlkem768p256,
ChaCha20Poly1305,
KdfShake128,
MlKem768P256,
false
);
test_setup_correctness!(
test_setup_correctness_mlkem1024p384,
ChaCha20Poly1305,
KdfShake256,
MlKem1024P384,
false
);
test_setup_soundness!(
test_setup_soundness_mlkem1024p384,
ChaCha20Poly1305,
KdfShake256,
MlKem1024P384,
false
);
}
#[cfg(all(feature = "mlkem", feature = "x25519", feature = "chacha"))]
mod xwing_tests {
use super::*;
use crate::{kdf::KdfTurboShake128, kem::*};
test_setup_correctness!(
test_setup_correctness_xwing,
ChaCha20Poly1305,
KdfTurboShake128,
XWing,
false
);
test_setup_soundness!(
test_setup_soundness_xwing,
ChaCha20Poly1305,
KdfTurboShake128,
XWing,
false
);
}
}