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