Skip to main content

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::{self, Aad, LessSafeKey, NONCE_LEN, Nonce, UnboundKey};
6use aws_lc_rs::agreement;
7use aws_lc_rs::cipher::{AES_128_KEY_LEN, AES_256_KEY_LEN};
8use aws_lc_rs::digest::{SHA256_OUTPUT_LEN, SHA384_OUTPUT_LEN, SHA512_OUTPUT_LEN};
9use aws_lc_rs::encoding::{AsBigEndian, Curve25519SeedBin, EcPrivateKeyBin};
10use zeroize::Zeroize;
11
12use crate::crypto::aws_lc_rs::hmac::{HMAC_SHA256, HMAC_SHA384, HMAC_SHA512};
13use crate::crypto::aws_lc_rs::unspecified_err;
14use crate::crypto::hpke::{
15    EncapsulatedSecret, Hpke, HpkeOpener, HpkePrivateKey, HpkePublicKey, HpkeSealer, HpkeSuite,
16};
17use crate::crypto::tls13::{HkdfExpander, HkdfPrkExtract, HkdfUsingHmac, expand};
18use crate::msgs::enums::{HpkeAead, HpkeKdf, HpkeKem};
19use crate::msgs::handshake::HpkeSymmetricCipherSuite;
20#[cfg(feature = "std")]
21use crate::sync::Arc;
22use crate::{Error, OtherError};
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 sealing_key = LessSafeKey::new(key);
443
444        let nonce = self.key_schedule.compute_nonce();
445        self.key_schedule
446            .increment_seq_num()
447            .map_err(unspecified_err)?;
448
449        let mut in_out_buffer = Vec::from(plaintext);
450        sealing_key
451            .seal_in_place_append_tag(nonce, Aad::from(aad), &mut in_out_buffer)
452            .map_err(unspecified_err)?;
453
454        Ok(in_out_buffer)
455    }
456}
457
458impl<const KEY_SIZE: usize, const KDF_SIZE: usize> Debug for Sealer<KEY_SIZE, KDF_SIZE> {
459    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
460        f.debug_struct("Sealer").finish()
461    }
462}
463
464/// Adapts a [KeySchedule] and [AeadKey] for the role of a [HpkeOpener].
465struct Opener<const KEY_SIZE: usize, const KDF_SIZE: usize> {
466    key_schedule: KeySchedule<KEY_SIZE>,
467}
468
469impl<const KEY_SIZE: usize, const KDF_SIZE: usize> Opener<KEY_SIZE, KDF_SIZE> {
470    /// See [RFC 9180 §5.1.1 "Encryption to a Public Key"][0].
471    ///
472    /// [0]: https://www.rfc-editor.org/rfc/rfc9180.html#section-5.1.1
473    fn new(
474        suite: &HpkeAwsLcRs<KEY_SIZE, KDF_SIZE>,
475        enc: &EncapsulatedSecret,
476        info: &[u8],
477        secret_key: &HpkePrivateKey,
478    ) -> Result<Self, Error> {
479        // def SetupBaseR(enc, skR, info):
480        //   shared_secret = Decap(enc, skR)
481        //   return KeyScheduleR(mode_base, shared_secret, info,
482        //                       default_psk, default_psk_id)
483        Ok(Self {
484            key_schedule: suite.key_schedule(suite.dh_kem.decap(enc, secret_key)?, info)?,
485        })
486    }
487}
488
489impl<const KEY_SIZE: usize, const KDF_SIZE: usize> HpkeOpener for Opener<KEY_SIZE, KDF_SIZE> {
490    fn open(&mut self, aad: &[u8], ciphertext: &[u8]) -> Result<Vec<u8>, Error> {
491        // def ContextR.Open(aad, ct):
492        //   pt = Open(self.key, self.ComputeNonce(self.seq), aad, ct)
493        //   if pt == OpenError:
494        //     raise OpenError
495        //   self.IncrementSeq()
496        //   return pt
497
498        let key = UnboundKey::new(self.key_schedule.aead, &self.key_schedule.key.0)
499            .map_err(unspecified_err)?;
500        let opening_key = LessSafeKey::new(key);
501
502        let mut in_out_buffer = Vec::from(ciphertext);
503        let plaintext = opening_key
504            .open_in_place(
505                self.key_schedule.compute_nonce(),
506                Aad::from(aad),
507                &mut in_out_buffer,
508            )
509            .map_err(unspecified_err)?;
510        self.key_schedule
511            .increment_seq_num()
512            .map_err(unspecified_err)?;
513
514        Ok(plaintext.to_vec())
515    }
516}
517
518impl<const KEY_SIZE: usize, const KDF_SIZE: usize> Debug for Opener<KEY_SIZE, KDF_SIZE> {
519    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
520        f.debug_struct("Opener").finish()
521    }
522}
523
524/// A Diffie-Hellman (DH) based Key Encapsulation Mechanism (KEM).
525///
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
529struct DhKem<const KDF_SIZE: usize> {
530    id: HpkeKem,
531    agreement_algorithm: &'static agreement::Algorithm,
532    key_generator:
533        &'static (dyn Fn() -> Result<(HpkePublicKey, HpkePrivateKey), Error> + Send + Sync),
534    hkdf: &'static dyn HkdfPrkExtract,
535}
536
537impl<const KDF_SIZE: usize> DhKem<KDF_SIZE> {
538    /// See [RFC 9180 §4.1 "DH-Based KEM (DHKEM)"][0].
539    ///
540    /// [0]: https://www.rfc-editor.org/rfc/rfc9180.html#section-4.1
541    fn encap(
542        &self,
543        recipient: &HpkePublicKey,
544    ) -> Result<(KemSharedSecret<KDF_SIZE>, EncapsulatedSecret), Error> {
545        // def Encap(pkR):
546        //   skE, pkE = GenerateKeyPair()
547
548        let sk_e =
549            agreement::PrivateKey::generate(self.agreement_algorithm).map_err(unspecified_err)?;
550        self.encap_impl(recipient, sk_e)
551    }
552
553    /// A test-only encap operation that uses a fixed `test_only_ske` instead of generating
554    /// one randomly.
555    #[cfg(test)]
556    fn test_only_encap(
557        &self,
558        recipient: &HpkePublicKey,
559        test_only_ske: &[u8],
560    ) -> Result<(KemSharedSecret<KDF_SIZE>, EncapsulatedSecret), Error> {
561        // For test contexts only, we accept a static sk_e as an argument.
562        let sk_e = agreement::PrivateKey::from_private_key(self.agreement_algorithm, test_only_ske)
563            .map_err(key_rejected_err)?;
564        self.encap_impl(recipient, sk_e)
565    }
566
567    fn encap_impl(
568        &self,
569        recipient: &HpkePublicKey,
570        sk_e: agreement::PrivateKey,
571    ) -> Result<(KemSharedSecret<KDF_SIZE>, EncapsulatedSecret), Error> {
572        // def Encap(pkR):
573        //   skE, pkE = GenerateKeyPair()
574        //   dh = DH(skE, pkR)
575        //   enc = SerializePublicKey(pkE)
576        //
577        //   pkRm = SerializePublicKey(pkR)
578        //   kem_context = concat(enc, pkRm)
579        //
580        //   shared_secret = ExtractAndExpand(dh, kem_context)
581        //   return shared_secret, enc
582
583        let enc = sk_e
584            .compute_public_key()
585            .map_err(unspecified_err)?;
586        let pk_r = agreement::UnparsedPublicKey::new(self.agreement_algorithm, &recipient.0);
587        let kem_context = [enc.as_ref(), pk_r.bytes()].concat();
588
589        let shared_secret = agreement::agree(&sk_e, pk_r, aws_lc_rs::error::Unspecified, |dh| {
590            Ok(self.extract_and_expand(dh, &kem_context))
591        })
592        .map_err(unspecified_err)?;
593
594        Ok((
595            KemSharedSecret(shared_secret),
596            EncapsulatedSecret(enc.as_ref().into()),
597        ))
598    }
599
600    /// See [RFC 9180 §4.1 "DH-Based KEM (DHKEM)"][0].
601    ///
602    /// [0]: https://www.rfc-editor.org/rfc/rfc9180.html#section-4.1
603    fn decap(
604        &self,
605        enc: &EncapsulatedSecret,
606        recipient: &HpkePrivateKey,
607    ) -> Result<KemSharedSecret<KDF_SIZE>, Error> {
608        // def Decap(enc, skR):
609        //   pkE = DeserializePublicKey(enc)
610        //   dh = DH(skR, pkE)
611        //
612        //   pkRm = SerializePublicKey(pk(skR))
613        //   kem_context = concat(enc, pkRm)
614        //
615        //   shared_secret = ExtractAndExpand(dh, kem_context)
616        //   return shared_secret
617
618        let pk_e = agreement::UnparsedPublicKey::new(self.agreement_algorithm, &enc.0);
619        let sk_r = agreement::PrivateKey::from_private_key(
620            self.agreement_algorithm,
621            recipient.secret_bytes(),
622        )
623        .map_err(key_rejected_err)?;
624        let pk_rm = sk_r
625            .compute_public_key()
626            .map_err(unspecified_err)?;
627        let kem_context = [&enc.0, pk_rm.as_ref()].concat();
628
629        let shared_secret = agreement::agree(&sk_r, pk_e, aws_lc_rs::error::Unspecified, |dh| {
630            Ok(self.extract_and_expand(dh, &kem_context))
631        })
632        .map_err(unspecified_err)?;
633
634        Ok(KemSharedSecret(shared_secret))
635    }
636
637    /// See [RFC 9180 §4.1 "DH-Based KEM (DHKEM)"][0].
638    ///
639    /// [0]: https://www.rfc-editor.org/rfc/rfc9180.html#section-4.1
640    fn extract_and_expand(&self, dh: &[u8], kem_context: &[u8]) -> [u8; KDF_SIZE] {
641        // def ExtractAndExpand(dh, kem_context):
642        //   eae_prk = LabeledExtract("", "eae_prk", dh)
643        //   shared_secret = LabeledExpand(eae_prk, "shared_secret",
644        //                                 kem_context, Nsecret)
645        //   return shared_secret
646
647        let suite_id = LabeledSuiteId::Kem(self.id);
648        labeled_expand(
649            suite_id,
650            labeled_extract_for_expand(self.hkdf, suite_id, None, Label::EaePrk, dh),
651            Label::SharedSecret,
652            kem_context,
653        )
654    }
655}
656
657static DH_KEM_P256_HKDF_SHA256: &DhKem<SHA256_OUTPUT_LEN> = &DhKem {
658    id: HpkeKem::DHKEM_P256_HKDF_SHA256,
659    agreement_algorithm: &agreement::ECDH_P256,
660    key_generator: &|| generate_p_curve_key_pair(&agreement::ECDH_P256),
661    hkdf: RING_HKDF_HMAC_SHA256,
662};
663
664static DH_KEM_P384_HKDF_SHA384: &DhKem<SHA384_OUTPUT_LEN> = &DhKem {
665    id: HpkeKem::DHKEM_P384_HKDF_SHA384,
666    agreement_algorithm: &agreement::ECDH_P384,
667    key_generator: &|| generate_p_curve_key_pair(&agreement::ECDH_P384),
668    hkdf: RING_HKDF_HMAC_SHA384,
669};
670
671static DH_KEM_P521_HKDF_SHA512: &DhKem<SHA512_OUTPUT_LEN> = &DhKem {
672    id: HpkeKem::DHKEM_P521_HKDF_SHA512,
673    agreement_algorithm: &agreement::ECDH_P521,
674    key_generator: &|| generate_p_curve_key_pair(&agreement::ECDH_P521),
675    hkdf: RING_HKDF_HMAC_SHA512,
676};
677
678static DH_KEM_X25519_HKDF_SHA256: &DhKem<SHA256_OUTPUT_LEN> = &DhKem {
679    id: HpkeKem::DHKEM_X25519_HKDF_SHA256,
680    agreement_algorithm: &agreement::X25519,
681    key_generator: &generate_x25519_key_pair,
682    hkdf: RING_HKDF_HMAC_SHA256,
683};
684
685/// Generate a NIST P-256, P-384 or P-512 key pair expressed as a raw big-endian fixed-length
686/// integer.
687///
688/// We must disambiguate the [`AsBigEndian`] trait in-use and this function uses
689/// [`AsBigEndian<EcPrivateKeyBin>`], which does not support [`agreement::X25519`].
690/// For generating X25519 keys see [`generate_x25519_key_pair`].
691fn generate_p_curve_key_pair(
692    alg: &'static agreement::Algorithm,
693) -> Result<(HpkePublicKey, HpkePrivateKey), Error> {
694    // We only initialize DH KEM instances that use this function as a key generator
695    // for non-X25519 algorithms. Debug assert this just in case since `AsBigEndian<EcPrivateKeyBin>`
696    // will panic for this algorithm.
697    debug_assert_ne!(alg, &agreement::X25519);
698    let (public_key, private_key) = generate_key_pair(alg)?;
699    let raw_private_key: EcPrivateKeyBin<'_> = private_key
700        .as_be_bytes()
701        .map_err(unspecified_err)?;
702    Ok((
703        public_key,
704        HpkePrivateKey::from(raw_private_key.as_ref().to_vec()),
705    ))
706}
707
708/// Generate a X25519 key pair expressed as a raw big-endian fixed-length
709/// integer.
710///
711/// We must disambiguate the [`AsBigEndian`] trait in-use and this function uses
712/// [`AsBigEndian<Curve25519SeedBin>`], which only supports [`agreement::X25519`].
713/// For generating P-256, P-384 and P-512 keys see [`generate_p_curve_key_pair`].
714fn generate_x25519_key_pair() -> Result<(HpkePublicKey, HpkePrivateKey), Error> {
715    let (public_key, private_key) = generate_key_pair(&agreement::X25519)?;
716    let raw_private_key: Curve25519SeedBin<'_> = private_key
717        .as_be_bytes()
718        .map_err(unspecified_err)?;
719    Ok((
720        public_key,
721        HpkePrivateKey::from(raw_private_key.as_ref().to_vec()),
722    ))
723}
724
725fn generate_key_pair(
726    alg: &'static agreement::Algorithm,
727) -> Result<(HpkePublicKey, agreement::PrivateKey), Error> {
728    let private_key = agreement::PrivateKey::generate(alg).map_err(unspecified_err)?;
729    let public_key = HpkePublicKey(
730        private_key
731            .compute_public_key()
732            .map_err(unspecified_err)?
733            .as_ref()
734            .to_vec(),
735    );
736    Ok((public_key, private_key))
737}
738
739/// KeySchedule holds the derived AEAD key, base nonce, and seq number
740/// common to both a [Sealer] and [Opener].
741struct KeySchedule<const KEY_SIZE: usize> {
742    aead: &'static aead::Algorithm,
743    key: AeadKey<KEY_SIZE>,
744    base_nonce: [u8; NONCE_LEN],
745    seq_num: u32,
746}
747
748impl<const KEY_SIZE: usize> KeySchedule<KEY_SIZE> {
749    /// See [RFC 9180 §5.2 "Encryption and Decryption"][0].
750    ///
751    /// [0]: https://www.rfc-editor.org/rfc/rfc9180.html#section-5.2
752    fn compute_nonce(&self) -> Nonce {
753        // def Context<ROLE>.ComputeNonce(seq):
754        //   seq_bytes = I2OSP(seq, Nn)
755        //   return xor(self.base_nonce, seq_bytes)
756
757        // Each new N-byte nonce is conceptually two parts:
758        //   * N-4 bytes of the base nonce (0s in `nonce` to XOR in as-is).
759        //   * 4 bytes derived from the sequence number XOR the base nonce.
760        let mut nonce = [0; NONCE_LEN];
761        let seq_bytes = self.seq_num.to_be_bytes();
762        nonce[NONCE_LEN - seq_bytes.len()..].copy_from_slice(&seq_bytes);
763
764        for (n, &b) in nonce.iter_mut().zip(&self.base_nonce) {
765            *n ^= b;
766        }
767
768        Nonce::assume_unique_for_key(nonce)
769    }
770
771    /// See [RFC 9180 §5.2 "Encryption and Decryption"][0].
772    ///
773    /// [0]: https://www.rfc-editor.org/rfc/rfc9180.html#section-5.2
774    fn increment_seq_num(&mut self) -> Result<(), aws_lc_rs::error::Unspecified> {
775        // def Context<ROLE>.IncrementSeq():
776        //   if self.seq >= (1 << (8*Nn)) - 1:
777        //     raise MessageLimitReachedError
778        //   self.seq += 1
779
780        // Determine the maximum sequence number using the AEAD nonce's length in bits.
781        // Do this as an u128 to prevent overflowing.
782        let max_seq_num = (1u128 << (NONCE_LEN * 8)) - 1;
783
784        // Promote the u32 sequence number to an u128 and compare against the maximum allowed
785        // sequence number.
786        if u128::from(self.seq_num) >= max_seq_num {
787            return Err(aws_lc_rs::error::Unspecified);
788        }
789
790        self.seq_num += 1;
791        Ok(())
792    }
793}
794
795/// See [RFC 9180 §4 "Cryptographic Dependencies"][0].
796///
797/// [0]: https://www.rfc-editor.org/rfc/rfc9180.html#section-4
798fn labeled_extract_for_expand(
799    hkdf: &'static dyn HkdfPrkExtract,
800    suite_id: LabeledSuiteId,
801    salt: Option<&[u8]>,
802    label: Label,
803    ikm: &[u8],
804) -> Box<dyn HkdfExpander> {
805    // def LabeledExtract(salt, label, ikm):
806    //   labeled_ikm = concat("HPKE-v1", suite_id, label, ikm)
807    //   return Extract(salt, labeled_ikm)
808
809    let labeled_ikm = [&b"HPKE-v1"[..], &suite_id.encoded(), label.as_ref(), ikm].concat();
810    hkdf.extract_from_secret(salt, &labeled_ikm)
811}
812
813/// See [RFC 9180 §4 "Cryptographic Dependencies"][0].
814///
815/// [0]: https://www.rfc-editor.org/rfc/rfc9180.html#section-4
816fn labeled_extract_for_prk(
817    hkdf: &'static dyn HkdfPrkExtract,
818    suite_id: LabeledSuiteId,
819    salt: Option<&[u8]>,
820    label: Label,
821    ikm: &[u8],
822) -> Vec<u8> {
823    // def LabeledExtract(salt, label, ikm):
824    //   labeled_ikm = concat("HPKE-v1", suite_id, label, ikm)
825    //   return Extract(salt, labeled_ikm)
826
827    let labeled_ikm = [&b"HPKE-v1"[..], &suite_id.encoded(), label.as_ref(), ikm].concat();
828    hkdf.extract_prk_from_secret(salt, &labeled_ikm)
829}
830
831/// See [RFC 9180 §4 "Cryptographic Dependencies"][0].
832///
833/// [0]: https://www.rfc-editor.org/rfc/rfc9180.html#section-4
834fn labeled_expand<const L: usize>(
835    suite_id: LabeledSuiteId,
836    expander: Box<dyn HkdfExpander>,
837    label: Label,
838    kem_context: &[u8],
839) -> [u8; L] {
840    // def LabeledExpand(prk, label, info, L):
841    //   labeled_info = concat(I2OSP(L, 2), "HPKE-v1", suite_id,
842    //                         label, info)
843    //   return Expand(prk, labeled_info, L)
844
845    let output_len = u16::to_be_bytes(L as u16);
846    let info = &[
847        &output_len[..],
848        b"HPKE-v1",
849        &suite_id.encoded(),
850        label.as_ref(),
851        kem_context,
852    ];
853
854    expand(&*expander, info)
855}
856
857/// Label describes the possible labels for use with [labeled_extract_for_expand] and [labeled_expand].
858#[derive(Debug)]
859enum Label {
860    PskIdHash,
861    InfoHash,
862    Secret,
863    Key,
864    BaseNonce,
865    EaePrk,
866    SharedSecret,
867}
868
869impl AsRef<[u8]> for Label {
870    fn as_ref(&self) -> &[u8] {
871        match self {
872            Self::PskIdHash => b"psk_id_hash",
873            Self::InfoHash => b"info_hash",
874            Self::Secret => b"secret",
875            Self::Key => b"key",
876            Self::BaseNonce => b"base_nonce",
877            Self::EaePrk => b"eae_prk",
878            Self::SharedSecret => b"shared_secret",
879        }
880    }
881}
882
883/// LabeledSuiteId describes the possible suite ID values for use with [labeled_extract_for_expand] and
884/// [labeled_expand].
885#[derive(Debug, Copy, Clone)]
886enum LabeledSuiteId {
887    Hpke(HpkeSuite),
888    Kem(HpkeKem),
889}
890
891impl LabeledSuiteId {
892    /// The suite ID encoding depends on the context of use. In the general HPKE context,
893    /// we use a "HPKE" prefix and encode the entire ciphersuite. In the KEM context we use a
894    /// "KEM" prefix and only encode the KEM ID.
895    ///
896    /// See the bottom of [RFC 9180 §4](https://www.rfc-editor.org/rfc/rfc9180.html#section-4)
897    /// for more information.
898    fn encoded(&self) -> Vec<u8> {
899        match self {
900            Self::Hpke(suite) => [
901                &b"HPKE"[..],
902                &u16::from(suite.kem).to_be_bytes(),
903                &u16::from(suite.sym.kdf_id).to_be_bytes(),
904                &u16::from(suite.sym.aead_id).to_be_bytes(),
905            ]
906            .concat(),
907            Self::Kem(kem) => [&b"KEM"[..], &u16::from(*kem).to_be_bytes()].concat(),
908        }
909    }
910}
911
912/// A newtype wrapper for an unbound AEAD key.
913struct AeadKey<const KEY_LEN: usize>([u8; KEY_LEN]);
914
915impl<const KEY_LEN: usize> Drop for AeadKey<KEY_LEN> {
916    fn drop(&mut self) {
917        self.0.zeroize()
918    }
919}
920
921/// A newtype wrapper for a DH KEM shared secret.
922struct KemSharedSecret<const KDF_LEN: usize>([u8; KDF_LEN]);
923
924impl<const KDF_LEN: usize> Drop for KemSharedSecret<KDF_LEN> {
925    fn drop(&mut self) {
926        self.0.zeroize();
927    }
928}
929
930fn key_rejected_err(_e: aws_lc_rs::error::KeyRejected) -> Error {
931    #[cfg(feature = "std")]
932    {
933        Error::Other(OtherError(Arc::new(_e)))
934    }
935    #[cfg(not(feature = "std"))]
936    {
937        Error::Other(OtherError())
938    }
939}
940
941// The `cipher::chacha::KEY_LEN` const is not exported, so we copy it here:
942// https://github.com/aws/aws-lc-rs/blob/0186ef7bb1a4d7e140bae8074a9871f49afedf1b/aws-lc-rs/src/cipher/chacha.rs#L13
943const CHACHA_KEY_LEN: usize = 32;
944
945static RING_HKDF_HMAC_SHA256: &HkdfUsingHmac<'static> = &HkdfUsingHmac(&HMAC_SHA256);
946static RING_HKDF_HMAC_SHA384: &HkdfUsingHmac<'static> = &HkdfUsingHmac(&HMAC_SHA384);
947static RING_HKDF_HMAC_SHA512: &HkdfUsingHmac<'static> = &HkdfUsingHmac(&HMAC_SHA512);
948
949#[cfg(test)]
950mod tests {
951    use alloc::{format, vec};
952
953    use super::*;
954
955    #[test]
956    fn smoke_test() {
957        for suite in ALL_SUPPORTED_SUITES {
958            _ = format!("{suite:?}"); // HpkeAwsLcRs suites should be Debug.
959
960            // We should be able to generate a random keypair.
961            let (pk, sk) = suite.generate_key_pair().unwrap();
962
963            // Info value corresponds to the first RFC 9180 base mode test vector.
964            let info = &[
965                0x4f, 0x64, 0x65, 0x20, 0x6f, 0x6e, 0x20, 0x61, 0x20, 0x47, 0x72, 0x65, 0x63, 0x69,
966                0x61, 0x6e, 0x20, 0x55, 0x72, 0x6e,
967            ][..];
968
969            // We should be able to set up a sealer.
970            let (enc, mut sealer) = suite.setup_sealer(info, &pk).unwrap();
971
972            _ = format!("{sealer:?}"); // Sealer should be Debug.
973
974            // Setting up a sealer with an invalid public key should fail.
975            let bad_setup_res = suite.setup_sealer(info, &HpkePublicKey(vec![]));
976            assert!(matches!(bad_setup_res.unwrap_err(), Error::Other(_)));
977
978            // We should be able to seal some plaintext.
979            let aad = &[0xC0, 0xFF, 0xEE];
980            let pt = &[0xF0, 0x0D];
981            let ct = sealer.seal(aad, pt).unwrap();
982
983            // We should be able to set up an opener.
984            let mut opener = suite
985                .setup_opener(&enc, info, &sk)
986                .unwrap();
987            _ = format!("{opener:?}"); // Opener should be Debug.
988
989            // Setting up an opener with an invalid private key should fail.
990            let bad_key_res = suite.setup_opener(&enc, info, &HpkePrivateKey::from(vec![]));
991            assert!(matches!(bad_key_res.unwrap_err(), Error::Other(_)));
992
993            // Opening the plaintext should work with the correct opener and aad.
994            let pt_prime = opener.open(aad, &ct).unwrap();
995            assert_eq!(pt_prime, pt);
996
997            // Opening the plaintext with the correct opener and wrong aad should fail.
998            let open_res = opener.open(&[0x0], &ct);
999            assert!(matches!(open_res.unwrap_err(), Error::Other(_)));
1000
1001            // Opening the plaintext with the wrong opener should fail.
1002            let mut sk_rm_prime = sk.secret_bytes().to_vec();
1003            sk_rm_prime[10] ^= 0xFF; // Corrupt a byte of the private key.
1004            let mut opener_two = suite
1005                .setup_opener(&enc, info, &HpkePrivateKey::from(sk_rm_prime))
1006                .unwrap();
1007            let open_res = opener_two.open(aad, &ct);
1008            assert!(matches!(open_res.unwrap_err(), Error::Other(_)));
1009        }
1010    }
1011
1012    #[cfg(not(feature = "fips"))] // Ensure all supported suites are available to test.
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    #[test]
1038    fn failed_open_does_not_consume_sequence() {
1039        for suite in ALL_SUPPORTED_SUITES {
1040            let (pk, sk) = suite.generate_key_pair().unwrap();
1041            let (enc, mut sealer) = suite
1042                .setup_sealer(b"example", &pk)
1043                .unwrap();
1044            let ct0 = sealer
1045                .seal(b"aad", b"message 0")
1046                .unwrap();
1047            let ct1 = sealer
1048                .seal(b"aad", b"message 1")
1049                .unwrap();
1050
1051            let mut opener = suite
1052                .setup_opener(&enc, b"example", &sk)
1053                .unwrap();
1054            assert!(opener.open(b"wrong aad", &ct0).is_err());
1055            assert_eq!(opener.open(b"aad", &ct0).unwrap(), b"message 0");
1056            assert_eq!(opener.open(b"aad", &ct1).unwrap(), b"message 1");
1057        }
1058    }
1059}
1060
1061#[cfg(test)]
1062mod rfc_tests {
1063    use alloc::string::String;
1064    use std::fs::File;
1065    use std::println;
1066
1067    use serde::Deserialize;
1068
1069    use super::*;
1070
1071    /// Confirm open/seal operations work using the test vectors from [RFC 9180 Appendix A].
1072    ///
1073    /// [RFC 9180 Appendix A]: https://www.rfc-editor.org/rfc/rfc9180#TestVectors
1074    #[test]
1075    fn check_test_vectors() {
1076        for (idx, vec) in test_vectors().into_iter().enumerate() {
1077            let Some(hpke) = vec.applicable() else {
1078                println!("skipping inapplicable vector {idx}");
1079                continue;
1080            };
1081
1082            println!("testing vector {idx}");
1083            let pk_r = HpkePublicKey(hex::decode(vec.pk_rm).unwrap());
1084            let sk_r = HpkePrivateKey::from(hex::decode(vec.sk_rm).unwrap());
1085            let sk_em = hex::decode(vec.sk_em).unwrap();
1086            let info = hex::decode(vec.info).unwrap();
1087            let expected_enc = hex::decode(vec.enc).unwrap();
1088
1089            let (enc, mut sealer) = hpke
1090                .setup_test_sealer(&info, &pk_r, &sk_em)
1091                .unwrap();
1092            assert_eq!(enc.0, expected_enc);
1093
1094            let mut opener = hpke
1095                .setup_opener(&enc, &info, &sk_r)
1096                .unwrap();
1097
1098            for test_encryption in vec.encryptions {
1099                let aad = hex::decode(test_encryption.aad).unwrap();
1100                let pt = hex::decode(test_encryption.pt).unwrap();
1101                let expected_ct = hex::decode(test_encryption.ct).unwrap();
1102
1103                let ciphertext = sealer.seal(&aad, &pt).unwrap();
1104                assert_eq!(ciphertext, expected_ct);
1105
1106                let plaintext = opener.open(&aad, &ciphertext).unwrap();
1107                assert_eq!(plaintext, pt);
1108            }
1109        }
1110    }
1111
1112    trait TestHpke: Hpke {
1113        fn setup_test_sealer(
1114            &self,
1115            info: &[u8],
1116            pub_key: &HpkePublicKey,
1117            sk_em: &[u8],
1118        ) -> Result<(EncapsulatedSecret, Box<dyn HpkeSealer + 'static>), Error>;
1119    }
1120
1121    impl<const KEY_SIZE: usize, const KDF_SIZE: usize> TestHpke for HpkeAwsLcRs<KEY_SIZE, KDF_SIZE> {
1122        fn setup_test_sealer(
1123            &self,
1124            info: &[u8],
1125            pub_key: &HpkePublicKey,
1126            sk_em: &[u8],
1127        ) -> Result<(EncapsulatedSecret, Box<dyn HpkeSealer + 'static>), Error> {
1128            let (encap, sealer) = Sealer::test_only_new(self, info, pub_key, sk_em)?;
1129            Ok((encap, Box::new(sealer)))
1130        }
1131    }
1132
1133    static TEST_SUITES: &[&dyn TestHpke] = &[
1134        DH_KEM_P256_HKDF_SHA256_AES_128,
1135        DH_KEM_P256_HKDF_SHA256_AES_256,
1136        #[cfg(not(feature = "fips"))]
1137        DH_KEM_P256_HKDF_SHA256_CHACHA20_POLY1305,
1138        DH_KEM_P384_HKDF_SHA384_AES_128,
1139        DH_KEM_P384_HKDF_SHA384_AES_256,
1140        #[cfg(not(feature = "fips"))]
1141        DH_KEM_P384_HKDF_SHA384_CHACHA20_POLY1305,
1142        DH_KEM_P521_HKDF_SHA512_AES_128,
1143        DH_KEM_P521_HKDF_SHA512_AES_256,
1144        #[cfg(not(feature = "fips"))]
1145        DH_KEM_P521_HKDF_SHA512_CHACHA20_POLY1305,
1146        #[cfg(not(feature = "fips"))]
1147        DH_KEM_X25519_HKDF_SHA256_AES_128,
1148        #[cfg(not(feature = "fips"))]
1149        DH_KEM_X25519_HKDF_SHA256_AES_256,
1150        #[cfg(not(feature = "fips"))]
1151        DH_KEM_X25519_HKDF_SHA256_CHACHA20_POLY1305,
1152    ];
1153
1154    #[derive(Deserialize, Debug)]
1155    struct TestVector {
1156        mode: u8,
1157        kem_id: u16,
1158        kdf_id: u16,
1159        aead_id: u16,
1160        info: String,
1161        #[serde(rename(deserialize = "pkRm"))]
1162        pk_rm: String,
1163        #[serde(rename(deserialize = "skRm"))]
1164        sk_rm: String,
1165        #[serde(rename(deserialize = "skEm"))]
1166        sk_em: String,
1167        enc: String,
1168        encryptions: Vec<TestEncryption>,
1169    }
1170
1171    #[derive(Deserialize, Debug)]
1172    struct TestEncryption {
1173        aad: String,
1174        pt: String,
1175        ct: String,
1176    }
1177
1178    impl TestVector {
1179        fn suite(&self) -> HpkeSuite {
1180            HpkeSuite {
1181                kem: HpkeKem::from(self.kem_id),
1182                sym: HpkeSymmetricCipherSuite {
1183                    kdf_id: HpkeKdf::from(self.kdf_id),
1184                    aead_id: HpkeAead::from(self.aead_id),
1185                },
1186            }
1187        }
1188
1189        fn applicable(&self) -> Option<&'static dyn TestHpke> {
1190            // Only base mode test vectors for supported suites are applicable.
1191            if self.mode != 0 {
1192                return None;
1193            }
1194
1195            Self::lookup_suite(self.suite(), TEST_SUITES)
1196        }
1197
1198        fn lookup_suite(
1199            suite: HpkeSuite,
1200            supported: &[&'static dyn TestHpke],
1201        ) -> Option<&'static dyn TestHpke> {
1202            supported
1203                .iter()
1204                .find(|s| s.suite() == suite)
1205                .copied()
1206        }
1207    }
1208
1209    fn test_vectors() -> Vec<TestVector> {
1210        serde_json::from_reader(
1211            &mut File::open("../rustls-provider-test/tests/rfc-9180-test-vectors.json")
1212                .expect("failed to open test vectors data file"),
1213        )
1214        .expect("failed to deserialize test vectors")
1215    }
1216}