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