hpke_core/
lib.rs

1#![doc = include_str!("../README.md")]
2#![no_std]
3#![forbid(unsafe_code, unused_must_use, unstable_features)]
4#![deny(
5    trivial_casts,
6    trivial_numeric_casts,
7    missing_docs,
8    unused_import_braces,
9    unused_extern_crates,
10    unused_qualifications
11)]
12#![allow(clippy::must_use_candidate)]
13
14pub mod error;
15pub mod kem;
16
17extern crate alloc;
18#[cfg(feature = "std")]
19extern crate std;
20
21use alloc::vec::Vec;
22use core::fmt;
23use core::marker::PhantomData;
24
25pub use hpke_crypto::*;
26
27pub use crate::error::Error;
28
29/// The HPKE configuration.
30#[derive(Debug, Clone, Copy)]
31pub struct Hpke<C> {
32    /// The HPKE ciphersuite in use.
33    cipher_suite: HpkeCipherSuite,
34
35    /// The crypto backend.
36    _crypto_backend: PhantomData<C>,
37}
38
39impl<C: Crypto> Hpke<C> {
40    /// Create a new HPKE configuration with the given ciphersuite.
41    pub fn prepare(cipher_suite: HpkeCipherSuite) -> Self {
42        Self {
43            cipher_suite,
44            _crypto_backend: PhantomData,
45        }
46    }
47
48    #[allow(clippy::too_many_arguments)]
49    /// 5.1. Creating the Encryption Context
50    ///
51    /// This is a convenience function that wraps all four setup functions.
52    ///
53    /// See [RFC 9180, Section 5.1] for details.
54    ///
55    /// # Errors
56    ///
57    /// Various errors may occur during the key encapsulation or decapsulation
58    /// process, or if the provided PSK does not meet security requirements.
59    ///
60    /// [RFC 9180, Section 5.1]: https://www.rfc-editor.org/rfc/rfc9180.html#section-5.1
61    pub fn setup_s(
62        &self,
63        crypto_backend: C,
64        mode: HpkeMode,
65        pk_r: HpkePublicKeyRef<'_>,
66        info: &[u8],
67        psk: Option<&[u8]>,
68        psk_id: Option<&[u8]>,
69        sk_s: Option<HpkePrivateKeyRef<'_>>,
70    ) -> Result<(EncapsulatedSecret, Context<C, Sender>), Error> {
71        match mode {
72            HpkeMode::Base => self.setup_base_s(crypto_backend, pk_r, info),
73            HpkeMode::Psk => self.setup_psk_s(
74                crypto_backend,
75                pk_r,
76                info,
77                psk.ok_or_else(|| Error::InvalidInput("For PSK mode, must provide psk"))?,
78                psk_id.ok_or_else(|| Error::InvalidInput("For PSK mode, must provide psk_id"))?,
79            ),
80            HpkeMode::Auth => self.setup_auth_s(
81                crypto_backend,
82                pk_r,
83                info,
84                sk_s.ok_or_else(|| Error::InvalidInput("For Auth mode, must provide sk_s"))?,
85            ),
86            HpkeMode::AuthPsk => self.setup_auth_psk_s(
87                crypto_backend,
88                pk_r,
89                info,
90                psk.ok_or_else(|| Error::InvalidInput("For PSK mode, must provide psk"))?,
91                psk_id.ok_or_else(|| Error::InvalidInput("For PSK mode, must provide psk_id"))?,
92                sk_s.ok_or_else(|| Error::InvalidInput("For PSK mode, must provide sk_s"))?,
93            ),
94        }
95    }
96
97    #[allow(clippy::too_many_arguments)]
98    /// 5.1. Creating the Encryption Context
99    ///
100    /// This is a convenience function that wraps all four setup functions.
101    ///
102    /// See [RFC 9180, Section 5.1] for details.
103    ///
104    /// # Errors
105    ///
106    /// Various errors may occur during the key encapsulation or decapsulation
107    /// process, or if the provided PSK does not meet security requirements.
108    ///
109    /// [RFC 9180, Section 5.1]: https://www.rfc-editor.org/rfc/rfc9180.html#section-5.1
110    pub fn setup_r(
111        &self,
112        crypto_backend: C,
113        mode: HpkeMode,
114        enc: EncapsulatedSecretRef<'_>,
115        sk_r: HpkePrivateKeyRef<'_>,
116        info: &[u8],
117        psk: Option<&[u8]>,
118        psk_id: Option<&[u8]>,
119        pk_s: Option<HpkePublicKeyRef<'_>>,
120    ) -> Result<Context<C, Recipient>, Error> {
121        match mode {
122            HpkeMode::Base => self.setup_base_r(crypto_backend, enc, sk_r, info),
123            HpkeMode::Psk => self.setup_psk_r(
124                crypto_backend,
125                enc,
126                sk_r,
127                info,
128                psk.ok_or_else(|| Error::InvalidInput("For PSK mode, must provide psk"))?,
129                psk_id.ok_or_else(|| Error::InvalidInput("For PSK mode, must provide psk_id"))?,
130            ),
131            HpkeMode::Auth => self.setup_auth_r(
132                crypto_backend,
133                enc,
134                sk_r,
135                info,
136                pk_s.ok_or_else(|| Error::InvalidInput("For Auth mode, must provide pk_s"))?,
137            ),
138            HpkeMode::AuthPsk => self.setup_auth_psk_r(
139                crypto_backend,
140                enc,
141                sk_r,
142                info,
143                psk.ok_or_else(|| Error::InvalidInput("For PSK mode, must provide psk"))?,
144                psk_id.ok_or_else(|| Error::InvalidInput("For PSK mode, must provide psk_id"))?,
145                pk_s.ok_or_else(|| Error::InvalidInput("For PSK mode, must provide pk_s"))?,
146            ),
147        }
148    }
149
150    /// 5.1.1. Encryption to a Public Key
151    ///
152    /// The most basic function of an HPKE scheme is to enable encryption to the
153    /// holder of a given KEM private key. The `SetupBaseS()` and `SetupBaseR()`
154    /// procedures establish contexts that can be used to encrypt and decrypt,
155    /// respectively, for a given private key.
156    ///
157    /// The KEM shared secret is combined via the KDF with information
158    /// describing the key exchange, as well as the explicit `info` parameter
159    /// provided by the caller.
160    ///
161    /// The parameter `pkR` is a public key, and `enc` is an encapsulated KEM
162    /// shared secret.
163    ///
164    /// ```no_run
165    /// def SetupBaseS(pkR, info):
166    ///   shared_secret, enc = Encap(pkR)
167    ///   return enc, KeyScheduleS(mode_base, shared_secret, info,
168    ///                            default_psk, default_psk_id)
169    /// ```
170    ///
171    /// See [RFC 9180, Section 5.1.1] for details.
172    ///
173    /// # Errors
174    ///
175    /// Various errors may occur during the key encapsulation or decapsulation
176    /// process.
177    ///
178    /// [RFC 9180, Section 5.1.1]: https://www.rfc-editor.org/rfc/rfc9180.html#section-5.1.1
179    pub fn setup_base_s(
180        &self,
181        mut crypto_backend: C,
182        pk_r: HpkePublicKeyRef<'_>,
183        info: &[u8],
184    ) -> Result<(EncapsulatedSecret, Context<C, Sender>), Error> {
185        let (shared_secret, enc) = kem::encap(&mut crypto_backend, self.cipher_suite.kem_id, pk_r)?;
186
187        let context = self.key_schedule(
188            crypto_backend,
189            HpkeMode::Base,
190            &shared_secret,
191            info,
192            &[],
193            &[],
194        )?;
195
196        Ok((enc, context))
197    }
198
199    /// 5.1.1. Encryption to a Public Key
200    ///
201    /// The most basic function of an HPKE scheme is to enable encryption to the
202    /// holder of a given KEM private key. The `SetupBaseS()` and `SetupBaseR()`
203    /// procedures establish contexts that can be used to encrypt and decrypt,
204    /// respectively, for a given private key.
205    ///
206    /// The KEM shared secret is combined via the KDF with information
207    /// describing the key exchange, as well as the explicit `info` parameter
208    /// provided by the caller.
209    ///
210    /// The parameter `pkR` is a public key, and `enc` is an encapsulated KEM
211    /// shared secret.
212    ///
213    /// ```no_run
214    /// def SetupBaseR(enc, skR, info):
215    ///   shared_secret = Decap(enc, skR)
216    ///   return KeyScheduleR(mode_base, shared_secret, info,
217    ///                       default_psk, default_psk_id)
218    /// ```
219    ///
220    /// See [RFC 9180, Section 5.1.1] for details.
221    ///
222    /// # Errors
223    ///
224    /// Various errors may occur during the key encapsulation or decapsulation
225    /// process.
226    ///
227    /// [RFC 9180, Section 5.1.1]: https://www.rfc-editor.org/rfc/rfc9180.html#section-5.1.1
228    pub fn setup_base_r(
229        &self,
230        mut crypto_backend: C,
231        enc: EncapsulatedSecretRef<'_>,
232        sk_r: HpkePrivateKeyRef<'_>,
233        info: &[u8],
234    ) -> Result<Context<C, Recipient>, Error> {
235        let shared_secret = kem::decap(&mut crypto_backend, self.cipher_suite.kem_id, enc, sk_r)?;
236
237        self.key_schedule(
238            crypto_backend,
239            HpkeMode::Base,
240            shared_secret.as_ref(),
241            info,
242            &[],
243            &[],
244        )
245    }
246
247    /// 5.1.2. Authentication Using a Pre-Shared Key
248    ///
249    /// This variant extends the base mechanism by allowing the recipient to
250    /// authenticate that the sender possessed a given PSK. The PSK also
251    /// improves confidentiality guarantees in certain adversary models, as
252    /// described in more detail in [RFC 9180, Section 9.1]. We assume that both
253    /// parties have been provisioned with both the PSK value `psk` and
254    /// another byte string `psk_id` that is used to identify which PSK
255    /// should be used.
256    ///
257    /// The primary difference from the base case is that the `psk` and `psk_id`
258    /// values are used as `ikm` inputs to the KDF (instead of using the empty
259    /// string).
260    ///
261    /// The PSK MUST have at least 32 bytes of entropy and SHOULD be of length
262    /// `Nh` bytes or longer. See [RFC 9180, Section 9.5] for a more detailed
263    /// discussion.
264    ///
265    /// ```no_run
266    /// def SetupPSKS(pkR, info, psk, psk_id):
267    ///   shared_secret, enc = Encap(pkR)
268    ///   return enc, KeyScheduleS(mode_psk, shared_secret, info, psk, psk_id)
269    /// ```
270    ///
271    /// See [RFC 9180, Section 5.1.2] for details.
272    ///
273    /// # Errors
274    ///
275    /// Various errors may occur during the key encapsulation or decapsulation
276    /// process, or if the provided PSK does not meet security requirements.
277    ///
278    /// [RFC 9180, Section 5.1.2]: https://www.rfc-editor.org/rfc/rfc9180.html#section-5.1.2
279    /// [RFC 9180, Section 9.1]: https://www.rfc-editor.org/rfc/rfc9180.html#section-9.1
280    /// [RFC 9180, Section 9.5]: https://www.rfc-editor.org/rfc/rfc9180.html#section-9.5
281    pub fn setup_psk_s(
282        &self,
283        mut crypto_backend: C,
284        pk_r: HpkePublicKeyRef<'_>,
285        info: &[u8],
286        psk: &[u8],
287        psk_id: &[u8],
288    ) -> Result<(EncapsulatedSecret, Context<C, Sender>), Error> {
289        let (shared_secret, enc) = kem::encap(&mut crypto_backend, self.cipher_suite.kem_id, pk_r)?;
290
291        let context = self.key_schedule(
292            crypto_backend,
293            HpkeMode::Psk,
294            shared_secret.as_ref(),
295            info,
296            psk,
297            psk_id,
298        )?;
299
300        Ok((enc, context))
301    }
302
303    /// 5.1.2. Authentication Using a Pre-Shared Key
304    ///
305    /// This variant extends the base mechanism by allowing the recipient to
306    /// authenticate that the sender possessed a given PSK. The PSK also
307    /// improves confidentiality guarantees in certain adversary models, as
308    /// described in more detail in [RFC 9180, Section 9.1]. We assume that both
309    /// parties have been provisioned with both the PSK value `psk` and
310    /// another byte string `psk_id` that is used to identify which PSK
311    /// should be used.
312    ///
313    /// The primary difference from the base case is that the `psk` and `psk_id`
314    /// values are used as `ikm` inputs to the KDF (instead of using the empty
315    /// string).
316    ///
317    /// The PSK MUST have at least 32 bytes of entropy and SHOULD be of length
318    /// `Nh` bytes or longer. See [RFC 9180, Section 9.5] for a more detailed
319    /// discussion.
320    ///
321    /// ```no_run
322    /// def SetupPSKR(enc, skR, info, psk, psk_id):
323    ///   shared_secret = Decap(enc, skR)
324    ///   return KeyScheduleR(mode_psk, shared_secret, info, psk, psk_id)
325    /// ```
326    ///
327    /// See [RFC 9180, Section 5.1.2] for details.
328    ///
329    /// # Errors
330    ///
331    /// Various errors may occur during the key encapsulation or decapsulation
332    /// process, or if the provided PSK does not meet security requirements.
333    ///
334    /// [RFC 9180, Section 5.1.2]: https://www.rfc-editor.org/rfc/rfc9180.html#section-5.1.2
335    /// [RFC 9180, Section 9.1]: https://www.rfc-editor.org/rfc/rfc9180.html#section-9.1
336    /// [RFC 9180, Section 9.5]: https://www.rfc-editor.org/rfc/rfc9180.html#section-9.5
337    pub fn setup_psk_r(
338        &self,
339        mut crypto_backend: C,
340        enc: EncapsulatedSecretRef<'_>,
341        sk_r: HpkePrivateKeyRef<'_>,
342        info: &[u8],
343        psk: &[u8],
344        psk_id: &[u8],
345    ) -> Result<Context<C, Recipient>, Error> {
346        let shared_secret = kem::decap(&mut crypto_backend, self.cipher_suite.kem_id, enc, sk_r)?;
347
348        self.key_schedule(
349            crypto_backend,
350            HpkeMode::Psk,
351            shared_secret.as_ref(),
352            info,
353            psk,
354            psk_id,
355        )
356    }
357
358    /// 5.1.3. Authentication Using an Asymmetric Key
359    ///
360    /// This variant extends the base mechanism by allowing the recipient to
361    /// authenticate that the sender possessed a given KEM private key. This is
362    /// because `AuthDecap(enc, skR, pkS)` produces the correct KEM shared
363    /// secret only if the encapsulated value `enc` was produced by
364    /// `AuthEncap(pkR, skS)`, where `skS` is the private key corresponding
365    /// to `pkS`. In other words, at most two entities (precisely two, in
366    /// the case of DHKEM) could have produced this secret, so if the
367    /// recipient is at most one, then the sender is the other with
368    /// overwhelming probability.
369    ///
370    /// The primary difference from the base case is that the calls to `Encap()`
371    /// and `Decap()` are replaced with calls to `AuthEncap()` and
372    /// `AuthDecap()`, which add the sender public key to their internal
373    /// context string. The function parameters `pkR` and `pkS` are public
374    /// keys, and `enc` is an encapsulated KEM shared secret.
375    ///
376    /// Obviously, this variant can only be used with a KEM that provides
377    /// `AuthEncap()` and `AuthDecap()` procedures.
378    ///
379    /// This mechanism authenticates only the key pair of the sender, not any
380    /// other identifier. If an application wishes to bind HPKE ciphertexts or
381    /// exported secrets to another identity for the sender (e.g., an email
382    /// address or domain name), then this identifier should be included in the
383    /// info parameter to avoid identity misbinding issues.
384    ///
385    /// ```no_run
386    /// def SetupAuthS(pkR, info, skS):
387    ///   shared_secret, enc = AuthEncap(pkR, skS)
388    ///   return enc, KeyScheduleS(mode_auth, shared_secret, info,
389    ///                            default_psk, default_psk_id)
390    /// ```
391    ///
392    /// See [RFC 9180, Section 5.1.3] for details.
393    ///
394    /// # Errors
395    ///
396    /// Various errors may occur during the key encapsulation or decapsulation
397    /// process.
398    ///
399    /// [RFC 9180, Section 5.1.3]: https://www.rfc-editor.org/rfc/rfc9180.html#section-5.1.3
400    pub fn setup_auth_s(
401        &self,
402        mut crypto_backend: C,
403        pk_r: HpkePublicKeyRef<'_>,
404        info: &[u8],
405        sk_s: HpkePrivateKeyRef<'_>,
406    ) -> Result<(EncapsulatedSecret, Context<C, Sender>), Error> {
407        let (shared_secret, enc) =
408            kem::auth_encap(&mut crypto_backend, self.cipher_suite.kem_id, pk_r, sk_s)?;
409
410        let context = self.key_schedule(
411            crypto_backend,
412            HpkeMode::Auth,
413            shared_secret.as_ref(),
414            info,
415            &[],
416            &[],
417        )?;
418
419        Ok((enc, context))
420    }
421
422    /// 5.1.3. Authentication Using an Asymmetric Key
423    ///
424    /// This variant extends the base mechanism by allowing the recipient to
425    /// authenticate that the sender possessed a given KEM private key. This is
426    /// because `AuthDecap(enc, skR, pkS)` produces the correct KEM shared
427    /// secret only if the encapsulated value `enc` was produced by
428    /// `AuthEncap(pkR, skS)`, where `skS` is the private key corresponding
429    /// to `pkS`. In other words, at most two entities (precisely two, in
430    /// the case of DHKEM) could have produced this secret, so if the
431    /// recipient is at most one, then the sender is the other with
432    /// overwhelming probability.
433    ///
434    /// The primary difference from the base case is that the calls to `Encap()`
435    /// and `Decap()` are replaced with calls to `AuthEncap()` and
436    /// `AuthDecap()`, which add the sender public key to their internal
437    /// context string. The function parameters `pkR` and `pkS` are public
438    /// keys, and `enc` is an encapsulated KEM shared secret.
439    ///
440    /// Obviously, this variant can only be used with a KEM that provides
441    /// `AuthEncap()` and `AuthDecap()` procedures.
442    ///
443    /// This mechanism authenticates only the key pair of the sender, not any
444    /// other identifier. If an application wishes to bind HPKE ciphertexts or
445    /// exported secrets to another identity for the sender (e.g., an email
446    /// address or domain name), then this identifier should be included in the
447    /// info parameter to avoid identity misbinding issues.
448    ///
449    /// ```no_run
450    /// def SetupAuthR(enc, skR, info, pkS):
451    ///   shared_secret = AuthDecap(enc, skR, pkS)
452    ///   return KeyScheduleR(mode_auth, shared_secret, info,
453    ///                       default_psk, default_psk_id)
454    /// ```
455    ///
456    /// See [RFC 9180, Section 5.1.3] for details.
457    ///
458    /// # Errors
459    ///
460    /// Various errors may occur during the key encapsulation or decapsulation
461    /// process.
462    ///
463    /// [RFC 9180, Section 5.1.3]: https://www.rfc-editor.org/rfc/rfc9180.html#section-5.1.3
464    pub fn setup_auth_r(
465        &self,
466        mut crypto_backend: C,
467        enc: EncapsulatedSecretRef<'_>,
468        sk_r: HpkePrivateKeyRef<'_>,
469        info: &[u8],
470        pk_s: HpkePublicKeyRef<'_>,
471    ) -> Result<Context<C, Recipient>, Error> {
472        let shared_secret = kem::auth_decap(
473            &mut crypto_backend,
474            self.cipher_suite.kem_id,
475            enc,
476            sk_r,
477            pk_s,
478        )?;
479
480        self.key_schedule(
481            crypto_backend,
482            HpkeMode::Auth,
483            shared_secret.as_ref(),
484            info,
485            &[],
486            &[],
487        )
488    }
489
490    #[allow(clippy::too_many_arguments)]
491    /// 5.1.4. Authentication Using Both a PSK and an Asymmetric Key
492    ///
493    /// This mode is a straightforward combination of the PSK and authenticated
494    /// modes. Like the PSK mode, a PSK is provided as input to the key
495    /// schedule, and like the authenticated mode, authenticated KEM variants
496    /// are used.
497    ///
498    /// ```no_run
499    /// def SetupAuthPSKS(pkR, info, psk, psk_id, skS):
500    ///   shared_secret, enc = AuthEncap(pkR, skS)
501    ///   return enc, KeyScheduleS(mode_auth_psk, shared_secret, info,
502    ///                            psk, psk_id)
503    /// ```
504    ///
505    /// The PSK MUST have at least 32 bytes of entropy and SHOULD be of length
506    /// `Nh` bytes or longer. See [RFC 9180, Section 9.5] for a more detailed
507    /// discussion.
508    ///
509    /// See [RFC 9180, Section 5.1.4] for details.
510    ///
511    /// # Errors
512    ///
513    /// Various errors may occur during the key encapsulation or decapsulation
514    /// process, or if the provided PSK does not meet security requirements.
515    ///
516    /// [RFC 9180, Section 5.1.4]: https://www.rfc-editor.org/rfc/rfc9180.html#section-5.1.4
517    /// [RFC 9180, Section 9.5]: https://www.rfc-editor.org/rfc/rfc9180.html#section-9.5
518    pub fn setup_auth_psk_s(
519        &self,
520        mut crypto_backend: C,
521        pk_r: HpkePublicKeyRef<'_>,
522        info: &[u8],
523        psk: &[u8],
524        psk_id: &[u8],
525        sk_s: HpkePrivateKeyRef<'_>,
526    ) -> Result<(EncapsulatedSecret, Context<C, Sender>), Error> {
527        let (shared_secret, enc) =
528            kem::auth_encap(&mut crypto_backend, self.cipher_suite.kem_id, pk_r, sk_s)?;
529
530        let context = self.key_schedule(
531            crypto_backend,
532            HpkeMode::AuthPsk,
533            shared_secret.as_ref(),
534            info,
535            psk,
536            psk_id,
537        )?;
538
539        Ok((enc, context))
540    }
541
542    #[allow(clippy::too_many_arguments)]
543    /// 5.1.4. Authentication Using Both a PSK and an Asymmetric Key
544    ///
545    /// This mode is a straightforward combination of the PSK and authenticated
546    /// modes. Like the PSK mode, a PSK is provided as input to the key
547    /// schedule, and like the authenticated mode, authenticated KEM variants
548    /// are used.
549    ///
550    /// ```no_run
551    /// def SetupAuthPSKR(enc, skR, info, psk, psk_id, pkS):
552    ///   shared_secret = AuthDecap(enc, skR, pkS)
553    ///   return KeyScheduleR(mode_auth_psk, shared_secret, info,
554    ///                       psk, psk_id)
555    /// ```
556    ///
557    /// The PSK MUST have at least 32 bytes of entropy and SHOULD be of length
558    /// `Nh` bytes or longer. See [RFC 9180, Section 9.5] for a more detailed
559    /// discussion.
560    ///
561    /// See [RFC 9180, Section 5.1.4] for details.
562    ///
563    /// # Errors
564    ///
565    /// Various errors may occur during the key encapsulation or decapsulation
566    /// process, or if the provided PSK does not meet security requirements.
567    ///
568    /// [RFC 9180, Section 5.1.4]: https://www.rfc-editor.org/rfc/rfc9180.html#section-5.1.4
569    /// [RFC 9180, Section 9.5]: https://www.rfc-editor.org/rfc/rfc9180.html#section-9.5
570    pub fn setup_auth_psk_r(
571        &self,
572        mut crypto_backend: C,
573        enc: EncapsulatedSecretRef<'_>,
574        sk_r: HpkePrivateKeyRef<'_>,
575        info: &[u8],
576        psk: &[u8],
577        psk_id: &[u8],
578        pk_s: HpkePublicKeyRef<'_>,
579    ) -> Result<Context<C, Recipient>, Error> {
580        let shared_secret = kem::auth_decap(
581            &mut crypto_backend,
582            self.cipher_suite.kem_id,
583            enc,
584            sk_r,
585            pk_s,
586        )?;
587
588        self.key_schedule(
589            crypto_backend,
590            HpkeMode::AuthPsk,
591            shared_secret.as_ref(),
592            info,
593            psk,
594            psk_id,
595        )
596    }
597
598    #[inline]
599    /// ```text
600    /// def VerifyPSKInputs(mode, psk, psk_id):
601    ///   got_psk = (psk != default_psk)
602    ///   got_psk_id = (psk_id != default_psk_id)
603    ///   if got_psk != got_psk_id:
604    ///     raise Exception("Inconsistent PSK inputs")
605    ///
606    ///   if got_psk and (mode in [mode_base, mode_auth]):
607    ///     raise Exception("PSK input provided when not needed")
608    ///   if (not got_psk) and (mode in [mode_psk, mode_auth_psk]):
609    ///     raise Exception("Missing required PSK input")
610    /// ```
611    const fn verify_psk_inputs(mode: HpkeMode, psk: &[u8], psk_id: &[u8]) -> Result<(), Error> {
612        let got_psk = !psk.is_empty();
613        let got_psk_id = !psk_id.is_empty();
614
615        if got_psk != got_psk_id {
616            return Err(Error::InconsistentPsk);
617        }
618
619        if got_psk && matches!(mode, HpkeMode::Base | HpkeMode::Auth) {
620            return Err(Error::UnnecessaryPsk);
621        }
622
623        if !got_psk && matches!(mode, HpkeMode::Psk | HpkeMode::AuthPsk) {
624            return Err(Error::MissingPsk);
625        }
626
627        // Here different from RFC 9180's definition of `VerifyPSKInputs()`, we
628        // also check the PSK length requirement: the PSK MUST have at least 32 bytes of
629        // entropy and SHOULD be of length `Nh` bytes or longer. See [RFC 9180,
630        // Section 9.5] for a more detailed discussion.
631        if matches!(mode, HpkeMode::Psk | HpkeMode::AuthPsk) && psk.len() < 32 {
632            return Err(Error::InsecurePsk);
633        }
634
635        Ok(())
636    }
637
638    /// ```text
639    /// def KeySchedule<ROLE>(mode, shared_secret, info, psk, psk_id):
640    ///   // ...
641    ///
642    ///   psk_id_hash = LabeledExtract("", "psk_id_hash", psk_id)
643    ///   info_hash = LabeledExtract("", "info_hash", info)
644    ///   key_schedule_context = concat(mode, psk_id_hash, info_hash)
645    ///
646    ///   // ...
647    /// ```
648    ///
649    /// (Split out mainly for testing purposes.)
650    fn key_schedule_context(
651        &self,
652        crypto_backend: &C,
653        mode: HpkeMode,
654        info: &[u8],
655        psk_id: &[u8],
656    ) -> Result<Vec<u8>, Error> {
657        // psk_id_hash = LabeledExtract("", "psk_id_hash", psk_id)
658        let psk_id_hash = kdf::labeled_extract(
659            crypto_backend,
660            self.cipher_suite.kdf_id,
661            &self.cipher_suite.suite_id(),
662            &[0],
663            "psk_id_hash",
664            IkmRef::from(psk_id),
665        )?;
666
667        // info_hash = LabeledExtract("", "info_hash", info)
668        let info_hash = kdf::labeled_extract(
669            crypto_backend,
670            self.cipher_suite.kdf_id,
671            &self.cipher_suite.suite_id(),
672            &[0],
673            "info_hash",
674            IkmRef::from(info),
675        )?;
676
677        // key_schedule_context = concat(mode, psk_id_hash, info_hash)
678        Ok([&[mode as u8], &*psk_id_hash, &*info_hash].concat())
679    }
680
681    #[allow(clippy::needless_pass_by_value)]
682    /// ```text
683    /// def KeySchedule<ROLE>(mode, shared_secret, info, psk, psk_id):
684    ///   // ...
685    ///
686    ///   secret = LabeledExtract(shared_secret, "secret", psk)
687    ///
688    ///   // ...
689    /// ```
690    ///
691    /// (Split out mainly for testing purposes.)
692    fn key_schedule_secret(
693        &self,
694        crypto_backend: &C,
695        shared_secret: SharedSecretRef<'_>,
696        psk: &[u8],
697    ) -> Result<Prk, Error> {
698        // secret = LabeledExtract(shared_secret, "secret", psk)
699        kdf::labeled_extract(
700            crypto_backend,
701            self.cipher_suite.kdf_id,
702            &self.cipher_suite.suite_id(),
703            &shared_secret,
704            "secret",
705            IkmRef::from(psk),
706        )
707        .map_err(Into::into)
708    }
709
710    /// ```text
711    /// def KeySchedule<ROLE>(mode, shared_secret, info, psk, psk_id):
712    ///   VerifyPSKInputs(mode, psk, psk_id)
713    ///
714    ///   psk_id_hash = LabeledExtract("", "psk_id_hash", psk_id)
715    ///   info_hash = LabeledExtract("", "info_hash", info)
716    ///   key_schedule_context = concat(mode, psk_id_hash, info_hash)
717    ///
718    ///   secret = LabeledExtract(shared_secret, "secret", psk)
719    ///
720    ///   key = LabeledExpand(secret, "key", key_schedule_context, Nk)
721    ///   base_nonce = LabeledExpand(secret, "base_nonce",
722    ///                              key_schedule_context, Nn)
723    ///   exporter_secret = LabeledExpand(secret, "exp",
724    ///                                   key_schedule_context, Nh)
725    ///
726    ///   return Context<ROLE>(key, base_nonce, 0, exporter_secret)
727    /// ```
728    fn key_schedule<'a, Role>(
729        &self,
730        crypto_backend: C,
731        mode: HpkeMode,
732        shared_secret: impl Into<SharedSecretRef<'a>>,
733        info: &[u8],
734        psk: &[u8],
735        psk_id: &[u8],
736    ) -> Result<Context<C, Role>, Error> {
737        Self::verify_psk_inputs(mode, psk, psk_id)?;
738
739        // psk_id_hash = LabeledExtract("", "psk_id_hash", psk_id)
740        // info_hash = LabeledExtract("", "info_hash", info)
741        // key_schedule_context = concat(mode, psk_id_hash, info_hash)
742        let key_schedule_context =
743            self.key_schedule_context(&crypto_backend, mode, info, psk_id)?;
744
745        // secret = LabeledExtract(shared_secret, "secret", psk)
746        let secret = self.key_schedule_secret(&crypto_backend, shared_secret.into(), psk)?;
747
748        // key = LabeledExpand(secret, "key", key_schedule_context, Nk)
749        let key = kdf::labeled_expand(
750            &crypto_backend,
751            self.cipher_suite.kdf_id,
752            &self.cipher_suite.suite_id(),
753            PrkRef::from(&secret),
754            "key",
755            &key_schedule_context,
756            self.cipher_suite.aead_id.n_key(),
757        )?;
758        // base_nonce = LabeledExpand(secret, "base_nonce", key_schedule_context, Nn)
759        let base_nonce = kdf::labeled_expand(
760            &crypto_backend,
761            self.cipher_suite.kdf_id,
762            &self.cipher_suite.suite_id(),
763            PrkRef::from(&secret),
764            "base_nonce",
765            &key_schedule_context,
766            self.cipher_suite.aead_id.n_nonce(),
767        )?;
768        // exporter_secret = LabeledExpand(secret, "exp", key_schedule_context, Nh)
769        let exporter_secret = kdf::labeled_expand(
770            &crypto_backend,
771            self.cipher_suite.kdf_id,
772            &self.cipher_suite.suite_id(),
773            PrkRef::from(&secret),
774            "exp",
775            &key_schedule_context,
776            self.cipher_suite.kdf_id.n_hash(),
777        )?;
778
779        Ok(Context {
780            cipher_suite: self.cipher_suite,
781            aead: self
782                .cipher_suite
783                .aead_id
784                .new_crypto_info(&key, &base_nonce)
785                .expect("Must have valid key and nonce lengths"),
786            seq: 0,
787            exporter_secret: exporter_secret.to_vec(),
788            crypto_backend,
789            _role: PhantomData,
790        })
791    }
792}
793
794#[repr(u8)]
795#[derive(Debug, Clone, Copy, PartialEq, Eq)]
796/// The HPKE mode.
797///
798/// | Mode | Value |
799/// |:-:|:-:|
800/// | base | 0x00 |
801/// | psk  | 0x01 |
802/// | auth | 0x02 |
803/// | auth_psk | 0x03 |
804pub enum HpkeMode {
805    /// Base mode.
806    Base = 0x00,
807
808    /// PSK mode.
809    Psk = 0x01,
810
811    /// Authenticated mode.
812    Auth = 0x02,
813
814    /// Authenticated PSK mode.
815    AuthPsk = 0x03,
816}
817
818impl HpkeMode {
819    #[inline]
820    /// Try to convert a `u8` into an `HpkeMode`.
821    ///
822    /// # Errors
823    ///
824    /// [`UnknownHpkeMode`] if the value does not correspond to a known mode.
825    pub const fn try_from(value: u8) -> Result<Self, UnknownHpkeMode> {
826        match value {
827            v if v == Self::Base as u8 => Ok(Self::Base),
828            v if v == Self::Psk as u8 => Ok(Self::Psk),
829            v if v == Self::Auth as u8 => Ok(Self::Auth),
830            v if v == Self::AuthPsk as u8 => Ok(Self::AuthPsk),
831            other => Err(UnknownHpkeMode(other)),
832        }
833    }
834}
835
836impl TryFrom<u8> for HpkeMode {
837    type Error = UnknownHpkeMode;
838
839    fn try_from(value: u8) -> Result<Self, Self::Error> {
840        Self::try_from(value)
841    }
842}
843
844#[cfg(feature = "serde")]
845impl serde::Serialize for HpkeMode {
846    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
847    where
848        S: serde::Serializer,
849    {
850        serializer.serialize_u8(*self as u8)
851    }
852}
853
854#[cfg(feature = "serde")]
855impl<'de> serde::Deserialize<'de> for HpkeMode {
856    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
857    where
858        D: serde::Deserializer<'de>,
859    {
860        let value = u8::deserialize(deserializer)?;
861        HpkeMode::try_from(value).map_err(serde::de::Error::custom)
862    }
863}
864
865#[derive(Debug, Clone, Copy, PartialEq, Eq)]
866/// Error indicating an unknown HPKE mode.
867pub struct UnknownHpkeMode(pub u8);
868
869impl core::error::Error for UnknownHpkeMode {}
870
871impl fmt::Display for UnknownHpkeMode {
872    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
873        write!(f, "Unknown HPKE mode: {}", self.0)
874    }
875}
876
877#[derive(Debug)]
878/// Marker type for [`Context`] that indicates the `sender` role.
879pub struct Sender;
880
881#[derive(Debug)]
882/// Marker type for [`Context`] that indicates the `recipient` role.
883pub struct Recipient;
884
885/// The HPKE cryptographic context.
886///
887/// HPKE allows multiple encryption operations to be done based on a given
888/// setup transaction. Since the public key operations involved in setup are
889/// typically more expensive than symmetric encryption or decryption, this
890/// allows applications to amortize the cost of the public key operations,
891/// reducing the overall overhead.
892///
893/// In order to avoid nonce reuse, however, this encryption must be stateful.
894/// Each of the setup procedures above produces a role-specific context object
895/// that stores the AEAD and secret export parameters. The AEAD parameters
896/// consist of:
897///
898/// - The AEAD algorithm in use
899/// - A secret `key`
900/// - A base nonce `base_nonce`
901/// - A sequence number (initially 0)
902///
903/// The secret export parameters consist of:
904///
905/// - The HPKE ciphersuite in use and
906/// - An `exporter_secret` used for the secret export interface (see [RFC 9180,
907///   Section 5.3])
908///
909/// Note that the RFC currently doesn't define this.
910/// Also see <https://github.com/cfrg/draft-irtf-cfrg-hpke/issues/161>.
911///
912/// TODO: need pub?
913///
914/// [RFC 9180, Section 5.3]: https://www.rfc-editor.org/rfc/rfc9180.html#section-5.3
915pub struct Context<C, Role> {
916    /// The HPKE `ciphersuite` in use.
917    cipher_suite: HpkeCipherSuite,
918
919    /// The AEAD algorithm, secret `key` and `base_nonce`.
920    ///
921    /// The only way to get `None` here is to use an export-only AEAD,
922    aead: Option<HpkeAead>,
923
924    /// The sequence number.
925    seq: u32,
926
927    /// The exporter secret.
928    exporter_secret: Vec<u8>,
929
930    /// The crypto backend.
931    crypto_backend: C,
932
933    /// The role marker.
934    _role: PhantomData<Role>,
935}
936
937impl<C: Crypto> Context<C, Sender> {
938    /// See [`seal_in_place`](Self::seal_in_place).
939    ///
940    /// # Errors
941    ///
942    /// See [`seal_in_place`](Self::seal_in_place).
943    pub fn seal(&mut self, aad: &[u8], pt: &[u8]) -> Result<Vec<u8>, Error> {
944        let mut in_out = pt.to_vec();
945
946        self.seal_in_place(aad, &mut in_out)?;
947
948        Ok(in_out)
949    }
950
951    /// 5.2. Encryption and Decryption
952    ///
953    /// Encryption is unidirectional from sender to recipient. The sender's
954    /// context can encrypt a plaintext `pt` with associated data `aad` as
955    /// follows:
956    ///
957    /// ```text
958    /// def Context.Seal(aad, pt):
959    ///   ct = Seal(self.key, self.ComputeNonce(self.seq), aad, pt)
960    ///   self.IncrementSeq()
961    ///   return ct
962    /// ```
963    ///
964    /// See [RFC 9180, Section 5.2] for details.
965    ///
966    /// # Errors
967    ///
968    /// [`CryptoError`], or message limit reached.
969    ///
970    /// [RFC 9180, Section 5.2]: https://www.rfc-editor.org/rfc/rfc9180.html#section-5.2
971    pub fn seal_in_place(&mut self, aad: &[u8], in_out: &mut Vec<u8>) -> Result<(), Error> {
972        self.crypto_backend.aead_seal_in_place(
973            &self
974                .aead
975                .as_ref()
976                .ok_or(Error::InvalidInput("Export-only AEAD"))?
977                .copied_updating_nonce(|base_nonce| {
978                    Self::compute_nonce(base_nonce, self.seq);
979                }),
980            aad,
981            in_out,
982        )?;
983
984        self.increment_seq()?;
985
986        Ok(())
987    }
988}
989
990impl<C: Crypto> Context<C, Recipient> {
991    /// See [`open_in_place`](Self::open_in_place).
992    ///
993    /// # Errors
994    ///
995    /// See [`open_in_place`](Self::open_in_place).
996    pub fn open(&mut self, aad: &[u8], ct: &[u8]) -> Result<Vec<u8>, Error> {
997        let mut in_out = ct.to_vec();
998
999        self.open_in_place(aad, &mut in_out)?;
1000
1001        Ok(in_out)
1002    }
1003
1004    /// 5.2. Encryption and Decryption
1005    ///
1006    /// The recipient's context can decrypt a ciphertext `ct` with associated
1007    /// data `aad` as follows:
1008    ///
1009    /// ```no_run
1010    /// def Context.Open(aad, ct):
1011    ///   pt = Open(self.key, self.ComputeNonce(self.seq), aad, ct)
1012    ///   if pt == OpenError:
1013    ///     raise OpenError
1014    ///   self.IncrementSeq()
1015    ///   return pt
1016    /// ```
1017    ///
1018    /// See [RFC 9180, Section 5.2] for details.
1019    ///
1020    /// # Errors
1021    ///
1022    /// [`CryptoError`], or message limit reached.
1023    ///
1024    /// [RFC 9180, Section 5.2]: https://www.rfc-editor.org/rfc/rfc9180.html#section-5.2
1025    pub fn open_in_place(&mut self, aad: &[u8], in_out: &mut Vec<u8>) -> Result<(), Error> {
1026        self.crypto_backend.aead_open_in_place(
1027            &self
1028                .aead
1029                .as_ref()
1030                .ok_or(Error::InvalidInput("Export-only AEAD"))?
1031                .copied_updating_nonce(|base_nonce| {
1032                    Self::compute_nonce(base_nonce, self.seq);
1033                }),
1034            aad,
1035            in_out,
1036        )?;
1037
1038        self.increment_seq()?;
1039
1040        Ok(())
1041    }
1042}
1043
1044impl<C: Crypto, Role> Context<C, Role> {
1045    /// 5.3. Secret Export
1046    ///
1047    /// Takes a serialised exporter context as byte slice and a length for the
1048    /// output secret and returns an exporter secret as byte vector.
1049    ///
1050    /// ```no_run
1051    /// def Context.Export(exporter_context, L):
1052    ///   return LabeledExpand(self.exporter_secret, "sec", exporter_context, L)
1053    /// ```
1054    ///
1055    /// See [RFC 9180, Section 5.3] for details.
1056    ///
1057    /// # Errors
1058    ///
1059    /// See [`kdf::labeled_expand`].
1060    ///
1061    /// [RFC 9180, Section 5.3]: https://www.rfc-editor.org/rfc/rfc9180.html#section-5.3
1062    pub fn export(&self, exporter_context: &[u8], length: usize) -> Result<Okm, Error> {
1063        kdf::labeled_expand(
1064            &self.crypto_backend,
1065            self.cipher_suite.kdf_id,
1066            &self.cipher_suite.suite_id(),
1067            PrkRef::from(self.exporter_secret.as_slice()),
1068            "sec",
1069            exporter_context,
1070            length,
1071        )
1072        .map_err(Into::into)
1073    }
1074
1075    #[inline]
1076    /// ```no_run
1077    /// def Context<ROLE>.ComputeNonce(seq):
1078    ///   seq_bytes = I2OSP(seq, Nn)
1079    ///   return xor(self.base_nonce, seq_bytes)
1080    /// ```
1081    fn compute_nonce(base_nonce: &mut [u8], seq: u32) {
1082        // I2OSP: `to_be_bytes` then left padded with zeros to length `Nn`
1083        // We just XOR the bytes from right to left.
1084        for (o, i) in base_nonce
1085            .iter_mut()
1086            .rev()
1087            .zip(seq.to_be_bytes().into_iter().rev())
1088        {
1089            *o ^= i;
1090        }
1091    }
1092
1093    #[inline]
1094    /// ```no_run
1095    /// def Context<ROLE>.IncrementSeq():
1096    ///   if self.seq >= (1 << (8*Nn)) - 1:
1097    ///     raise MessageLimitReached
1098    ///   self.seq += 1
1099    /// ```
1100    const fn increment_seq(&mut self) -> Result<(), Error> {
1101        let nn = self.cipher_suite.aead_id.n_nonce() as u128;
1102
1103        if self.seq as u128 >= (1 << (8 * nn)) - 1 {
1104            return Err(Error::MessageLimitReached);
1105        }
1106
1107        self.seq += 1;
1108
1109        Ok(())
1110    }
1111}
1112
1113#[cfg(feature = "test-vectors")]
1114#[allow(missing_docs)]
1115pub static HPKE_TEST_VECTORS: std::sync::LazyLock<Vec<HpkeTestVector>> =
1116    std::sync::LazyLock::new(|| {
1117        let data = include_str!("../tests/test-vectors.json");
1118
1119        serde_json::from_str(data).expect("Failed to parse HPKE test vectors")
1120    });
1121
1122#[cfg(feature = "test-vectors")]
1123#[allow(missing_docs)]
1124#[derive(Debug, Clone)]
1125#[derive(serde::Serialize, serde::Deserialize)]
1126pub struct HpkeTestVector {
1127    pub mode: HpkeMode,
1128    pub kem_id: HpkeKemId,
1129    pub kdf_id: HpkeKdfId,
1130    pub aead_id: HpkeAeadId,
1131    pub info: HexString,
1132    #[serde(rename = "ikmR")]
1133    pub ikm_r: HexString,
1134    #[serde(rename = "ikmS")]
1135    pub ikm_s: Option<HexString>,
1136    #[serde(rename = "ikmE")]
1137    pub ikm_e: HexString,
1138    #[serde(rename = "skRm")]
1139    pub sk_rm: HexString,
1140    #[serde(default)]
1141    #[serde(rename = "skSm")]
1142    pub sk_sm: Option<HexString>,
1143    #[serde(rename = "skEm")]
1144    pub sk_em: HexString,
1145    pub psk: Option<HexString>,
1146    pub psk_id: Option<HexString>,
1147    #[serde(rename = "pkRm")]
1148    pub pk_rm: HexString,
1149    #[serde(rename = "pkSm")]
1150    pub pk_sm: Option<HexString>,
1151    #[serde(rename = "pkEm")]
1152    pub pk_em: HexString,
1153    pub enc: HexString,
1154    pub shared_secret: HexString,
1155    pub key_schedule_context: HexString,
1156    pub secret: HexString,
1157    pub key: HexString,
1158    pub base_nonce: HexString,
1159    pub exporter_secret: HexString,
1160    pub encryptions: Vec<HpkeTestVectorEncryption>,
1161    pub exports: Vec<HpkeTestVectorExport>,
1162}
1163
1164#[cfg(feature = "test-vectors")]
1165#[allow(missing_docs)]
1166#[derive(Debug, Clone)]
1167#[derive(serde::Serialize, serde::Deserialize)]
1168pub struct HpkeTestVectorEncryption {
1169    pub aad: HexString,
1170    pub ct: HexString,
1171    pub nonce: HexString,
1172    pub pt: HexString,
1173}
1174
1175#[cfg(feature = "test-vectors")]
1176#[allow(missing_docs)]
1177#[derive(Debug, Clone)]
1178#[derive(serde::Serialize, serde::Deserialize)]
1179pub struct HpkeTestVectorExport {
1180    pub exporter_context: HexString,
1181    #[serde(rename = "L")]
1182    pub l: usize,
1183    pub exported_value: HexString,
1184}
1185
1186#[cfg(feature = "test-vectors")]
1187#[allow(missing_docs)]
1188#[derive(Debug, Clone)]
1189pub struct HexString {
1190    pub bytes: Vec<u8>,
1191}
1192
1193#[cfg(feature = "test-vectors")]
1194impl serde::Serialize for HexString {
1195    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
1196    where
1197        S: serde::Serializer,
1198    {
1199        serializer.serialize_str(&const_hex::encode(&self.bytes))
1200    }
1201}
1202
1203#[cfg(feature = "test-vectors")]
1204impl<'de> serde::Deserialize<'de> for HexString {
1205    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
1206    where
1207        D: serde::Deserializer<'de>,
1208    {
1209        let hex_str = <&str>::deserialize(deserializer)?;
1210        let bytes = const_hex::decode(hex_str).map_err(serde::de::Error::custom)?;
1211        Ok(HexString { bytes })
1212    }
1213}
1214
1215#[cfg(feature = "test-vectors")]
1216impl core::ops::Deref for HexString {
1217    type Target = [u8];
1218
1219    fn deref(&self) -> &Self::Target {
1220        &self.bytes
1221    }
1222}
1223
1224#[cfg(test)]
1225mod kat_tests {
1226    use alloc::format;
1227    use alloc::vec::Vec;
1228    use core::panic::UnwindSafe;
1229    use std::panic::catch_unwind;
1230    use std::println;
1231
1232    use super::*;
1233
1234    #[test_case::test_matrix(
1235        [
1236            hpke_crypto::backend::HpkeCryptoAwsLc::new,
1237            hpke_crypto::backend::HpkeCryptoGraviola::new,
1238        ]
1239    )]
1240    fn test_setup<C, F>(crypto_backend: F)
1241    where
1242        C: Crypto + Send + Sync + UnwindSafe,
1243        F: Fn() -> Result<C, CryptoError> + UnwindSafe + Copy,
1244    {
1245        let mut rets = Vec::new();
1246
1247        for (idx, test_case) in HPKE_TEST_VECTORS.iter().enumerate() {
1248            let ret = catch_unwind(move || {
1249                test_setup_each(
1250                    crypto_backend,
1251                    idx,
1252                    Hpke::prepare(HpkeCipherSuite {
1253                        kem_id: test_case.kem_id,
1254                        kdf_id: test_case.kdf_id,
1255                        aead_id: test_case.aead_id,
1256                    }),
1257                    test_case.mode,
1258                    &test_case.pk_rm,
1259                    &test_case.info,
1260                    test_case.psk.as_deref(),
1261                    test_case.psk_id.as_deref(),
1262                    test_case.sk_sm.as_deref(),
1263                    &test_case.sk_rm,
1264                    test_case.pk_sm.as_deref(),
1265                );
1266            });
1267
1268            rets.push((format!("{}({})", test_case.kdf_id, idx), ret));
1269        }
1270
1271        let errors: Vec<_> = rets
1272            .iter()
1273            .filter(|(_, ret)| ret.is_err())
1274            .collect();
1275
1276        if !errors.is_empty() {
1277            for (name, err) in &errors {
1278                println!("[FAILED] {name}: {err:?}");
1279            }
1280
1281            panic!("{} test cases failed", errors.len());
1282        } else {
1283            println!("[OK] all {} test cases passed", rets.len());
1284        }
1285    }
1286
1287    fn test_setup_each<C: Crypto, F: Fn() -> Result<C, CryptoError>>(
1288        crypto_backend_f: F,
1289        idx: usize,
1290        hpke: Hpke<C>,
1291        mode: HpkeMode,
1292        pk_r: &[u8],
1293        info: &[u8],
1294        psk: Option<&[u8]>,
1295        psk_id: Option<&[u8]>,
1296        sk_s: Option<&[u8]>,
1297        sk_r: &[u8],
1298        pk_s: Option<&[u8]>,
1299    ) {
1300        let crypto_backend = crypto_backend_f().unwrap();
1301
1302        if !crypto_backend.is_kem_supported(&hpke.cipher_suite.kem_id) {
1303            // Skip unsupported KEMs.
1304            println!(
1305                "[{name}][{idx}] Skipping, unsupported KEM {alg:?}",
1306                name = core::any::type_name::<C>(),
1307                alg = hpke.cipher_suite.kem_id
1308            );
1309            return;
1310        }
1311
1312        if !crypto_backend.is_kdf_supported(&hpke.cipher_suite.kdf_id) {
1313            // Skip unsupported KDFs.
1314            println!(
1315                "[{name}][{idx}] Skipping, unsupported KDF {alg:?}",
1316                name = core::any::type_name::<C>(),
1317                alg = hpke.cipher_suite.kdf_id
1318            );
1319            return;
1320        }
1321
1322        let (enc_s, ctx_s) = hpke
1323            .setup_s(
1324                crypto_backend,
1325                mode,
1326                HpkePublicKeyRef::from(pk_r),
1327                info,
1328                psk,
1329                psk_id,
1330                sk_s.map(HpkePrivateKeyRef::from),
1331            )
1332            .unwrap_or_else(|e| {
1333                panic!("Failed to setup sender context: {e:?}, idx={idx}");
1334            });
1335
1336        let crypto_backend = crypto_backend_f().unwrap();
1337
1338        let ctx_r = hpke
1339            .setup_r(
1340                crypto_backend,
1341                mode,
1342                EncapsulatedSecretRef::from(&enc_s),
1343                HpkePrivateKeyRef::from(sk_r),
1344                info,
1345                psk,
1346                psk_id,
1347                pk_s.map(HpkePublicKeyRef::from),
1348            )
1349            .unwrap_or_else(|e| {
1350                panic!("Failed to setup recipient context: {e:?}, idx={idx}");
1351            });
1352
1353        assert_eq!(
1354            ctx_s.exporter_secret, ctx_r.exporter_secret,
1355            "Exporter secret mismatch"
1356        );
1357    }
1358
1359    #[test_case::test_matrix(
1360        [
1361            hpke_crypto::backend::HpkeCryptoAwsLc::new,
1362            hpke_crypto::backend::HpkeCryptoGraviola::new,
1363        ]
1364    )]
1365    fn test_key_schedule<C: Crypto + Send + Sync + UnwindSafe, F>(crypto_backend: F)
1366    where
1367        F: Fn() -> Result<C, CryptoError>,
1368    {
1369        let mut rets = Vec::new();
1370
1371        for (idx, test_case) in HPKE_TEST_VECTORS.iter().enumerate() {
1372            let crypto_backend = crypto_backend().unwrap();
1373
1374            let ret = catch_unwind(move || {
1375                test_key_schedule_each(
1376                    crypto_backend,
1377                    idx,
1378                    "GENERIC",
1379                    Hpke::prepare(HpkeCipherSuite {
1380                        kem_id: test_case.kem_id,
1381                        kdf_id: test_case.kdf_id,
1382                        aead_id: test_case.aead_id,
1383                    }),
1384                    test_case.mode,
1385                    &test_case.info,
1386                    test_case
1387                        .psk
1388                        .as_deref()
1389                        .unwrap_or_default(),
1390                    test_case
1391                        .psk_id
1392                        .as_deref()
1393                        .unwrap_or_default(),
1394                    &test_case.shared_secret,
1395                    test_case.aead_id,
1396                    &test_case.key_schedule_context,
1397                    &test_case.secret,
1398                    &test_case.key,
1399                    &test_case.base_nonce,
1400                    &test_case.exporter_secret,
1401                );
1402            });
1403
1404            rets.push((format!("{}({})", test_case.kdf_id, idx), ret));
1405        }
1406
1407        let errors: Vec<_> = rets
1408            .iter()
1409            .filter(|(_, ret)| ret.is_err())
1410            .collect();
1411
1412        if !errors.is_empty() {
1413            for (name, err) in &errors {
1414                println!("[FAILED] {name}: {err:?}");
1415            }
1416
1417            panic!("{} test cases failed", errors.len());
1418        } else {
1419            println!("[OK] all {} test cases passed", rets.len());
1420        }
1421    }
1422
1423    fn test_key_schedule_each<C: Crypto>(
1424        crypto_backend: C,
1425        idx: usize,
1426        role: &'static str,
1427        hpke: Hpke<C>,
1428        mode: HpkeMode,
1429        info: &[u8],
1430        psk: &[u8],
1431        psk_id: &[u8],
1432        shared_secret: &[u8],
1433        aead_id: HpkeAeadId,
1434        expected_key_schedule_context: &[u8],
1435        expected_secret: &[u8],
1436        expected_key: &[u8],
1437        expected_base_nonce: &[u8],
1438        expected_exporter_secret: &[u8],
1439    ) {
1440        if !crypto_backend.is_kdf_supported(&hpke.cipher_suite.kdf_id) {
1441            // Skip unsupported KDFs.
1442            println!(
1443                "[{name}][{idx}][{role}] Skipping, unsupported KDF {alg:?}",
1444                name = core::any::type_name::<C>(),
1445                alg = hpke.cipher_suite.kdf_id
1446            );
1447            return;
1448        }
1449
1450        // Testing key schedule context manually here.
1451        {
1452            let key_schedule_context = hpke
1453                .key_schedule_context(&crypto_backend, mode, info, psk_id)
1454                .unwrap_or_else(|e| {
1455                    panic!(
1456                        "Failed to create key schedule context: {e:?}, mode={mode:?}, \
1457                         info={info:?}, psk_id={psk_id:?}",
1458                    );
1459                });
1460
1461            assert_eq!(
1462                key_schedule_context, expected_key_schedule_context,
1463                "Key schedule context mismatch"
1464            );
1465        }
1466
1467        // Testing key schedule secret manually here.
1468        {
1469            let secret = hpke
1470                .key_schedule_secret(&crypto_backend, SharedSecretRef::from(&shared_secret), psk)
1471                .unwrap_or_else(|e| {
1472                    panic!(
1473                        "Failed to create key schedule secret: {e:?}, mode={mode:?}, \
1474                         shared_secret={shared_secret:?}, psk={psk:?}",
1475                    );
1476                });
1477
1478            assert_eq!(&*secret, expected_secret, "Key schedule secret mismatch");
1479        }
1480
1481        // Testing key schedule here.
1482        {
1483            let context = hpke
1484                .key_schedule::<Sender>(
1485                    crypto_backend,
1486                    mode,
1487                    SharedSecretRef::from(shared_secret),
1488                    &info,
1489                    psk,
1490                    psk_id,
1491                )
1492                .unwrap_or_else(|e| {
1493                    panic!(
1494                        "Failed to create context: {e:?}, mode={mode:?}, \
1495                         shared_secret={shared_secret:?}, \
1496                         key_schedule_context:{expected_key_schedule_context:?}, info={info:?}, \
1497                         psk={psk:?}, psk_id={psk_id:?}",
1498                    );
1499                });
1500
1501            // Note that key and nonce are empty for exporter only key derivation.
1502            if matches!(aead_id, HpkeAeadId::EXPORT_ONLY) {
1503                assert!(
1504                    context.aead.is_none(),
1505                    "AEAD key / nonce should be None for EXPORT_ONLY"
1506                );
1507            } else {
1508                assert_eq!(
1509                    context.aead.as_ref().unwrap().key(),
1510                    expected_key,
1511                    "AEAD key mismatch"
1512                );
1513                assert_eq!(
1514                    context.aead.as_ref().unwrap().nonce(),
1515                    expected_base_nonce,
1516                    "AEAD base nonce mismatch"
1517                );
1518            }
1519            assert_eq!(
1520                context.seq, 0,
1521                "Initial sequence number must be 0 when initialized"
1522            );
1523            assert_eq!(
1524                context.exporter_secret, expected_exporter_secret,
1525                "Exporter secret mismatch"
1526            );
1527        };
1528    }
1529
1530    #[test_case::test_matrix(
1531        [
1532            hpke_crypto::backend::HpkeCryptoAwsLc::new,
1533            hpke_crypto::backend::HpkeCryptoGraviola::new,
1534        ]
1535    )]
1536    fn test_encryption<C: Crypto + Send + Sync + UnwindSafe, F>(crypto_backend: F)
1537    where
1538        F: Fn() -> Result<C, CryptoError>,
1539    {
1540        let mut rets = Vec::new();
1541
1542        for (idx, test_case) in HPKE_TEST_VECTORS.iter().enumerate() {
1543            for (enc_idx, enc) in test_case.encryptions.iter().enumerate() {
1544                let crypto_backend = crypto_backend().unwrap();
1545
1546                let context = Context {
1547                    cipher_suite: HpkeCipherSuite {
1548                        kem_id: test_case.kem_id,
1549                        kdf_id: test_case.kdf_id,
1550                        aead_id: test_case.aead_id,
1551                    },
1552                    aead: test_case
1553                        .aead_id
1554                        .new_crypto_info(&test_case.key, &test_case.base_nonce)
1555                        .expect("Must have valid key and nonce lengths"),
1556                    seq: 0,
1557                    exporter_secret: test_case.exporter_secret.bytes.clone(),
1558                    crypto_backend,
1559                    _role: PhantomData,
1560                };
1561
1562                let ret = catch_unwind(move || {
1563                    test_encryption_each(context, enc_idx as u32, &enc.aad, &enc.pt, &enc.ct);
1564                });
1565
1566                rets.push((format!("{}({})[{}]", test_case.kdf_id, idx, enc_idx), ret));
1567            }
1568        }
1569
1570        let errors: Vec<_> = rets
1571            .iter()
1572            .filter(|(_, ret)| ret.is_err())
1573            .collect();
1574
1575        if !errors.is_empty() {
1576            for (name, err) in &errors {
1577                println!("[FAILED] {name}: {err:?}");
1578            }
1579
1580            panic!("{} test cases failed", errors.len());
1581        } else {
1582            println!("[OK] all {} test cases passed", rets.len());
1583        }
1584    }
1585
1586    fn test_encryption_each<C: Crypto>(
1587        mut context: Context<C, Sender>,
1588        seq: u32,
1589        aad: &[u8],
1590        pt: &[u8],
1591        expected_ct: &[u8],
1592    ) {
1593        context.seq = seq;
1594
1595        let ct = context
1596            .seal(aad, pt)
1597            .unwrap_or_else(|e| {
1598                panic!("Failed to encrypt: {e:?}, seq={seq}, aad={aad:?}, pt={pt:?}",);
1599            });
1600
1601        assert_eq!(ct, expected_ct, "Ciphertext mismatch");
1602
1603        // Decrypt and verify.
1604        let mut context = Context {
1605            cipher_suite: context.cipher_suite,
1606            aead: context.aead,
1607            seq,
1608            exporter_secret: context.exporter_secret,
1609            crypto_backend: context.crypto_backend,
1610            _role: PhantomData::<Recipient>,
1611        };
1612
1613        let pt2 = context
1614            .open(aad, &ct)
1615            .unwrap_or_else(|e| {
1616                panic!("Failed to decrypt: {e:?}, seq={seq}, aad={aad:?}, ct={ct:?}");
1617            });
1618
1619        assert_eq!(pt2, pt, "Decrypted plaintext mismatch");
1620    }
1621
1622    #[test_case::test_matrix(
1623        [
1624            hpke_crypto::backend::HpkeCryptoAwsLc::new,
1625            hpke_crypto::backend::HpkeCryptoGraviola::new,
1626        ]
1627    )]
1628    fn test_exported_values<C: Crypto + Send + Sync + UnwindSafe, F>(crypto_backend: F)
1629    where
1630        F: Fn() -> Result<C, CryptoError>,
1631    {
1632        let mut rets = Vec::new();
1633
1634        for (idx, test_case) in HPKE_TEST_VECTORS.iter().enumerate() {
1635            for (enc_idx, enc) in test_case.exports.iter().enumerate() {
1636                let crypto_backend = crypto_backend().unwrap();
1637
1638                let context = Context {
1639                    cipher_suite: HpkeCipherSuite {
1640                        kem_id: test_case.kem_id,
1641                        kdf_id: test_case.kdf_id,
1642                        aead_id: test_case.aead_id,
1643                    },
1644                    aead: None,
1645                    seq: 0,
1646                    exporter_secret: test_case.exporter_secret.bytes.clone(),
1647                    crypto_backend,
1648                    _role: PhantomData,
1649                };
1650
1651                let ret = catch_unwind(move || {
1652                    test_exported_values_each(
1653                        context,
1654                        &enc.exporter_context,
1655                        enc.l,
1656                        &enc.exported_value,
1657                    );
1658                });
1659
1660                rets.push((format!("{}({})[{}]", test_case.kdf_id, idx, enc_idx), ret));
1661            }
1662        }
1663
1664        let errors: Vec<_> = rets
1665            .iter()
1666            .filter(|(_, ret)| ret.is_err())
1667            .collect();
1668
1669        if !errors.is_empty() {
1670            for (name, err) in &errors {
1671                println!("[FAILED] {name}: {err:?}");
1672            }
1673
1674            panic!("{} test cases failed", errors.len());
1675        } else {
1676            println!("[OK] all {} test cases passed", rets.len());
1677        }
1678    }
1679
1680    fn test_exported_values_each<C: Crypto>(
1681        context: Context<C, Sender>,
1682        exporter_context: &[u8],
1683        l: usize,
1684        expected_exported_value: &[u8],
1685    ) {
1686        let exported_value = context
1687            .export(exporter_context, l)
1688            .unwrap_or_else(|e| {
1689                panic!(
1690                    "Failed to export secret: {e:?}, exporter_context={exporter_context:?}, l={l}",
1691                );
1692            });
1693
1694        assert_eq!(
1695            &*exported_value, expected_exported_value,
1696            "Exported value mismatch"
1697        );
1698    }
1699}