rustls/crypto/aws_lc_rs/
hpke.rs

1use alloc::boxed::Box;
2use alloc::vec::Vec;
3use core::fmt::{self, Debug, Formatter};
4
5use aws_lc_rs::aead::{
6    self, Aad, BoundKey, NONCE_LEN, Nonce, NonceSequence, OpeningKey, SealingKey, UnboundKey,
7};
8use aws_lc_rs::agreement;
9use aws_lc_rs::cipher::{AES_128_KEY_LEN, AES_256_KEY_LEN};
10use aws_lc_rs::digest::{SHA256_OUTPUT_LEN, SHA384_OUTPUT_LEN, SHA512_OUTPUT_LEN};
11use aws_lc_rs::encoding::{AsBigEndian, Curve25519SeedBin, EcPrivateKeyBin};
12use zeroize::Zeroize;
13
14use crate::crypto::aws_lc_rs::hmac::{HMAC_SHA256, HMAC_SHA384, HMAC_SHA512};
15use crate::crypto::aws_lc_rs::unspecified_err;
16use crate::crypto::hpke::{
17    EncapsulatedSecret, Hpke, HpkeOpener, HpkePrivateKey, HpkePublicKey, HpkeSealer, HpkeSuite,
18};
19use crate::crypto::tls13::{HkdfExpander, HkdfPrkExtract, HkdfUsingHmac, expand};
20use crate::error::{Error, OtherError};
21use crate::msgs::enums::{HpkeAead, HpkeKdf, HpkeKem};
22use crate::msgs::handshake::HpkeSymmetricCipherSuite;
23
24/// Default [RFC 9180] Hybrid Public Key Encryption (HPKE) suites supported by aws-lc-rs cryptography.
25pub static ALL_SUPPORTED_SUITES: &[&dyn Hpke] = &[
26    DH_KEM_P256_HKDF_SHA256_AES_128,
27    DH_KEM_P256_HKDF_SHA256_AES_256,
28    #[cfg(not(feature = "fips"))]
29    DH_KEM_P256_HKDF_SHA256_CHACHA20_POLY1305,
30    DH_KEM_P384_HKDF_SHA384_AES_128,
31    DH_KEM_P384_HKDF_SHA384_AES_256,
32    #[cfg(not(feature = "fips"))]
33    DH_KEM_P384_HKDF_SHA384_CHACHA20_POLY1305,
34    DH_KEM_P521_HKDF_SHA512_AES_128,
35    DH_KEM_P521_HKDF_SHA512_AES_256,
36    #[cfg(not(feature = "fips"))]
37    DH_KEM_P521_HKDF_SHA512_CHACHA20_POLY1305,
38    #[cfg(not(feature = "fips"))]
39    DH_KEM_X25519_HKDF_SHA256_AES_128,
40    #[cfg(not(feature = "fips"))]
41    DH_KEM_X25519_HKDF_SHA256_AES_256,
42    #[cfg(not(feature = "fips"))]
43    DH_KEM_X25519_HKDF_SHA256_CHACHA20_POLY1305,
44];
45
46/// HPKE suite using ECDH P-256 for agreement, HKDF SHA-256 for key derivation, and AEAD AES-128-GCM
47/// for symmetric encryption.
48pub static DH_KEM_P256_HKDF_SHA256_AES_128: &HpkeAwsLcRs<AES_128_KEY_LEN, SHA256_OUTPUT_LEN> =
49    &HpkeAwsLcRs {
50        suite: HpkeSuite {
51            kem: HpkeKem::DHKEM_P256_HKDF_SHA256,
52            sym: HpkeSymmetricCipherSuite {
53                kdf_id: HpkeKdf::HKDF_SHA256,
54                aead_id: HpkeAead::AES_128_GCM,
55            },
56        },
57        dh_kem: DH_KEM_P256_HKDF_SHA256,
58        hkdf: RING_HKDF_HMAC_SHA256,
59        aead: &aead::AES_128_GCM,
60    };
61
62/// HPKE suite using ECDH P-256 for agreement, HKDF SHA-256 for key derivation and AEAD AES-256-GCM
63/// for symmetric encryption.
64pub static DH_KEM_P256_HKDF_SHA256_AES_256: &HpkeAwsLcRs<AES_256_KEY_LEN, SHA256_OUTPUT_LEN> =
65    &HpkeAwsLcRs {
66        suite: HpkeSuite {
67            kem: HpkeKem::DHKEM_P256_HKDF_SHA256,
68            sym: HpkeSymmetricCipherSuite {
69                kdf_id: HpkeKdf::HKDF_SHA256,
70                aead_id: HpkeAead::AES_256_GCM,
71            },
72        },
73        dh_kem: DH_KEM_P256_HKDF_SHA256,
74        hkdf: RING_HKDF_HMAC_SHA256,
75        aead: &aead::AES_256_GCM,
76    };
77
78/// HPKE suite using ECDH P-256 for agreement, HKDF SHA-256 for key derivation, and AEAD
79/// CHACHA20-POLY-1305 for symmetric encryption.
80pub static DH_KEM_P256_HKDF_SHA256_CHACHA20_POLY1305: &HpkeAwsLcRs<
81    CHACHA_KEY_LEN,
82    SHA256_OUTPUT_LEN,
83> = &HpkeAwsLcRs {
84    suite: HpkeSuite {
85        kem: HpkeKem::DHKEM_P256_HKDF_SHA256,
86        sym: HpkeSymmetricCipherSuite {
87            kdf_id: HpkeKdf::HKDF_SHA256,
88            aead_id: HpkeAead::CHACHA20_POLY_1305,
89        },
90    },
91    dh_kem: DH_KEM_P256_HKDF_SHA256,
92    hkdf: RING_HKDF_HMAC_SHA256,
93    aead: &aead::CHACHA20_POLY1305,
94};
95
96/// HPKE suite using ECDH P-384 for agreement, HKDF SHA-384 for key derivation, and AEAD AES-128-GCM
97/// for symmetric encryption.
98pub static DH_KEM_P384_HKDF_SHA384_AES_128: &HpkeAwsLcRs<AES_128_KEY_LEN, SHA384_OUTPUT_LEN> =
99    &HpkeAwsLcRs {
100        suite: HpkeSuite {
101            kem: HpkeKem::DHKEM_P384_HKDF_SHA384,
102            sym: HpkeSymmetricCipherSuite {
103                kdf_id: HpkeKdf::HKDF_SHA384,
104                aead_id: HpkeAead::AES_128_GCM,
105            },
106        },
107        dh_kem: DH_KEM_P384_HKDF_SHA384,
108        hkdf: RING_HKDF_HMAC_SHA384,
109        aead: &aead::AES_128_GCM,
110    };
111
112/// HPKE suite using ECDH P-384 for agreement, HKDF SHA-384 for key derivation, and AEAD AES-256-GCM
113/// for symmetric encryption.
114pub static DH_KEM_P384_HKDF_SHA384_AES_256: &HpkeAwsLcRs<AES_256_KEY_LEN, SHA384_OUTPUT_LEN> =
115    &HpkeAwsLcRs {
116        suite: HpkeSuite {
117            kem: HpkeKem::DHKEM_P384_HKDF_SHA384,
118            sym: HpkeSymmetricCipherSuite {
119                kdf_id: HpkeKdf::HKDF_SHA384,
120                aead_id: HpkeAead::AES_256_GCM,
121            },
122        },
123        dh_kem: DH_KEM_P384_HKDF_SHA384,
124        hkdf: RING_HKDF_HMAC_SHA384,
125        aead: &aead::AES_256_GCM,
126    };
127
128/// HPKE suite using ECDH P-384 for agreement, HKDF SHA-384 for key derivation, and AEAD
129/// CHACHA20-POLY-1305 for symmetric encryption.
130pub static DH_KEM_P384_HKDF_SHA384_CHACHA20_POLY1305: &HpkeAwsLcRs<
131    CHACHA_KEY_LEN,
132    SHA384_OUTPUT_LEN,
133> = &HpkeAwsLcRs {
134    suite: HpkeSuite {
135        kem: HpkeKem::DHKEM_P384_HKDF_SHA384,
136        sym: HpkeSymmetricCipherSuite {
137            kdf_id: HpkeKdf::HKDF_SHA384,
138            aead_id: HpkeAead::CHACHA20_POLY_1305,
139        },
140    },
141    dh_kem: DH_KEM_P384_HKDF_SHA384,
142    hkdf: RING_HKDF_HMAC_SHA384,
143    aead: &aead::CHACHA20_POLY1305,
144};
145
146/// HPKE suite using ECDH P-521 for agreement, HKDF SHA-512 for key derivation, and AEAD AES-128-GCM
147/// for symmetric encryption.
148pub static DH_KEM_P521_HKDF_SHA512_AES_128: &HpkeAwsLcRs<AES_128_KEY_LEN, SHA512_OUTPUT_LEN> =
149    &HpkeAwsLcRs {
150        suite: HpkeSuite {
151            kem: HpkeKem::DHKEM_P521_HKDF_SHA512,
152            sym: HpkeSymmetricCipherSuite {
153                kdf_id: HpkeKdf::HKDF_SHA512,
154                aead_id: HpkeAead::AES_128_GCM,
155            },
156        },
157        dh_kem: DH_KEM_P521_HKDF_SHA512,
158        hkdf: RING_HKDF_HMAC_SHA512,
159        aead: &aead::AES_128_GCM,
160    };
161
162/// HPKE suite using ECDH P-521 for agreement, HKDF SHA-512 for key derivation, and AEAD AES-256-GCM
163/// for symmetric encryption.
164pub static DH_KEM_P521_HKDF_SHA512_AES_256: &HpkeAwsLcRs<AES_256_KEY_LEN, SHA512_OUTPUT_LEN> =
165    &HpkeAwsLcRs {
166        suite: HpkeSuite {
167            kem: HpkeKem::DHKEM_P521_HKDF_SHA512,
168            sym: HpkeSymmetricCipherSuite {
169                kdf_id: HpkeKdf::HKDF_SHA512,
170                aead_id: HpkeAead::AES_256_GCM,
171            },
172        },
173        dh_kem: DH_KEM_P521_HKDF_SHA512,
174        hkdf: RING_HKDF_HMAC_SHA512,
175        aead: &aead::AES_256_GCM,
176    };
177
178/// HPKE suite using ECDH P-521 for agreement, HKDF SHA-512 for key derivation, and AEAD
179/// CHACHA20-POLY-1305 for symmetric encryption.
180pub static DH_KEM_P521_HKDF_SHA512_CHACHA20_POLY1305: &HpkeAwsLcRs<
181    CHACHA_KEY_LEN,
182    SHA512_OUTPUT_LEN,
183> = &HpkeAwsLcRs {
184    suite: HpkeSuite {
185        kem: HpkeKem::DHKEM_P521_HKDF_SHA512,
186        sym: HpkeSymmetricCipherSuite {
187            kdf_id: HpkeKdf::HKDF_SHA512,
188            aead_id: HpkeAead::CHACHA20_POLY_1305,
189        },
190    },
191    dh_kem: DH_KEM_P521_HKDF_SHA512,
192    hkdf: RING_HKDF_HMAC_SHA512,
193    aead: &aead::CHACHA20_POLY1305,
194};
195
196/// HPKE suite using ECDH X25519 for agreement, HKDF SHA-256 for key derivation, and AEAD AES-128-GCM
197/// for symmetric encryption.
198pub static DH_KEM_X25519_HKDF_SHA256_AES_128: &HpkeAwsLcRs<AES_128_KEY_LEN, SHA256_OUTPUT_LEN> =
199    &HpkeAwsLcRs {
200        suite: HpkeSuite {
201            kem: HpkeKem::DHKEM_X25519_HKDF_SHA256,
202            sym: HpkeSymmetricCipherSuite {
203                kdf_id: HpkeKdf::HKDF_SHA256,
204                aead_id: HpkeAead::AES_128_GCM,
205            },
206        },
207        dh_kem: DH_KEM_X25519_HKDF_SHA256,
208        hkdf: RING_HKDF_HMAC_SHA256,
209        aead: &aead::AES_128_GCM,
210    };
211
212/// HPKE suite using ECDH X25519 for agreement, HKDF SHA-256 for key derivation, and AEAD AES-256-GCM
213/// for symmetric encryption.
214pub static DH_KEM_X25519_HKDF_SHA256_AES_256: &HpkeAwsLcRs<AES_256_KEY_LEN, SHA256_OUTPUT_LEN> =
215    &HpkeAwsLcRs {
216        suite: HpkeSuite {
217            kem: HpkeKem::DHKEM_X25519_HKDF_SHA256,
218            sym: HpkeSymmetricCipherSuite {
219                kdf_id: HpkeKdf::HKDF_SHA256,
220                aead_id: HpkeAead::AES_256_GCM,
221            },
222        },
223        dh_kem: DH_KEM_X25519_HKDF_SHA256,
224        hkdf: RING_HKDF_HMAC_SHA256,
225        aead: &aead::AES_256_GCM,
226    };
227
228/// HPKE suite using ECDH X25519 for agreement, HKDF SHA-256 for key derivation, and AEAD
229/// CHACHA20-POLY-1305 for symmetric encryption.
230pub static DH_KEM_X25519_HKDF_SHA256_CHACHA20_POLY1305: &HpkeAwsLcRs<
231    CHACHA_KEY_LEN,
232    SHA256_OUTPUT_LEN,
233> = &HpkeAwsLcRs {
234    suite: HpkeSuite {
235        kem: HpkeKem::DHKEM_X25519_HKDF_SHA256,
236        sym: HpkeSymmetricCipherSuite {
237            kdf_id: HpkeKdf::HKDF_SHA256,
238            aead_id: HpkeAead::CHACHA20_POLY_1305,
239        },
240    },
241    dh_kem: DH_KEM_X25519_HKDF_SHA256,
242    hkdf: RING_HKDF_HMAC_SHA256,
243    aead: &aead::CHACHA20_POLY1305,
244};
245
246/// `HpkeAwsLcRs` holds the concrete instantiations of the algorithms specified by the [HpkeSuite].
247pub struct HpkeAwsLcRs<const KEY_SIZE: usize, const KDF_SIZE: usize> {
248    suite: HpkeSuite,
249    dh_kem: &'static DhKem<KDF_SIZE>,
250    hkdf: &'static dyn HkdfPrkExtract,
251    aead: &'static aead::Algorithm,
252}
253
254impl<const KEY_SIZE: usize, const KDF_SIZE: usize> HpkeAwsLcRs<KEY_SIZE, KDF_SIZE> {
255    /// See [RFC 9180 §5.1 "Creating the Encryption Context"][0].
256    ///
257    /// [0]: https://www.rfc-editor.org/rfc/rfc9180.html#section-5.1
258    fn key_schedule(
259        &self,
260        shared_secret: KemSharedSecret<KDF_SIZE>,
261        info: &[u8],
262    ) -> Result<KeySchedule<KEY_SIZE>, Error> {
263        // Note: we use an empty IKM for the `psk_id_hash` and `secret` labelled extractions because
264        // there is no PSK ID in base mode HPKE.
265
266        let suite_id = LabeledSuiteId::Hpke(self.suite);
267        let psk_id_hash = labeled_extract_for_prk(self.hkdf, suite_id, None, Label::PskIdHash, &[]);
268        let info_hash = labeled_extract_for_prk(self.hkdf, suite_id, None, Label::InfoHash, info);
269        let key_schedule_context = [
270            &[0][..], // base mode (0x00)
271            &psk_id_hash,
272            &info_hash,
273        ]
274        .concat();
275
276        let key = AeadKey(self.key_schedule_labeled_expand::<KEY_SIZE>(
277            &shared_secret,
278            &key_schedule_context,
279            Label::Key,
280        ));
281
282        let base_nonce = self.key_schedule_labeled_expand::<NONCE_LEN>(
283            &shared_secret,
284            &key_schedule_context,
285            Label::BaseNonce,
286        );
287
288        Ok(KeySchedule {
289            aead: self.aead,
290            key,
291            base_nonce,
292            seq_num: 0,
293        })
294    }
295
296    fn key_schedule_labeled_expand<const L: usize>(
297        &self,
298        shared_secret: &KemSharedSecret<KDF_SIZE>,
299        key_schedule_context: &[u8],
300        label: Label,
301    ) -> [u8; L] {
302        let suite_id = LabeledSuiteId::Hpke(self.suite);
303        labeled_expand::<L>(
304            suite_id,
305            labeled_extract_for_expand(
306                self.hkdf,
307                suite_id,
308                Some(&shared_secret.0),
309                Label::Secret,
310                &[],
311            ),
312            label,
313            key_schedule_context,
314        )
315    }
316}
317
318impl<const KEY_SIZE: usize, const KDF_SIZE: usize> Hpke for HpkeAwsLcRs<KEY_SIZE, KDF_SIZE> {
319    fn seal(
320        &self,
321        info: &[u8],
322        aad: &[u8],
323        plaintext: &[u8],
324        pub_key: &HpkePublicKey,
325    ) -> Result<(EncapsulatedSecret, Vec<u8>), Error> {
326        let (encap, mut sealer) = self.setup_sealer(info, pub_key)?;
327        Ok((encap, sealer.seal(aad, plaintext)?))
328    }
329
330    fn setup_sealer(
331        &self,
332        info: &[u8],
333        pub_key: &HpkePublicKey,
334    ) -> Result<(EncapsulatedSecret, Box<dyn HpkeSealer + 'static>), Error> {
335        let (encap, sealer) = Sealer::new(self, info, pub_key)?;
336        Ok((encap, Box::new(sealer)))
337    }
338
339    fn open(
340        &self,
341        enc: &EncapsulatedSecret,
342        info: &[u8],
343        aad: &[u8],
344        ciphertext: &[u8],
345        secret_key: &HpkePrivateKey,
346    ) -> Result<Vec<u8>, Error> {
347        self.setup_opener(enc, info, secret_key)?
348            .open(aad, ciphertext)
349    }
350
351    fn setup_opener(
352        &self,
353        enc: &EncapsulatedSecret,
354        info: &[u8],
355        secret_key: &HpkePrivateKey,
356    ) -> Result<Box<dyn HpkeOpener + 'static>, Error> {
357        Ok(Box::new(Opener::new(self, enc, info, secret_key)?))
358    }
359
360    fn fips(&self) -> bool {
361        matches!(
362            // We make a FIPS determination based on the suite's DH KEM and AEAD choice.
363            // We don't need to examine the KDF choice because all supported KDFs are FIPS
364            // compatible.
365            (self.suite.kem, self.suite.sym.aead_id),
366            (
367                // Only the NIST "P-curve" DH KEMs are FIPS compatible.
368                HpkeKem::DHKEM_P256_HKDF_SHA256
369                    | HpkeKem::DHKEM_P384_HKDF_SHA384
370                    | HpkeKem::DHKEM_P521_HKDF_SHA512,
371                // Only the AES AEADs are FIPS compatible.
372                HpkeAead::AES_128_GCM | HpkeAead::AES_256_GCM,
373            )
374        )
375    }
376
377    fn generate_key_pair(&self) -> Result<(HpkePublicKey, HpkePrivateKey), Error> {
378        (self.dh_kem.key_generator)()
379    }
380
381    fn suite(&self) -> HpkeSuite {
382        self.suite
383    }
384}
385
386impl<const KEY_SIZE: usize, const KDF_SIZE: usize> Debug for HpkeAwsLcRs<KEY_SIZE, KDF_SIZE> {
387    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
388        self.suite.fmt(f)
389    }
390}
391
392/// Adapts a [KeySchedule] and [AeadKey] for the role of a [HpkeSealer].
393struct Sealer<const KEY_SIZE: usize, const KDF_SIZE: usize> {
394    key_schedule: KeySchedule<KEY_SIZE>,
395}
396
397impl<const KEY_SIZE: usize, const KDF_SIZE: usize> Sealer<KEY_SIZE, KDF_SIZE> {
398    /// See [RFC 9180 §5.1.1 "Encryption to a Public Key"][0].
399    ///
400    /// [0]: https://www.rfc-editor.org/rfc/rfc9180.html#section-5.1.1
401    fn new(
402        suite: &HpkeAwsLcRs<KEY_SIZE, KDF_SIZE>,
403        info: &[u8],
404        pub_key: &HpkePublicKey,
405    ) -> Result<(EncapsulatedSecret, Self), Error> {
406        // def SetupBaseS(pkR, info):
407        //   shared_secret, enc = Encap(pkR)
408        //   return enc, KeyScheduleS(mode_base, shared_secret, info,
409        //                            default_psk, default_psk_id)
410
411        let (shared_secret, enc) = suite.dh_kem.encap(pub_key)?;
412        let key_schedule = suite.key_schedule(shared_secret, info)?;
413        Ok((enc, Self { key_schedule }))
414    }
415
416    /// A **test only** constructor that uses a pre-specified ephemeral agreement private key
417    /// instead of one that is randomly generated.
418    #[cfg(test)]
419    fn test_only_new(
420        suite: &HpkeAwsLcRs<KEY_SIZE, KDF_SIZE>,
421        info: &[u8],
422        pub_key: &HpkePublicKey,
423        sk_e: &[u8],
424    ) -> Result<(EncapsulatedSecret, Self), Error> {
425        let (shared_secret, enc) = suite
426            .dh_kem
427            .test_only_encap(pub_key, sk_e)?;
428        let key_schedule = suite.key_schedule(shared_secret, info)?;
429        Ok((enc, Self { key_schedule }))
430    }
431}
432
433impl<const KEY_SIZE: usize, const KDF_SIZE: usize> HpkeSealer for Sealer<KEY_SIZE, KDF_SIZE> {
434    fn seal(&mut self, aad: &[u8], plaintext: &[u8]) -> Result<Vec<u8>, Error> {
435        // def ContextS.Seal(aad, pt):
436        //   ct = Seal(self.key, self.ComputeNonce(self.seq), aad, pt)
437        //   self.IncrementSeq()
438        //   return ct
439
440        let key = UnboundKey::new(self.key_schedule.aead, &self.key_schedule.key.0)
441            .map_err(unspecified_err)?;
442        let mut sealing_key = SealingKey::new(key, &mut self.key_schedule);
443
444        let mut in_out_buffer = Vec::from(plaintext);
445        sealing_key
446            .seal_in_place_append_tag(Aad::from(aad), &mut in_out_buffer)
447            .map_err(unspecified_err)?;
448
449        Ok(in_out_buffer)
450    }
451}
452
453impl<const KEY_SIZE: usize, const KDF_SIZE: usize> Debug for Sealer<KEY_SIZE, KDF_SIZE> {
454    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
455        f.debug_struct("Sealer").finish()
456    }
457}
458
459/// Adapts a [KeySchedule] and [AeadKey] for the role of a [HpkeOpener].
460struct Opener<const KEY_SIZE: usize, const KDF_SIZE: usize> {
461    key_schedule: KeySchedule<KEY_SIZE>,
462}
463
464impl<const KEY_SIZE: usize, const KDF_SIZE: usize> Opener<KEY_SIZE, KDF_SIZE> {
465    /// See [RFC 9180 §5.1.1 "Encryption to a Public Key"][0].
466    ///
467    /// [0]: https://www.rfc-editor.org/rfc/rfc9180.html#section-5.1.1
468    fn new(
469        suite: &HpkeAwsLcRs<KEY_SIZE, KDF_SIZE>,
470        enc: &EncapsulatedSecret,
471        info: &[u8],
472        secret_key: &HpkePrivateKey,
473    ) -> Result<Self, Error> {
474        // def SetupBaseR(enc, skR, info):
475        //   shared_secret = Decap(enc, skR)
476        //   return KeyScheduleR(mode_base, shared_secret, info,
477        //                       default_psk, default_psk_id)
478        Ok(Self {
479            key_schedule: suite.key_schedule(suite.dh_kem.decap(enc, secret_key)?, info)?,
480        })
481    }
482}
483
484impl<const KEY_SIZE: usize, const KDF_SIZE: usize> HpkeOpener for Opener<KEY_SIZE, KDF_SIZE> {
485    fn open(&mut self, aad: &[u8], ciphertext: &[u8]) -> Result<Vec<u8>, Error> {
486        // def ContextR.Open(aad, ct):
487        //   pt = Open(self.key, self.ComputeNonce(self.seq), aad, ct)
488        //   if pt == OpenError:
489        //     raise OpenError
490        //   self.IncrementSeq()
491        //   return pt
492
493        let key = UnboundKey::new(self.key_schedule.aead, &self.key_schedule.key.0)
494            .map_err(unspecified_err)?;
495        let mut opening_key = OpeningKey::new(key, &mut self.key_schedule);
496
497        let mut in_out_buffer = Vec::from(ciphertext);
498        let plaintext = opening_key
499            .open_in_place(Aad::from(aad), &mut in_out_buffer)
500            .map_err(unspecified_err)?;
501
502        Ok(plaintext.to_vec())
503    }
504}
505
506impl<const KEY_SIZE: usize, const KDF_SIZE: usize> Debug for Opener<KEY_SIZE, KDF_SIZE> {
507    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
508        f.debug_struct("Opener").finish()
509    }
510}
511
512/// A Diffie-Hellman (DH) based Key Encapsulation Mechanism (KEM).
513///
514/// See [RFC 9180 §4.1 "DH-Based KEM (DHKEM)"][0].
515///
516/// [0]: https://www.rfc-editor.org/rfc/rfc9180.html#section-4.1
517struct DhKem<const KDF_SIZE: usize> {
518    id: HpkeKem,
519    agreement_algorithm: &'static agreement::Algorithm,
520    key_generator:
521        &'static (dyn Fn() -> Result<(HpkePublicKey, HpkePrivateKey), Error> + Send + Sync),
522    hkdf: &'static dyn HkdfPrkExtract,
523}
524
525impl<const KDF_SIZE: usize> DhKem<KDF_SIZE> {
526    /// See [RFC 9180 §4.1 "DH-Based KEM (DHKEM)"][0].
527    ///
528    /// [0]: https://www.rfc-editor.org/rfc/rfc9180.html#section-4.1
529    fn encap(
530        &self,
531        recipient: &HpkePublicKey,
532    ) -> Result<(KemSharedSecret<KDF_SIZE>, EncapsulatedSecret), Error> {
533        // def Encap(pkR):
534        //   skE, pkE = GenerateKeyPair()
535
536        let sk_e =
537            agreement::PrivateKey::generate(self.agreement_algorithm).map_err(unspecified_err)?;
538        self.encap_impl(recipient, sk_e)
539    }
540
541    /// A test-only encap operation that uses a fixed `test_only_ske` instead of generating
542    /// one randomly.
543    #[cfg(test)]
544    fn test_only_encap(
545        &self,
546        recipient: &HpkePublicKey,
547        test_only_ske: &[u8],
548    ) -> Result<(KemSharedSecret<KDF_SIZE>, EncapsulatedSecret), Error> {
549        // For test contexts only, we accept a static sk_e as an argument.
550        let sk_e = agreement::PrivateKey::from_private_key(self.agreement_algorithm, test_only_ske)
551            .map_err(key_rejected_err)?;
552        self.encap_impl(recipient, sk_e)
553    }
554
555    fn encap_impl(
556        &self,
557        recipient: &HpkePublicKey,
558        sk_e: agreement::PrivateKey,
559    ) -> Result<(KemSharedSecret<KDF_SIZE>, EncapsulatedSecret), Error> {
560        // def Encap(pkR):
561        //   skE, pkE = GenerateKeyPair()
562        //   dh = DH(skE, pkR)
563        //   enc = SerializePublicKey(pkE)
564        //
565        //   pkRm = SerializePublicKey(pkR)
566        //   kem_context = concat(enc, pkRm)
567        //
568        //   shared_secret = ExtractAndExpand(dh, kem_context)
569        //   return shared_secret, enc
570
571        let enc = sk_e
572            .compute_public_key()
573            .map_err(unspecified_err)?;
574        let pk_r = agreement::UnparsedPublicKey::new(self.agreement_algorithm, &recipient.0);
575        let kem_context = [enc.as_ref(), pk_r.bytes()].concat();
576
577        let shared_secret = agreement::agree(&sk_e, pk_r, aws_lc_rs::error::Unspecified, |dh| {
578            Ok(self.extract_and_expand(dh, &kem_context))
579        })
580        .map_err(unspecified_err)?;
581
582        Ok((
583            KemSharedSecret(shared_secret),
584            EncapsulatedSecret(enc.as_ref().into()),
585        ))
586    }
587
588    /// See [RFC 9180 §4.1 "DH-Based KEM (DHKEM)"][0].
589    ///
590    /// [0]: https://www.rfc-editor.org/rfc/rfc9180.html#section-4.1
591    fn decap(
592        &self,
593        enc: &EncapsulatedSecret,
594        recipient: &HpkePrivateKey,
595    ) -> Result<KemSharedSecret<KDF_SIZE>, Error> {
596        // def Decap(enc, skR):
597        //   pkE = DeserializePublicKey(enc)
598        //   dh = DH(skR, pkE)
599        //
600        //   pkRm = SerializePublicKey(pk(skR))
601        //   kem_context = concat(enc, pkRm)
602        //
603        //   shared_secret = ExtractAndExpand(dh, kem_context)
604        //   return shared_secret
605
606        let pk_e = agreement::UnparsedPublicKey::new(self.agreement_algorithm, &enc.0);
607        let sk_r = agreement::PrivateKey::from_private_key(
608            self.agreement_algorithm,
609            recipient.secret_bytes(),
610        )
611        .map_err(key_rejected_err)?;
612        let pk_rm = sk_r
613            .compute_public_key()
614            .map_err(unspecified_err)?;
615        let kem_context = [&enc.0, pk_rm.as_ref()].concat();
616
617        let shared_secret = agreement::agree(&sk_r, pk_e, aws_lc_rs::error::Unspecified, |dh| {
618            Ok(self.extract_and_expand(dh, &kem_context))
619        })
620        .map_err(unspecified_err)?;
621
622        Ok(KemSharedSecret(shared_secret))
623    }
624
625    /// See [RFC 9180 §4.1 "DH-Based KEM (DHKEM)"][0].
626    ///
627    /// [0]: https://www.rfc-editor.org/rfc/rfc9180.html#section-4.1
628    fn extract_and_expand(&self, dh: &[u8], kem_context: &[u8]) -> [u8; KDF_SIZE] {
629        // def ExtractAndExpand(dh, kem_context):
630        //   eae_prk = LabeledExtract("", "eae_prk", dh)
631        //   shared_secret = LabeledExpand(eae_prk, "shared_secret",
632        //                                 kem_context, Nsecret)
633        //   return shared_secret
634
635        let suite_id = LabeledSuiteId::Kem(self.id);
636        labeled_expand(
637            suite_id,
638            labeled_extract_for_expand(self.hkdf, suite_id, None, Label::EaePrk, dh),
639            Label::SharedSecret,
640            kem_context,
641        )
642    }
643}
644
645static DH_KEM_P256_HKDF_SHA256: &DhKem<SHA256_OUTPUT_LEN> = &DhKem {
646    id: HpkeKem::DHKEM_P256_HKDF_SHA256,
647    agreement_algorithm: &agreement::ECDH_P256,
648    key_generator: &|| generate_p_curve_key_pair(&agreement::ECDH_P256),
649    hkdf: RING_HKDF_HMAC_SHA256,
650};
651
652static DH_KEM_P384_HKDF_SHA384: &DhKem<SHA384_OUTPUT_LEN> = &DhKem {
653    id: HpkeKem::DHKEM_P384_HKDF_SHA384,
654    agreement_algorithm: &agreement::ECDH_P384,
655    key_generator: &|| generate_p_curve_key_pair(&agreement::ECDH_P384),
656    hkdf: RING_HKDF_HMAC_SHA384,
657};
658
659static DH_KEM_P521_HKDF_SHA512: &DhKem<SHA512_OUTPUT_LEN> = &DhKem {
660    id: HpkeKem::DHKEM_P521_HKDF_SHA512,
661    agreement_algorithm: &agreement::ECDH_P521,
662    key_generator: &|| generate_p_curve_key_pair(&agreement::ECDH_P521),
663    hkdf: RING_HKDF_HMAC_SHA512,
664};
665
666static DH_KEM_X25519_HKDF_SHA256: &DhKem<SHA256_OUTPUT_LEN> = &DhKem {
667    id: HpkeKem::DHKEM_X25519_HKDF_SHA256,
668    agreement_algorithm: &agreement::X25519,
669    key_generator: &generate_x25519_key_pair,
670    hkdf: RING_HKDF_HMAC_SHA256,
671};
672
673/// Generate a NIST P-256, P-384 or P-512 key pair expressed as a raw big-endian fixed-length
674/// integer.
675///
676/// We must disambiguate the [`AsBigEndian`] trait in-use and this function uses
677/// [`AsBigEndian<EcPrivateKeyBin>`], which does not support [`agreement::X25519`].
678/// For generating X25519 keys see [`generate_x25519_key_pair`].
679fn generate_p_curve_key_pair(
680    alg: &'static agreement::Algorithm,
681) -> Result<(HpkePublicKey, HpkePrivateKey), Error> {
682    // We only initialize DH KEM instances that use this function as a key generator
683    // for non-X25519 algorithms. Debug assert this just in case since `AsBigEndian<EcPrivateKeyBin>`
684    // will panic for this algorithm.
685    debug_assert_ne!(alg, &agreement::X25519);
686    let (public_key, private_key) = generate_key_pair(alg)?;
687    let raw_private_key: EcPrivateKeyBin<'_> = private_key
688        .as_be_bytes()
689        .map_err(unspecified_err)?;
690    Ok((
691        public_key,
692        HpkePrivateKey::from(raw_private_key.as_ref().to_vec()),
693    ))
694}
695
696/// Generate a X25519 key pair expressed as a raw big-endian fixed-length
697/// integer.
698///
699/// We must disambiguate the [`AsBigEndian`] trait in-use and this function uses
700/// [`AsBigEndian<Curve25519SeedBin>`], which only supports [`agreement::X25519`].
701/// For generating P-256, P-384 and P-512 keys see [`generate_p_curve_key_pair`].
702fn generate_x25519_key_pair() -> Result<(HpkePublicKey, HpkePrivateKey), Error> {
703    let (public_key, private_key) = generate_key_pair(&agreement::X25519)?;
704    let raw_private_key: Curve25519SeedBin<'_> = private_key
705        .as_be_bytes()
706        .map_err(unspecified_err)?;
707    Ok((
708        public_key,
709        HpkePrivateKey::from(raw_private_key.as_ref().to_vec()),
710    ))
711}
712
713fn generate_key_pair(
714    alg: &'static agreement::Algorithm,
715) -> Result<(HpkePublicKey, agreement::PrivateKey), Error> {
716    let private_key = agreement::PrivateKey::generate(alg).map_err(unspecified_err)?;
717    let public_key = HpkePublicKey(
718        private_key
719            .compute_public_key()
720            .map_err(unspecified_err)?
721            .as_ref()
722            .to_vec(),
723    );
724    Ok((public_key, private_key))
725}
726
727/// KeySchedule holds the derived AEAD key, base nonce, and seq number
728/// common to both a [Sealer] and [Opener].
729struct KeySchedule<const KEY_SIZE: usize> {
730    aead: &'static aead::Algorithm,
731    key: AeadKey<KEY_SIZE>,
732    base_nonce: [u8; NONCE_LEN],
733    seq_num: u32,
734}
735
736impl<const KEY_SIZE: usize> KeySchedule<KEY_SIZE> {
737    /// See [RFC 9180 §5.2 "Encryption and Decryption"][0].
738    ///
739    /// [0]: https://www.rfc-editor.org/rfc/rfc9180.html#section-5.2
740    fn compute_nonce(&self) -> [u8; NONCE_LEN] {
741        // def Context<ROLE>.ComputeNonce(seq):
742        //   seq_bytes = I2OSP(seq, Nn)
743        //   return xor(self.base_nonce, seq_bytes)
744
745        // Each new N-byte nonce is conceptually two parts:
746        //   * N-4 bytes of the base nonce (0s in `nonce` to XOR in as-is).
747        //   * 4 bytes derived from the sequence number XOR the base nonce.
748        let mut nonce = [0; NONCE_LEN];
749        let seq_bytes = self.seq_num.to_be_bytes();
750        nonce[NONCE_LEN - seq_bytes.len()..].copy_from_slice(&seq_bytes);
751
752        for (n, &b) in nonce.iter_mut().zip(&self.base_nonce) {
753            *n ^= b;
754        }
755
756        nonce
757    }
758
759    /// See [RFC 9180 §5.2 "Encryption and Decryption"][0].
760    ///
761    /// [0]: https://www.rfc-editor.org/rfc/rfc9180.html#section-5.2
762    fn increment_seq_num(&mut self) -> Result<(), aws_lc_rs::error::Unspecified> {
763        // def Context<ROLE>.IncrementSeq():
764        //   if self.seq >= (1 << (8*Nn)) - 1:
765        //     raise MessageLimitReachedError
766        //   self.seq += 1
767
768        // Determine the maximum sequence number using the AEAD nonce's length in bits.
769        // Do this as an u128 to prevent overflowing.
770        let max_seq_num = (1u128 << (NONCE_LEN * 8)) - 1;
771
772        // Promote the u32 sequence number to an u128 and compare against the maximum allowed
773        // sequence number.
774        if u128::from(self.seq_num) >= max_seq_num {
775            return Err(aws_lc_rs::error::Unspecified);
776        }
777
778        self.seq_num += 1;
779        Ok(())
780    }
781}
782
783impl<const KEY_SIZE: usize> NonceSequence for &mut KeySchedule<KEY_SIZE> {
784    fn advance(&mut self) -> Result<Nonce, aws_lc_rs::error::Unspecified> {
785        let nonce = self.compute_nonce();
786        self.increment_seq_num()?;
787        Nonce::try_assume_unique_for_key(&nonce)
788    }
789}
790
791/// See [RFC 9180 §4 "Cryptographic Dependencies"][0].
792///
793/// [0]: https://www.rfc-editor.org/rfc/rfc9180.html#section-4
794fn labeled_extract_for_expand(
795    hkdf: &'static dyn HkdfPrkExtract,
796    suite_id: LabeledSuiteId,
797    salt: Option<&[u8]>,
798    label: Label,
799    ikm: &[u8],
800) -> Box<dyn HkdfExpander> {
801    // def LabeledExtract(salt, label, ikm):
802    //   labeled_ikm = concat("HPKE-v1", suite_id, label, ikm)
803    //   return Extract(salt, labeled_ikm)
804
805    let labeled_ikm = [&b"HPKE-v1"[..], &suite_id.encoded(), label.as_ref(), ikm].concat();
806    hkdf.extract_from_secret(salt, &labeled_ikm)
807}
808
809/// See [RFC 9180 §4 "Cryptographic Dependencies"][0].
810///
811/// [0]: https://www.rfc-editor.org/rfc/rfc9180.html#section-4
812fn labeled_extract_for_prk(
813    hkdf: &'static dyn HkdfPrkExtract,
814    suite_id: LabeledSuiteId,
815    salt: Option<&[u8]>,
816    label: Label,
817    ikm: &[u8],
818) -> Vec<u8> {
819    // def LabeledExtract(salt, label, ikm):
820    //   labeled_ikm = concat("HPKE-v1", suite_id, label, ikm)
821    //   return Extract(salt, labeled_ikm)
822
823    let labeled_ikm = [&b"HPKE-v1"[..], &suite_id.encoded(), label.as_ref(), ikm].concat();
824    hkdf.extract_prk_from_secret(salt, &labeled_ikm)
825}
826
827/// See [RFC 9180 §4 "Cryptographic Dependencies"][0].
828///
829/// [0]: https://www.rfc-editor.org/rfc/rfc9180.html#section-4
830fn labeled_expand<const L: usize>(
831    suite_id: LabeledSuiteId,
832    expander: Box<dyn HkdfExpander>,
833    label: Label,
834    kem_context: &[u8],
835) -> [u8; L] {
836    // def LabeledExpand(prk, label, info, L):
837    //   labeled_info = concat(I2OSP(L, 2), "HPKE-v1", suite_id,
838    //                         label, info)
839    //   return Expand(prk, labeled_info, L)
840
841    let output_len = u16::to_be_bytes(L as u16);
842    let info = &[
843        &output_len[..],
844        b"HPKE-v1",
845        &suite_id.encoded(),
846        label.as_ref(),
847        kem_context,
848    ];
849
850    expand(&*expander, info)
851}
852
853/// Label describes the possible labels for use with [labeled_extract_for_expand] and [labeled_expand].
854#[derive(Debug)]
855enum Label {
856    PskIdHash,
857    InfoHash,
858    Secret,
859    Key,
860    BaseNonce,
861    EaePrk,
862    SharedSecret,
863}
864
865impl AsRef<[u8]> for Label {
866    fn as_ref(&self) -> &[u8] {
867        match self {
868            Self::PskIdHash => b"psk_id_hash",
869            Self::InfoHash => b"info_hash",
870            Self::Secret => b"secret",
871            Self::Key => b"key",
872            Self::BaseNonce => b"base_nonce",
873            Self::EaePrk => b"eae_prk",
874            Self::SharedSecret => b"shared_secret",
875        }
876    }
877}
878
879/// LabeledSuiteId describes the possible suite ID values for use with [labeled_extract_for_expand] and
880/// [labeled_expand].
881#[derive(Debug, Copy, Clone)]
882enum LabeledSuiteId {
883    Hpke(HpkeSuite),
884    Kem(HpkeKem),
885}
886
887impl LabeledSuiteId {
888    /// The suite ID encoding depends on the context of use. In the general HPKE context,
889    /// we use a "HPKE" prefix and encode the entire ciphersuite. In the KEM context we use a
890    /// "KEM" prefix and only encode the KEM ID.
891    ///
892    /// See the bottom of [RFC 9180 §4](https://www.rfc-editor.org/rfc/rfc9180.html#section-4)
893    /// for more information.
894    fn encoded(&self) -> Vec<u8> {
895        match self {
896            Self::Hpke(suite) => [
897                &b"HPKE"[..],
898                &u16::from(suite.kem).to_be_bytes(),
899                &u16::from(suite.sym.kdf_id).to_be_bytes(),
900                &u16::from(suite.sym.aead_id).to_be_bytes(),
901            ]
902            .concat(),
903            Self::Kem(kem) => [&b"KEM"[..], &u16::from(*kem).to_be_bytes()].concat(),
904        }
905    }
906}
907
908/// A newtype wrapper for an unbound AEAD key.
909struct AeadKey<const KEY_LEN: usize>([u8; KEY_LEN]);
910
911impl<const KEY_LEN: usize> Drop for AeadKey<KEY_LEN> {
912    fn drop(&mut self) {
913        self.0.zeroize()
914    }
915}
916
917/// A newtype wrapper for a DH KEM shared secret.
918struct KemSharedSecret<const KDF_LEN: usize>([u8; KDF_LEN]);
919
920impl<const KDF_LEN: usize> Drop for KemSharedSecret<KDF_LEN> {
921    fn drop(&mut self) {
922        self.0.zeroize();
923    }
924}
925
926fn key_rejected_err(e: aws_lc_rs::error::KeyRejected) -> Error {
927    Error::Other(OtherError::new(e))
928}
929
930// The `cipher::chacha::KEY_LEN` const is not exported, so we copy it here:
931// https://github.com/aws/aws-lc-rs/blob/0186ef7bb1a4d7e140bae8074a9871f49afedf1b/aws-lc-rs/src/cipher/chacha.rs#L13
932const CHACHA_KEY_LEN: usize = 32;
933
934static RING_HKDF_HMAC_SHA256: &HkdfUsingHmac<'static> = &HkdfUsingHmac(&HMAC_SHA256);
935static RING_HKDF_HMAC_SHA384: &HkdfUsingHmac<'static> = &HkdfUsingHmac(&HMAC_SHA384);
936static RING_HKDF_HMAC_SHA512: &HkdfUsingHmac<'static> = &HkdfUsingHmac(&HMAC_SHA512);
937
938#[cfg(test)]
939mod tests {
940    use alloc::{format, vec};
941
942    use super::*;
943
944    #[test]
945    fn smoke_test() {
946        for suite in ALL_SUPPORTED_SUITES {
947            _ = format!("{suite:?}"); // HpkeAwsLcRs suites should be Debug.
948
949            // We should be able to generate a random keypair.
950            let (pk, sk) = suite.generate_key_pair().unwrap();
951
952            // Info value corresponds to the first RFC 9180 base mode test vector.
953            let info = &[
954                0x4f, 0x64, 0x65, 0x20, 0x6f, 0x6e, 0x20, 0x61, 0x20, 0x47, 0x72, 0x65, 0x63, 0x69,
955                0x61, 0x6e, 0x20, 0x55, 0x72, 0x6e,
956            ][..];
957
958            // We should be able to set up a sealer.
959            let (enc, mut sealer) = suite.setup_sealer(info, &pk).unwrap();
960
961            _ = format!("{sealer:?}"); // Sealer should be Debug.
962
963            // Setting up a sealer with an invalid public key should fail.
964            let bad_setup_res = suite.setup_sealer(info, &HpkePublicKey(vec![]));
965            assert!(matches!(bad_setup_res.unwrap_err(), Error::Other(_)));
966
967            // We should be able to seal some plaintext.
968            let aad = &[0xC0, 0xFF, 0xEE];
969            let pt = &[0xF0, 0x0D];
970            let ct = sealer.seal(aad, pt).unwrap();
971
972            // We should be able to set up an opener.
973            let mut opener = suite
974                .setup_opener(&enc, info, &sk)
975                .unwrap();
976            _ = format!("{opener:?}"); // Opener should be Debug.
977
978            // Setting up an opener with an invalid private key should fail.
979            let bad_key_res = suite.setup_opener(&enc, info, &HpkePrivateKey::from(vec![]));
980            assert!(matches!(bad_key_res.unwrap_err(), Error::Other(_)));
981
982            // Opening the plaintext should work with the correct opener and aad.
983            let pt_prime = opener.open(aad, &ct).unwrap();
984            assert_eq!(pt_prime, pt);
985
986            // Opening the plaintext with the correct opener and wrong aad should fail.
987            let open_res = opener.open(&[0x0], &ct);
988            assert!(matches!(open_res.unwrap_err(), Error::Other(_)));
989
990            // Opening the plaintext with the wrong opener should fail.
991            let mut sk_rm_prime = sk.secret_bytes().to_vec();
992            sk_rm_prime[10] ^= 0xFF; // Corrupt a byte of the private key.
993            let mut opener_two = suite
994                .setup_opener(&enc, info, &HpkePrivateKey::from(sk_rm_prime))
995                .unwrap();
996            let open_res = opener_two.open(aad, &ct);
997            assert!(matches!(open_res.unwrap_err(), Error::Other(_)));
998        }
999    }
1000
1001    #[cfg(not(feature = "fips"))] // Ensure all supported suites are available to test.
1002    #[test]
1003    fn test_fips() {
1004        let testcases: &[(&dyn Hpke, bool)] = &[
1005            // FIPS compatible.
1006            (DH_KEM_P256_HKDF_SHA256_AES_128, true),
1007            (DH_KEM_P256_HKDF_SHA256_AES_256, true),
1008            (DH_KEM_P384_HKDF_SHA384_AES_128, true),
1009            (DH_KEM_P384_HKDF_SHA384_AES_256, true),
1010            (DH_KEM_P521_HKDF_SHA512_AES_128, true),
1011            (DH_KEM_P521_HKDF_SHA512_AES_256, true),
1012            // AEAD is not FIPS compatible.
1013            (DH_KEM_P256_HKDF_SHA256_CHACHA20_POLY1305, false),
1014            (DH_KEM_P384_HKDF_SHA384_CHACHA20_POLY1305, false),
1015            (DH_KEM_P521_HKDF_SHA512_CHACHA20_POLY1305, false),
1016            // KEM is not FIPS compatible.
1017            (DH_KEM_X25519_HKDF_SHA256_AES_128, false),
1018            (DH_KEM_X25519_HKDF_SHA256_AES_256, false),
1019            (DH_KEM_X25519_HKDF_SHA256_CHACHA20_POLY1305, false),
1020        ];
1021        for (suite, expected) in testcases {
1022            assert_eq!(suite.fips(), *expected);
1023        }
1024    }
1025}
1026
1027#[cfg(test)]
1028mod rfc_tests {
1029    use alloc::string::String;
1030    use std::fs::File;
1031    use std::println;
1032
1033    use serde::Deserialize;
1034
1035    use super::*;
1036
1037    /// Confirm open/seal operations work using the test vectors from [RFC 9180 Appendix A].
1038    ///
1039    /// [RFC 9180 Appendix A]: https://www.rfc-editor.org/rfc/rfc9180#TestVectors
1040    #[test]
1041    fn check_test_vectors() {
1042        for (idx, vec) in test_vectors().into_iter().enumerate() {
1043            let Some(hpke) = vec.applicable() else {
1044                println!("skipping inapplicable vector {idx}");
1045                continue;
1046            };
1047
1048            println!("testing vector {idx}");
1049            let pk_r = HpkePublicKey(hex::decode(vec.pk_rm).unwrap());
1050            let sk_r = HpkePrivateKey::from(hex::decode(vec.sk_rm).unwrap());
1051            let sk_em = hex::decode(vec.sk_em).unwrap();
1052            let info = hex::decode(vec.info).unwrap();
1053            let expected_enc = hex::decode(vec.enc).unwrap();
1054
1055            let (enc, mut sealer) = hpke
1056                .setup_test_sealer(&info, &pk_r, &sk_em)
1057                .unwrap();
1058            assert_eq!(enc.0, expected_enc);
1059
1060            let mut opener = hpke
1061                .setup_opener(&enc, &info, &sk_r)
1062                .unwrap();
1063
1064            for test_encryption in vec.encryptions {
1065                let aad = hex::decode(test_encryption.aad).unwrap();
1066                let pt = hex::decode(test_encryption.pt).unwrap();
1067                let expected_ct = hex::decode(test_encryption.ct).unwrap();
1068
1069                let ciphertext = sealer.seal(&aad, &pt).unwrap();
1070                assert_eq!(ciphertext, expected_ct);
1071
1072                let plaintext = opener.open(&aad, &ciphertext).unwrap();
1073                assert_eq!(plaintext, pt);
1074            }
1075        }
1076    }
1077
1078    trait TestHpke: Hpke {
1079        fn setup_test_sealer(
1080            &self,
1081            info: &[u8],
1082            pub_key: &HpkePublicKey,
1083            sk_em: &[u8],
1084        ) -> Result<(EncapsulatedSecret, Box<dyn HpkeSealer + 'static>), Error>;
1085    }
1086
1087    impl<const KEY_SIZE: usize, const KDF_SIZE: usize> TestHpke for HpkeAwsLcRs<KEY_SIZE, KDF_SIZE> {
1088        fn setup_test_sealer(
1089            &self,
1090            info: &[u8],
1091            pub_key: &HpkePublicKey,
1092            sk_em: &[u8],
1093        ) -> Result<(EncapsulatedSecret, Box<dyn HpkeSealer + 'static>), Error> {
1094            let (encap, sealer) = Sealer::test_only_new(self, info, pub_key, sk_em)?;
1095            Ok((encap, Box::new(sealer)))
1096        }
1097    }
1098
1099    static TEST_SUITES: &[&dyn TestHpke] = &[
1100        DH_KEM_P256_HKDF_SHA256_AES_128,
1101        DH_KEM_P256_HKDF_SHA256_AES_256,
1102        #[cfg(not(feature = "fips"))]
1103        DH_KEM_P256_HKDF_SHA256_CHACHA20_POLY1305,
1104        DH_KEM_P384_HKDF_SHA384_AES_128,
1105        DH_KEM_P384_HKDF_SHA384_AES_256,
1106        #[cfg(not(feature = "fips"))]
1107        DH_KEM_P384_HKDF_SHA384_CHACHA20_POLY1305,
1108        DH_KEM_P521_HKDF_SHA512_AES_128,
1109        DH_KEM_P521_HKDF_SHA512_AES_256,
1110        #[cfg(not(feature = "fips"))]
1111        DH_KEM_P521_HKDF_SHA512_CHACHA20_POLY1305,
1112        #[cfg(not(feature = "fips"))]
1113        DH_KEM_X25519_HKDF_SHA256_AES_128,
1114        #[cfg(not(feature = "fips"))]
1115        DH_KEM_X25519_HKDF_SHA256_AES_256,
1116        #[cfg(not(feature = "fips"))]
1117        DH_KEM_X25519_HKDF_SHA256_CHACHA20_POLY1305,
1118    ];
1119
1120    #[derive(Deserialize, Debug)]
1121    struct TestVector {
1122        mode: u8,
1123        kem_id: u16,
1124        kdf_id: u16,
1125        aead_id: u16,
1126        info: String,
1127        #[serde(rename(deserialize = "pkRm"))]
1128        pk_rm: String,
1129        #[serde(rename(deserialize = "skRm"))]
1130        sk_rm: String,
1131        #[serde(rename(deserialize = "skEm"))]
1132        sk_em: String,
1133        enc: String,
1134        encryptions: Vec<TestEncryption>,
1135    }
1136
1137    #[derive(Deserialize, Debug)]
1138    struct TestEncryption {
1139        aad: String,
1140        pt: String,
1141        ct: String,
1142    }
1143
1144    impl TestVector {
1145        fn suite(&self) -> HpkeSuite {
1146            HpkeSuite {
1147                kem: HpkeKem::from(self.kem_id),
1148                sym: HpkeSymmetricCipherSuite {
1149                    kdf_id: HpkeKdf::from(self.kdf_id),
1150                    aead_id: HpkeAead::from(self.aead_id),
1151                },
1152            }
1153        }
1154
1155        fn applicable(&self) -> Option<&'static dyn TestHpke> {
1156            // Only base mode test vectors for supported suites are applicable.
1157            if self.mode != 0 {
1158                return None;
1159            }
1160
1161            Self::lookup_suite(self.suite(), TEST_SUITES)
1162        }
1163
1164        fn lookup_suite(
1165            suite: HpkeSuite,
1166            supported: &[&'static dyn TestHpke],
1167        ) -> Option<&'static dyn TestHpke> {
1168            supported
1169                .iter()
1170                .find(|s| s.suite() == suite)
1171                .copied()
1172        }
1173    }
1174
1175    fn test_vectors() -> Vec<TestVector> {
1176        serde_json::from_reader(
1177            &mut File::open("../rustls-provider-test/tests/rfc-9180-test-vectors.json")
1178                .expect("failed to open test vectors data file"),
1179        )
1180        .expect("failed to deserialize test vectors")
1181    }
1182}