Skip to main content

hpke_ng/
lib.rs

1//! `hpke-ng` — RFC 9180 HPKE implementation.
2//!
3//! ## Example
4//!
5//! ```
6//! use hpke_ng::*;
7//! use rand_core::{OsRng, TryRngCore as _};
8//!
9//! type Suite = Hpke<DhKemX25519HkdfSha256, HkdfSha256, ChaCha20Poly1305>;
10//!
11//! let mut os = OsRng;
12//! let mut rng = os.unwrap_mut();
13//! let (sk_r, pk_r) = DhKemX25519HkdfSha256::generate(&mut rng).unwrap();
14//! let (enc, ct) =
15//!     Suite::seal_base(&mut rng, &pk_r, b"info", b"aad", b"hello").unwrap();
16//! let pt = Suite::open_base(&enc, &sk_r, b"info", b"aad", &ct).unwrap();
17//! assert_eq!(pt, b"hello");
18//! ```
19//!
20//! See the [Readme](https://github.com/symbolicsoft/hpke-ng) for design notes
21//! and the constant-time disclosure table.
22
23#![cfg_attr(not(feature = "std"), no_std)]
24#![forbid(unsafe_code, unstable_features)]
25#![deny(
26	missing_docs,
27	rustdoc::broken_intra_doc_links,
28	rustdoc::private_intra_doc_links,
29	trivial_casts,
30	trivial_numeric_casts,
31	unused_must_use,
32	unused_import_braces,
33	unused_qualifications,
34	clippy::pedantic
35)]
36#![allow(
37	clippy::module_name_repetitions,
38	clippy::missing_errors_doc,
39	clippy::type_complexity,
40	unused_extern_crates
41)]
42
43extern crate alloc;
44
45mod aead;
46mod error;
47mod kdf;
48mod sealed;
49
50pub mod kem;
51
52pub use aead::{Aead, Aes128Gcm, Aes256Gcm, ChaCha20Poly1305, ExportOnly, SealingAead};
53pub use error::HpkeError;
54pub use kdf::{HkdfSha256, HkdfSha384, HkdfSha512, Kdf};
55pub use kem::{
56	AuthKem, Kem,
57	dh::{
58		DhKemK256HkdfSha256, DhKemP256HkdfSha256, DhKemP384HkdfSha384, DhKemP521HkdfSha512,
59		DhKemX448HkdfSha512, DhKemX25519HkdfSha256,
60	},
61};
62
63#[cfg(feature = "pq")]
64pub use kem::pq::{MlKem768, MlKem1024, XWingDraft06};
65
66mod context;
67
68pub use context::Context;
69
70use alloc::vec::Vec;
71use core::marker::PhantomData;
72
73use zeroize::Zeroizing;
74
75use crate::kdf::{labeled_expand, labeled_extract};
76
77/// HPKE configuration parameterized over a KEM, KDF, and AEAD.
78///
79/// `Hpke` is a zero-sized type. All operations are associated functions; there
80/// is no instance state and no PRNG owned by the configuration.
81///
82/// # Example
83///
84/// ```no_run
85/// use hpke_ng::*;
86/// use rand_core::{OsRng, TryRngCore as _};
87///
88/// type Suite = Hpke<DhKemX25519HkdfSha256, HkdfSha256, ChaCha20Poly1305>;
89///
90/// let mut os = OsRng;
91/// let mut rng = os.unwrap_mut();
92/// let (sk_r, pk_r) = DhKemX25519HkdfSha256::generate(&mut rng).unwrap();
93/// let (enc, ct) =
94///     Suite::seal_base(&mut rng, &pk_r, b"info", b"aad", b"hello").unwrap();
95/// let pt = Suite::open_base(&enc, &sk_r, b"info", b"aad", &ct).unwrap();
96/// assert_eq!(pt, b"hello");
97/// ```
98#[derive(Debug, Clone, Copy, Default)]
99pub struct Hpke<K: Kem, F: Kdf, A: Aead>(PhantomData<(K, F, A)>);
100
101pub(crate) mod modes {
102	pub const BASE: u8 = 0x00;
103	pub const PSK: u8 = 0x01;
104	pub const AUTH: u8 = 0x02;
105	pub const AUTH_PSK: u8 = 0x03;
106}
107
108#[inline]
109pub(crate) fn ciphersuite<K: Kem, F: Kdf, A: Aead>() -> [u8; 10] {
110	let mut s = [0u8; 10];
111	s[..4].copy_from_slice(b"HPKE");
112	s[4..6].copy_from_slice(&K::ID.to_be_bytes());
113	s[6..8].copy_from_slice(&F::ID.to_be_bytes());
114	s[8..10].copy_from_slice(&A::ID.to_be_bytes());
115	s
116}
117
118#[inline]
119fn verify_psk_inputs(mode: u8, psk: &[u8], psk_id: &[u8]) -> Result<(), HpkeError> {
120	let got_psk = !psk.is_empty();
121	let got_psk_id = !psk_id.is_empty();
122	if got_psk != got_psk_id {
123		return Err(HpkeError::InconsistentPsk);
124	}
125	if got_psk && (mode == modes::BASE || mode == modes::AUTH) {
126		return Err(HpkeError::UnnecessaryPsk);
127	}
128	if !got_psk && (mode == modes::PSK || mode == modes::AUTH_PSK) {
129		return Err(HpkeError::MissingPsk);
130	}
131	if got_psk && psk.len() < 32 {
132		return Err(HpkeError::InsecurePsk);
133	}
134	Ok(())
135}
136
137#[cfg(not(feature = "kat-internals"))]
138pub(crate) fn key_schedule<K: Kem, F: Kdf, A: Aead>(
139	mode: u8,
140	shared_secret: &[u8],
141	info: &[u8],
142	psk: &[u8],
143	psk_id: &[u8],
144) -> Result<Context<K, F, A>, HpkeError> {
145	key_schedule_inner::<K, F, A>(mode, shared_secret, info, psk, psk_id)
146}
147
148#[cfg(feature = "kat-internals")]
149#[doc(hidden)]
150pub fn key_schedule<K: Kem, F: Kdf, A: Aead>(
151	mode: u8,
152	shared_secret: &[u8],
153	info: &[u8],
154	psk: &[u8],
155	psk_id: &[u8],
156) -> Result<Context<K, F, A>, HpkeError> {
157	key_schedule_inner::<K, F, A>(mode, shared_secret, info, psk, psk_id)
158}
159
160fn key_schedule_inner<K: Kem, F: Kdf, A: Aead>(
161	mode: u8,
162	shared_secret: &[u8],
163	info: &[u8],
164	psk: &[u8],
165	psk_id: &[u8],
166) -> Result<Context<K, F, A>, HpkeError> {
167	verify_psk_inputs(mode, psk, psk_id)?;
168	let suite = ciphersuite::<K, F, A>();
169	let psk_id_hash = labeled_extract::<F>(&[], &suite, b"psk_id_hash", psk_id);
170	let info_hash = labeled_extract::<F>(&[], &suite, b"info_hash", info);
171
172	let mut ks_ctx = Vec::with_capacity(1 + psk_id_hash.len() + info_hash.len());
173	ks_ctx.push(mode);
174	ks_ctx.extend_from_slice(&psk_id_hash);
175	ks_ctx.extend_from_slice(&info_hash);
176
177	let secret = Zeroizing::new(labeled_extract::<F>(shared_secret, &suite, b"secret", psk));
178	let key = labeled_expand::<F>(&secret, &suite, b"key", &ks_ctx, A::KEY_LEN)?;
179	let base_nonce = labeled_expand::<F>(&secret, &suite, b"base_nonce", &ks_ctx, A::NONCE_LEN)?;
180	let exporter_secret = labeled_expand::<F>(&secret, &suite, b"exp", &ks_ctx, F::HASH_LEN)?;
181
182	Ok(Context::new(key, base_nonce, exporter_secret))
183}
184
185#[cfg(test)]
186mod ks_tests {
187	use super::*;
188
189	#[test]
190	fn psk_validation_inconsistent() {
191		let r = verify_psk_inputs(modes::PSK, b"", b"some_id");
192		assert_eq!(r, Err(HpkeError::InconsistentPsk));
193		let r = verify_psk_inputs(modes::PSK, &[0u8; 32], b"");
194		assert_eq!(r, Err(HpkeError::InconsistentPsk));
195	}
196
197	#[test]
198	fn psk_validation_missing() {
199		let r = verify_psk_inputs(modes::PSK, b"", b"");
200		assert_eq!(r, Err(HpkeError::MissingPsk));
201	}
202
203	#[test]
204	fn psk_validation_unnecessary() {
205		let r = verify_psk_inputs(modes::BASE, &[0u8; 32], b"id");
206		assert_eq!(r, Err(HpkeError::UnnecessaryPsk));
207	}
208
209	#[test]
210	fn psk_validation_too_short() {
211		let r = verify_psk_inputs(modes::PSK, b"too short", b"id");
212		assert_eq!(r, Err(HpkeError::InsecurePsk));
213	}
214
215	#[test]
216	fn psk_validation_ok() {
217		assert!(verify_psk_inputs(modes::BASE, b"", b"").is_ok());
218		assert!(verify_psk_inputs(modes::PSK, &[0u8; 32], b"id").is_ok());
219	}
220}
221
222use rand_core::{CryptoRng, RngCore};
223
224impl<K: Kem, F: Kdf, A: Aead> Hpke<K, F, A> {
225	/// `SetupBaseS` (RFC 9180 §5.1.1).
226	pub fn setup_sender_base<R: CryptoRng + RngCore>(
227		rng: &mut R,
228		pk_r: &K::PublicKey,
229		info: &[u8],
230	) -> Result<(K::EncappedKey, Context<K, F, A>), HpkeError> {
231		let (ss, enc) = K::encap(rng, pk_r)?;
232		let ctx = key_schedule::<K, F, A>(modes::BASE, ss.as_ref(), info, &[], &[])?;
233		Ok((enc, ctx))
234	}
235
236	/// `SetupBaseR` (RFC 9180 §5.1.1).
237	pub fn setup_receiver_base(
238		enc: &K::EncappedKey,
239		sk_r: &K::PrivateKey,
240		info: &[u8],
241	) -> Result<Context<K, F, A>, HpkeError> {
242		let ss = K::decap(enc, sk_r)?;
243		key_schedule::<K, F, A>(modes::BASE, ss.as_ref(), info, &[], &[])
244	}
245
246	/// `SetupPSKS` (RFC 9180 §5.1.2).
247	///
248	/// `psk` MUST be at least 32 bytes of high-entropy random data. Length is
249	/// enforced; entropy is the caller's responsibility — see
250	/// [`HpkeError::InsecurePsk`].
251	pub fn setup_sender_psk<R: CryptoRng + RngCore>(
252		rng: &mut R,
253		pk_r: &K::PublicKey,
254		info: &[u8],
255		psk: &[u8],
256		psk_id: &[u8],
257	) -> Result<(K::EncappedKey, Context<K, F, A>), HpkeError> {
258		let (ss, enc) = K::encap(rng, pk_r)?;
259		let ctx = key_schedule::<K, F, A>(modes::PSK, ss.as_ref(), info, psk, psk_id)?;
260		Ok((enc, ctx))
261	}
262
263	/// `SetupPSKR` (RFC 9180 §5.1.2).
264	///
265	/// `psk` MUST be at least 32 bytes of high-entropy random data. Length is
266	/// enforced; entropy is the caller's responsibility — see
267	/// [`HpkeError::InsecurePsk`].
268	pub fn setup_receiver_psk(
269		enc: &K::EncappedKey,
270		sk_r: &K::PrivateKey,
271		info: &[u8],
272		psk: &[u8],
273		psk_id: &[u8],
274	) -> Result<Context<K, F, A>, HpkeError> {
275		let ss = K::decap(enc, sk_r)?;
276		key_schedule::<K, F, A>(modes::PSK, ss.as_ref(), info, psk, psk_id)
277	}
278}
279
280impl<K: Kem, F: Kdf, A: SealingAead> Hpke<K, F, A> {
281	/// Single-shot Base-mode encrypt (RFC 9180 §6.1).
282	pub fn seal_base<R: CryptoRng + RngCore>(
283		rng: &mut R,
284		pk_r: &K::PublicKey,
285		info: &[u8],
286		aad: &[u8],
287		pt: &[u8],
288	) -> Result<(K::EncappedKey, Vec<u8>), HpkeError> {
289		let (enc, mut ctx) = Self::setup_sender_base(rng, pk_r, info)?;
290		let ct = ctx.seal(aad, pt)?;
291		Ok((enc, ct))
292	}
293
294	/// Single-shot Base-mode decrypt (RFC 9180 §6.1).
295	pub fn open_base(
296		enc: &K::EncappedKey,
297		sk_r: &K::PrivateKey,
298		info: &[u8],
299		aad: &[u8],
300		ct: &[u8],
301	) -> Result<Vec<u8>, HpkeError> {
302		let mut ctx = Self::setup_receiver_base(enc, sk_r, info)?;
303		ctx.open(aad, ct)
304	}
305
306	/// Single-shot Psk-mode encrypt (RFC 9180 §6.1).
307	#[allow(clippy::too_many_arguments)]
308	pub fn seal_psk<R: CryptoRng + RngCore>(
309		rng: &mut R,
310		pk_r: &K::PublicKey,
311		info: &[u8],
312		aad: &[u8],
313		pt: &[u8],
314		psk: &[u8],
315		psk_id: &[u8],
316	) -> Result<(K::EncappedKey, Vec<u8>), HpkeError> {
317		let (enc, mut ctx) = Self::setup_sender_psk(rng, pk_r, info, psk, psk_id)?;
318		let ct = ctx.seal(aad, pt)?;
319		Ok((enc, ct))
320	}
321
322	/// Single-shot Psk-mode decrypt (RFC 9180 §6.1).
323	#[allow(clippy::too_many_arguments)]
324	pub fn open_psk(
325		enc: &K::EncappedKey,
326		sk_r: &K::PrivateKey,
327		info: &[u8],
328		aad: &[u8],
329		ct: &[u8],
330		psk: &[u8],
331		psk_id: &[u8],
332	) -> Result<Vec<u8>, HpkeError> {
333		let mut ctx = Self::setup_receiver_psk(enc, sk_r, info, psk, psk_id)?;
334		ctx.open(aad, ct)
335	}
336}
337
338impl<K: AuthKem, F: Kdf, A: SealingAead> Hpke<K, F, A> {
339	/// Single-shot Auth-mode encrypt (RFC 9180 §6.1).
340	pub fn seal_auth<R: CryptoRng + RngCore>(
341		rng: &mut R,
342		pk_r: &K::PublicKey,
343		info: &[u8],
344		aad: &[u8],
345		pt: &[u8],
346		sk_s: &K::PrivateKey,
347	) -> Result<(K::EncappedKey, Vec<u8>), HpkeError> {
348		let (enc, mut ctx) = Self::setup_sender_auth(rng, pk_r, info, sk_s)?;
349		let ct = ctx.seal(aad, pt)?;
350		Ok((enc, ct))
351	}
352
353	/// Single-shot Auth-mode decrypt (RFC 9180 §6.1).
354	pub fn open_auth(
355		enc: &K::EncappedKey,
356		sk_r: &K::PrivateKey,
357		info: &[u8],
358		aad: &[u8],
359		ct: &[u8],
360		pk_s: &K::PublicKey,
361	) -> Result<Vec<u8>, HpkeError> {
362		let mut ctx = Self::setup_receiver_auth(enc, sk_r, info, pk_s)?;
363		ctx.open(aad, ct)
364	}
365
366	/// Single-shot AuthPsk-mode encrypt (RFC 9180 §6.1).
367	#[allow(clippy::too_many_arguments)]
368	pub fn seal_auth_psk<R: CryptoRng + RngCore>(
369		rng: &mut R,
370		pk_r: &K::PublicKey,
371		info: &[u8],
372		aad: &[u8],
373		pt: &[u8],
374		psk: &[u8],
375		psk_id: &[u8],
376		sk_s: &K::PrivateKey,
377	) -> Result<(K::EncappedKey, Vec<u8>), HpkeError> {
378		let (enc, mut ctx) = Self::setup_sender_auth_psk(rng, pk_r, info, psk, psk_id, sk_s)?;
379		let ct = ctx.seal(aad, pt)?;
380		Ok((enc, ct))
381	}
382
383	/// Single-shot AuthPsk-mode decrypt (RFC 9180 §6.1).
384	#[allow(clippy::too_many_arguments)]
385	pub fn open_auth_psk(
386		enc: &K::EncappedKey,
387		sk_r: &K::PrivateKey,
388		info: &[u8],
389		aad: &[u8],
390		ct: &[u8],
391		psk: &[u8],
392		psk_id: &[u8],
393		pk_s: &K::PublicKey,
394	) -> Result<Vec<u8>, HpkeError> {
395		let mut ctx = Self::setup_receiver_auth_psk(enc, sk_r, info, psk, psk_id, pk_s)?;
396		ctx.open(aad, ct)
397	}
398}
399
400impl<K: Kem, F: Kdf, A: Aead> Hpke<K, F, A> {
401	/// Sender-side single-shot export — Base mode (RFC 9180 §6.2).
402	pub fn send_export_base<R: CryptoRng + RngCore>(
403		rng: &mut R,
404		pk_r: &K::PublicKey,
405		info: &[u8],
406		exporter_context: &[u8],
407		length: usize,
408	) -> Result<(K::EncappedKey, Vec<u8>), HpkeError> {
409		let (enc, ctx) = Self::setup_sender_base(rng, pk_r, info)?;
410		Ok((enc, ctx.export(exporter_context, length)?))
411	}
412
413	/// Receiver-side single-shot export — Base mode.
414	pub fn receiver_export_base(
415		enc: &K::EncappedKey,
416		sk_r: &K::PrivateKey,
417		info: &[u8],
418		exporter_context: &[u8],
419		length: usize,
420	) -> Result<Vec<u8>, HpkeError> {
421		let ctx = Self::setup_receiver_base(enc, sk_r, info)?;
422		ctx.export(exporter_context, length)
423	}
424
425	/// Sender-side single-shot export — Psk mode.
426	#[allow(clippy::too_many_arguments)]
427	pub fn send_export_psk<R: CryptoRng + RngCore>(
428		rng: &mut R,
429		pk_r: &K::PublicKey,
430		info: &[u8],
431		psk: &[u8],
432		psk_id: &[u8],
433		exporter_context: &[u8],
434		length: usize,
435	) -> Result<(K::EncappedKey, Vec<u8>), HpkeError> {
436		let (enc, ctx) = Self::setup_sender_psk(rng, pk_r, info, psk, psk_id)?;
437		Ok((enc, ctx.export(exporter_context, length)?))
438	}
439
440	/// Receiver-side single-shot export — Psk mode.
441	#[allow(clippy::too_many_arguments)]
442	pub fn receiver_export_psk(
443		enc: &K::EncappedKey,
444		sk_r: &K::PrivateKey,
445		info: &[u8],
446		psk: &[u8],
447		psk_id: &[u8],
448		exporter_context: &[u8],
449		length: usize,
450	) -> Result<Vec<u8>, HpkeError> {
451		let ctx = Self::setup_receiver_psk(enc, sk_r, info, psk, psk_id)?;
452		ctx.export(exporter_context, length)
453	}
454}
455
456impl<K: AuthKem, F: Kdf, A: Aead> Hpke<K, F, A> {
457	/// Sender-side single-shot export — Auth mode.
458	pub fn send_export_auth<R: CryptoRng + RngCore>(
459		rng: &mut R,
460		pk_r: &K::PublicKey,
461		info: &[u8],
462		sk_s: &K::PrivateKey,
463		exporter_context: &[u8],
464		length: usize,
465	) -> Result<(K::EncappedKey, Vec<u8>), HpkeError> {
466		let (enc, ctx) = Self::setup_sender_auth(rng, pk_r, info, sk_s)?;
467		Ok((enc, ctx.export(exporter_context, length)?))
468	}
469
470	/// Receiver-side single-shot export — Auth mode.
471	pub fn receiver_export_auth(
472		enc: &K::EncappedKey,
473		sk_r: &K::PrivateKey,
474		info: &[u8],
475		pk_s: &K::PublicKey,
476		exporter_context: &[u8],
477		length: usize,
478	) -> Result<Vec<u8>, HpkeError> {
479		let ctx = Self::setup_receiver_auth(enc, sk_r, info, pk_s)?;
480		ctx.export(exporter_context, length)
481	}
482
483	/// Sender-side single-shot export — `AuthPsk` mode.
484	#[allow(clippy::too_many_arguments)]
485	pub fn send_export_auth_psk<R: CryptoRng + RngCore>(
486		rng: &mut R,
487		pk_r: &K::PublicKey,
488		info: &[u8],
489		psk: &[u8],
490		psk_id: &[u8],
491		sk_s: &K::PrivateKey,
492		exporter_context: &[u8],
493		length: usize,
494	) -> Result<(K::EncappedKey, Vec<u8>), HpkeError> {
495		let (enc, ctx) = Self::setup_sender_auth_psk(rng, pk_r, info, psk, psk_id, sk_s)?;
496		Ok((enc, ctx.export(exporter_context, length)?))
497	}
498
499	/// Receiver-side single-shot export — `AuthPsk` mode.
500	#[allow(clippy::too_many_arguments)]
501	pub fn receiver_export_auth_psk(
502		enc: &K::EncappedKey,
503		sk_r: &K::PrivateKey,
504		info: &[u8],
505		psk: &[u8],
506		psk_id: &[u8],
507		pk_s: &K::PublicKey,
508		exporter_context: &[u8],
509		length: usize,
510	) -> Result<Vec<u8>, HpkeError> {
511		let ctx = Self::setup_receiver_auth_psk(enc, sk_r, info, psk, psk_id, pk_s)?;
512		ctx.export(exporter_context, length)
513	}
514}
515
516impl<K: AuthKem, F: Kdf, A: Aead> Hpke<K, F, A> {
517	/// `SetupAuthS` (RFC 9180 §5.1.3).
518	pub fn setup_sender_auth<R: CryptoRng + RngCore>(
519		rng: &mut R,
520		pk_r: &K::PublicKey,
521		info: &[u8],
522		sk_s: &K::PrivateKey,
523	) -> Result<(K::EncappedKey, Context<K, F, A>), HpkeError> {
524		let (ss, enc) = K::auth_encap(rng, pk_r, sk_s)?;
525		let ctx = key_schedule::<K, F, A>(modes::AUTH, ss.as_ref(), info, &[], &[])?;
526		Ok((enc, ctx))
527	}
528
529	/// `SetupAuthR` (RFC 9180 §5.1.3).
530	pub fn setup_receiver_auth(
531		enc: &K::EncappedKey,
532		sk_r: &K::PrivateKey,
533		info: &[u8],
534		pk_s: &K::PublicKey,
535	) -> Result<Context<K, F, A>, HpkeError> {
536		let ss = K::auth_decap(enc, sk_r, pk_s)?;
537		key_schedule::<K, F, A>(modes::AUTH, ss.as_ref(), info, &[], &[])
538	}
539
540	/// `SetupAuthPSKS` (RFC 9180 §5.1.4).
541	///
542	/// `psk` MUST be at least 32 bytes of high-entropy random data. Length is
543	/// enforced; entropy is the caller's responsibility — see
544	/// [`HpkeError::InsecurePsk`].
545	pub fn setup_sender_auth_psk<R: CryptoRng + RngCore>(
546		rng: &mut R,
547		pk_r: &K::PublicKey,
548		info: &[u8],
549		psk: &[u8],
550		psk_id: &[u8],
551		sk_s: &K::PrivateKey,
552	) -> Result<(K::EncappedKey, Context<K, F, A>), HpkeError> {
553		let (ss, enc) = K::auth_encap(rng, pk_r, sk_s)?;
554		let ctx = key_schedule::<K, F, A>(modes::AUTH_PSK, ss.as_ref(), info, psk, psk_id)?;
555		Ok((enc, ctx))
556	}
557
558	/// `SetupAuthPSKR` (RFC 9180 §5.1.4).
559	///
560	/// `psk` MUST be at least 32 bytes of high-entropy random data. Length is
561	/// enforced; entropy is the caller's responsibility — see
562	/// [`HpkeError::InsecurePsk`].
563	pub fn setup_receiver_auth_psk(
564		enc: &K::EncappedKey,
565		sk_r: &K::PrivateKey,
566		info: &[u8],
567		psk: &[u8],
568		psk_id: &[u8],
569		pk_s: &K::PublicKey,
570	) -> Result<Context<K, F, A>, HpkeError> {
571		let ss = K::auth_decap(enc, sk_r, pk_s)?;
572		key_schedule::<K, F, A>(modes::AUTH_PSK, ss.as_ref(), info, psk, psk_id)
573	}
574}
575
576#[cfg(feature = "kat-internals")]
577#[doc(hidden)]
578pub mod __test_only {
579	pub use crate::key_schedule;
580}
581
582#[cfg(test)]
583mod hpke_tests {
584	use super::*;
585	use rand_core::{OsRng, TryRngCore as _};
586
587	type Suite = Hpke<DhKemX25519HkdfSha256, HkdfSha256, ChaCha20Poly1305>;
588
589	#[test]
590	fn setup_base_roundtrip_x25519_chacha() {
591		let mut os_rng = OsRng;
592		let mut rng = os_rng.unwrap_mut();
593		let (sk_r, pk_r) = DhKemX25519HkdfSha256::generate(&mut rng).unwrap();
594		let (enc, mut sender) = Suite::setup_sender_base(&mut rng, &pk_r, b"info").unwrap();
595		let mut receiver = Suite::setup_receiver_base(&enc, &sk_r, b"info").unwrap();
596
597		let ct = sender.seal(b"aad", b"plaintext").unwrap();
598		let pt = receiver.open(b"aad", &ct).unwrap();
599		assert_eq!(pt, b"plaintext");
600	}
601
602	#[test]
603	fn setup_psk_roundtrip() {
604		let mut os_rng = OsRng;
605		let mut rng = os_rng.unwrap_mut();
606		let (sk_r, pk_r) = DhKemX25519HkdfSha256::generate(&mut rng).unwrap();
607		let psk = [0xAAu8; 32];
608		let psk_id = b"id";
609		let (enc, mut s) = Suite::setup_sender_psk(&mut rng, &pk_r, b"info", &psk, psk_id).unwrap();
610		let mut r = Suite::setup_receiver_psk(&enc, &sk_r, b"info", &psk, psk_id).unwrap();
611		let ct = s.seal(b"aad", b"hi").unwrap();
612		assert_eq!(r.open(b"aad", &ct).unwrap(), b"hi");
613	}
614
615	#[test]
616	fn setup_auth_roundtrip() {
617		let mut os_rng = OsRng;
618		let mut rng = os_rng.unwrap_mut();
619		let (sk_r, pk_r) = DhKemX25519HkdfSha256::generate(&mut rng).unwrap();
620		let (sk_s, pk_s) = DhKemX25519HkdfSha256::generate(&mut rng).unwrap();
621		let (enc, mut s) = Suite::setup_sender_auth(&mut rng, &pk_r, b"info", &sk_s).unwrap();
622		let mut r = Suite::setup_receiver_auth(&enc, &sk_r, b"info", &pk_s).unwrap();
623		let ct = s.seal(b"aad", b"auth").unwrap();
624		assert_eq!(r.open(b"aad", &ct).unwrap(), b"auth");
625	}
626
627	#[test]
628	fn setup_auth_psk_roundtrip() {
629		let mut os_rng = OsRng;
630		let mut rng = os_rng.unwrap_mut();
631		let (sk_r, pk_r) = DhKemX25519HkdfSha256::generate(&mut rng).unwrap();
632		let (sk_s, pk_s) = DhKemX25519HkdfSha256::generate(&mut rng).unwrap();
633		let psk = [0xBBu8; 32];
634		let (enc, mut s) =
635			Suite::setup_sender_auth_psk(&mut rng, &pk_r, b"info", &psk, b"id", &sk_s).unwrap();
636		let mut r =
637			Suite::setup_receiver_auth_psk(&enc, &sk_r, b"info", &psk, b"id", &pk_s).unwrap();
638		let ct = s.seal(b"aad", b"auth-psk").unwrap();
639		assert_eq!(r.open(b"aad", &ct).unwrap(), b"auth-psk");
640	}
641
642	#[test]
643	fn single_shot_base_roundtrip() {
644		let mut os_rng = OsRng;
645		let mut rng = os_rng.unwrap_mut();
646		let (sk_r, pk_r) = DhKemX25519HkdfSha256::generate(&mut rng).unwrap();
647		let (enc, ct) = Suite::seal_base(&mut rng, &pk_r, b"info", b"aad", b"hi").unwrap();
648		assert_eq!(
649			Suite::open_base(&enc, &sk_r, b"info", b"aad", &ct).unwrap(),
650			b"hi"
651		);
652	}
653
654	#[test]
655	fn single_shot_psk_roundtrip() {
656		let mut os_rng = OsRng;
657		let mut rng = os_rng.unwrap_mut();
658		let (sk_r, pk_r) = DhKemX25519HkdfSha256::generate(&mut rng).unwrap();
659		let psk = [0u8; 32];
660		let (enc, ct) = Suite::seal_psk(&mut rng, &pk_r, b"i", b"a", b"p", &psk, b"id").unwrap();
661		assert_eq!(
662			Suite::open_psk(&enc, &sk_r, b"i", b"a", &ct, &psk, b"id").unwrap(),
663			b"p",
664		);
665	}
666
667	#[test]
668	fn single_shot_auth_roundtrip() {
669		let mut os_rng = OsRng;
670		let mut rng = os_rng.unwrap_mut();
671		let (sk_r, pk_r) = DhKemX25519HkdfSha256::generate(&mut rng).unwrap();
672		let (sk_s, pk_s) = DhKemX25519HkdfSha256::generate(&mut rng).unwrap();
673		let (enc, ct) = Suite::seal_auth(&mut rng, &pk_r, b"i", b"a", b"p", &sk_s).unwrap();
674		assert_eq!(
675			Suite::open_auth(&enc, &sk_r, b"i", b"a", &ct, &pk_s).unwrap(),
676			b"p",
677		);
678	}
679
680	#[test]
681	fn single_shot_auth_psk_roundtrip() {
682		let mut os_rng = OsRng;
683		let mut rng = os_rng.unwrap_mut();
684		let (sk_r, pk_r) = DhKemX25519HkdfSha256::generate(&mut rng).unwrap();
685		let (sk_s, pk_s) = DhKemX25519HkdfSha256::generate(&mut rng).unwrap();
686		let psk = [0u8; 32];
687		let (enc, ct) =
688			Suite::seal_auth_psk(&mut rng, &pk_r, b"i", b"a", b"p", &psk, b"id", &sk_s).unwrap();
689		assert_eq!(
690			Suite::open_auth_psk(&enc, &sk_r, b"i", b"a", &ct, &psk, b"id", &pk_s).unwrap(),
691			b"p",
692		);
693	}
694
695	#[test]
696	fn export_sender_receiver_match_base() {
697		let mut os_rng = OsRng;
698		let mut rng = os_rng.unwrap_mut();
699		let (sk_r, pk_r) = DhKemX25519HkdfSha256::generate(&mut rng).unwrap();
700		let (enc, sender_secret) =
701			Suite::send_export_base(&mut rng, &pk_r, b"info", b"ctx", 32).unwrap();
702		let receiver_secret =
703			Suite::receiver_export_base(&enc, &sk_r, b"info", b"ctx", 32).unwrap();
704		assert_eq!(sender_secret, receiver_secret);
705		assert_eq!(sender_secret.len(), 32);
706	}
707
708	/// `ExportOnly` suites have export methods but `seal_*` / `open_*` are
709	/// uninstantiable because `A: SealingAead` is not satisfied — verified by
710	/// the fact that this function compiles.
711	#[test]
712	fn export_only_suite_compiles() {
713		type ExportSuite = Hpke<DhKemX25519HkdfSha256, HkdfSha256, ExportOnly>;
714		let mut os_rng = OsRng;
715		let mut rng = os_rng.unwrap_mut();
716		let (sk_r, pk_r) = DhKemX25519HkdfSha256::generate(&mut rng).unwrap();
717		let (enc, sec) =
718			ExportSuite::send_export_base(&mut rng, &pk_r, b"info", b"ctx", 32).unwrap();
719		let recv = ExportSuite::receiver_export_base(&enc, &sk_r, b"info", b"ctx", 32).unwrap();
720		assert_eq!(sec, recv);
721	}
722
723	#[test]
724	fn export_sender_receiver_match_psk() {
725		let mut os_rng = OsRng;
726		let mut rng = os_rng.unwrap_mut();
727		let (sk_r, pk_r) = DhKemX25519HkdfSha256::generate(&mut rng).unwrap();
728		let psk = [0x33u8; 32];
729		let (enc, sec) =
730			Suite::send_export_psk(&mut rng, &pk_r, b"info", &psk, b"id", b"ctx", 64).unwrap();
731		let recv =
732			Suite::receiver_export_psk(&enc, &sk_r, b"info", &psk, b"id", b"ctx", 64).unwrap();
733		assert_eq!(sec, recv);
734		assert_eq!(sec.len(), 64);
735	}
736
737	#[test]
738	fn export_sender_receiver_match_auth() {
739		let mut os_rng = OsRng;
740		let mut rng = os_rng.unwrap_mut();
741		let (sk_r, pk_r) = DhKemX25519HkdfSha256::generate(&mut rng).unwrap();
742		let (sk_s, pk_s) = DhKemX25519HkdfSha256::generate(&mut rng).unwrap();
743		let (enc, sec) =
744			Suite::send_export_auth(&mut rng, &pk_r, b"info", &sk_s, b"ctx", 48).unwrap();
745		let recv = Suite::receiver_export_auth(&enc, &sk_r, b"info", &pk_s, b"ctx", 48).unwrap();
746		assert_eq!(sec, recv);
747		assert_eq!(sec.len(), 48);
748	}
749
750	#[test]
751	fn export_sender_receiver_match_auth_psk() {
752		let mut os_rng = OsRng;
753		let mut rng = os_rng.unwrap_mut();
754		let (sk_r, pk_r) = DhKemX25519HkdfSha256::generate(&mut rng).unwrap();
755		let (sk_s, pk_s) = DhKemX25519HkdfSha256::generate(&mut rng).unwrap();
756		let psk = [0x77u8; 32];
757		let (enc, sec) =
758			Suite::send_export_auth_psk(&mut rng, &pk_r, b"info", &psk, b"id", &sk_s, b"ctx", 32)
759				.unwrap();
760		let recv =
761			Suite::receiver_export_auth_psk(&enc, &sk_r, b"info", &psk, b"id", &pk_s, b"ctx", 32)
762				.unwrap();
763		assert_eq!(sec, recv);
764		assert_eq!(sec.len(), 32);
765	}
766}