1 // Copyright 2024, The Android Open Source Project
2 //
3 // Licensed under the Apache License, Version 2.0 (the "License");
4 // you may not use this file except in compliance with the License.
5 // You may obtain a copy of the License at
6 //
7 //     http://www.apache.org/licenses/LICENSE-2.0
8 //
9 // Unless required by applicable law or agreed to in writing, software
10 // distributed under the License is distributed on an "AS IS" BASIS,
11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 // See the License for the specific language governing permissions and
13 // limitations under the License.
14 
15 //! Hybrid public key encryption.
16 
17 use bssl_crypto::hpke;
18 use mls_rs_core::crypto::{
19     CipherSuite, HpkeCiphertext, HpkeContextR, HpkeContextS, HpkePublicKey, HpkeSecretKey,
20 };
21 use mls_rs_core::error::{AnyError, IntoAnyError};
22 use mls_rs_crypto_traits::{DhType, KdfType, KemId, KemResult, KemType};
23 use std::sync::Mutex;
24 use thiserror::Error;
25 
26 /// Errors returned from HPKE.
27 #[derive(Debug, Error)]
28 pub enum HpkeError {
29     /// Error returned from BoringSSL.
30     #[error("BoringSSL error")]
31     BoringsslError,
32     /// Error returned from Diffie-Hellman operations.
33     #[error(transparent)]
34     DhError(AnyError),
35     /// Error returned from KDF operations.
36     #[error(transparent)]
37     KdfError(AnyError),
38     /// Error returned when unsupported cipher suite is requested.
39     #[error("unsupported cipher suite")]
40     UnsupportedCipherSuite,
41 }
42 
43 impl IntoAnyError for HpkeError {
into_dyn_error(self) -> Result<Box<dyn std::error::Error + Send + Sync>, Self>44     fn into_dyn_error(self) -> Result<Box<dyn std::error::Error + Send + Sync>, Self> {
45         Ok(self.into())
46     }
47 }
48 
49 #[derive(Clone, Debug, Eq, PartialEq)]
50 pub(crate) struct KdfWrapper<KDF: KdfType> {
51     suite_id: Vec<u8>,
52     kdf: KDF,
53 }
54 
55 impl<KDF: KdfType> KdfWrapper<KDF> {
new(suite_id: Vec<u8>, kdf: KDF) -> Self56     pub fn new(suite_id: Vec<u8>, kdf: KDF) -> Self {
57         Self { suite_id, kdf }
58     }
59 
60     // https://www.rfc-editor.org/rfc/rfc9180.html#section-4-9
61     #[cfg_attr(not(mls_build_async), maybe_async::must_be_sync)]
labeled_extract( &self, salt: &[u8], label: &[u8], ikm: &[u8], ) -> Result<Vec<u8>, <KDF as KdfType>::Error>62     pub async fn labeled_extract(
63         &self,
64         salt: &[u8],
65         label: &[u8],
66         ikm: &[u8],
67     ) -> Result<Vec<u8>, <KDF as KdfType>::Error> {
68         self.kdf.extract(salt, &[b"HPKE-v1" as &[u8], &self.suite_id, label, ikm].concat()).await
69     }
70 
71     // https://www.rfc-editor.org/rfc/rfc9180.html#section-4-9
72     #[cfg_attr(not(mls_build_async), maybe_async::must_be_sync)]
labeled_expand( &self, key: &[u8], label: &[u8], info: &[u8], len: usize, ) -> Result<Vec<u8>, <KDF as KdfType>::Error>73     pub async fn labeled_expand(
74         &self,
75         key: &[u8],
76         label: &[u8],
77         info: &[u8],
78         len: usize,
79     ) -> Result<Vec<u8>, <KDF as KdfType>::Error> {
80         let labeled_info =
81             [&(len as u16).to_be_bytes() as &[u8], b"HPKE-v1", &self.suite_id, label, info]
82                 .concat();
83         self.kdf.expand(key, &labeled_info, len).await
84     }
85 }
86 
87 /// KemType implementation backed by BoringSSL.
88 #[derive(Clone, Debug, Eq, PartialEq)]
89 pub struct DhKem<DH: DhType, KDF: KdfType> {
90     dh: DH,
91     kdf: KdfWrapper<KDF>,
92     kem_id: KemId,
93     n_secret: usize,
94 }
95 
96 impl<DH: DhType, KDF: KdfType> DhKem<DH, KDF> {
97     /// Creates a new DhKem.
new(cipher_suite: CipherSuite, dh: DH, kdf: KDF) -> Option<Self>98     pub fn new(cipher_suite: CipherSuite, dh: DH, kdf: KDF) -> Option<Self> {
99         // https://www.rfc-editor.org/rfc/rfc9180.html#section-4.1-5
100         let kem_id = KemId::new(cipher_suite)?;
101         let suite_id = [b"KEM", &(kem_id as u16).to_be_bytes() as &[u8]].concat();
102 
103         let kdf = KdfWrapper::new(suite_id, kdf);
104 
105         Some(Self { dh, kdf, kem_id, n_secret: kem_id.n_secret() })
106     }
107 }
108 
109 #[cfg_attr(not(mls_build_async), maybe_async::must_be_sync)]
110 #[cfg_attr(all(target_arch = "wasm32", mls_build_async), maybe_async::must_be_async(?Send))]
111 #[cfg_attr(all(not(target_arch = "wasm32"), mls_build_async), maybe_async::must_be_async)]
112 impl<DH: DhType, KDF: KdfType> KemType for DhKem<DH, KDF> {
113     type Error = HpkeError;
114 
kem_id(&self) -> u16115     fn kem_id(&self) -> u16 {
116         self.kem_id as u16
117     }
118 
generate(&self) -> Result<(HpkeSecretKey, HpkePublicKey), Self::Error>119     async fn generate(&self) -> Result<(HpkeSecretKey, HpkePublicKey), Self::Error> {
120         if self.kem_id != KemId::DhKemX25519Sha256 {
121             return Err(HpkeError::UnsupportedCipherSuite);
122         }
123 
124         let kem = hpke::Kem::X25519HkdfSha256;
125         let (public_key, private_key) = kem.generate_keypair();
126         Ok((private_key.to_vec().into(), public_key.to_vec().into()))
127     }
128 
129     // https://www.rfc-editor.org/rfc/rfc9180.html#section-7.1.3-8
derive(&self, ikm: &[u8]) -> Result<(HpkeSecretKey, HpkePublicKey), Self::Error>130     async fn derive(&self, ikm: &[u8]) -> Result<(HpkeSecretKey, HpkePublicKey), Self::Error> {
131         let dkp_prk = match self.kdf.labeled_extract(&[], b"dkp_prk", ikm).await {
132             Ok(p) => p,
133             Err(e) => return Err(HpkeError::KdfError(e.into_any_error())),
134         };
135         let sk =
136             match self.kdf.labeled_expand(&dkp_prk, b"sk", &[], self.dh.secret_key_size()).await {
137                 Ok(s) => s.into(),
138                 Err(e) => return Err(HpkeError::KdfError(e.into_any_error())),
139             };
140         let pk = match self.dh.to_public(&sk).await {
141             Ok(p) => p,
142             Err(e) => return Err(HpkeError::KdfError(e.into_any_error())),
143         };
144         Ok((sk, pk))
145     }
146 
public_key_validate(&self, key: &HpkePublicKey) -> Result<(), Self::Error>147     fn public_key_validate(&self, key: &HpkePublicKey) -> Result<(), Self::Error> {
148         match self.dh.public_key_validate(key) {
149             Ok(_) => Ok(()),
150             Err(e) => Err(HpkeError::DhError(e.into_any_error())),
151         }
152     }
153 
154     // Using BoringSSL's HPKE implementation so this is not needed.
encap(&self, _remote_pk: &HpkePublicKey) -> Result<KemResult, Self::Error>155     async fn encap(&self, _remote_pk: &HpkePublicKey) -> Result<KemResult, Self::Error> {
156         unimplemented!();
157     }
158 
159     // Using BoringSSL's HPKE implementation so this is not needed.
decap( &self, _enc: &[u8], _secret_key: &HpkeSecretKey, _public_key: &HpkePublicKey, ) -> Result<Vec<u8>, Self::Error>160     async fn decap(
161         &self,
162         _enc: &[u8],
163         _secret_key: &HpkeSecretKey,
164         _public_key: &HpkePublicKey,
165     ) -> Result<Vec<u8>, Self::Error> {
166         unimplemented!();
167     }
168 }
169 
170 /// HpkeContextS implementation backed by BoringSSL.
171 pub struct ContextS(pub Mutex<hpke::SenderContext>);
172 
173 #[cfg_attr(not(mls_build_async), maybe_async::must_be_sync)]
174 #[cfg_attr(all(target_arch = "wasm32", mls_build_async), maybe_async::must_be_async(?Send))]
175 #[cfg_attr(all(not(target_arch = "wasm32"), mls_build_async), maybe_async::must_be_async)]
176 impl HpkeContextS for ContextS {
177     type Error = HpkeError;
178 
179     #[cfg_attr(not(mls_build_async), maybe_async::must_be_sync)]
seal(&mut self, aad: Option<&[u8]>, data: &[u8]) -> Result<Vec<u8>, Self::Error>180     async fn seal(&mut self, aad: Option<&[u8]>, data: &[u8]) -> Result<Vec<u8>, Self::Error> {
181         Ok(self.0.lock().unwrap().seal(data, aad.unwrap_or_default()))
182     }
183 
184     #[cfg_attr(not(mls_build_async), maybe_async::must_be_sync)]
export(&self, exporter_context: &[u8], len: usize) -> Result<Vec<u8>, Self::Error>185     async fn export(&self, exporter_context: &[u8], len: usize) -> Result<Vec<u8>, Self::Error> {
186         Ok(self.0.lock().unwrap().export(exporter_context, len).to_vec())
187     }
188 }
189 
190 /// HpkeContextR implementation backed by BoringSSL.
191 pub struct ContextR(pub Mutex<hpke::RecipientContext>);
192 
193 #[cfg_attr(not(mls_build_async), maybe_async::must_be_sync)]
194 #[cfg_attr(all(target_arch = "wasm32", mls_build_async), maybe_async::must_be_async(?Send))]
195 #[cfg_attr(all(not(target_arch = "wasm32"), mls_build_async), maybe_async::must_be_async)]
196 impl HpkeContextR for ContextR {
197     type Error = HpkeError;
198 
199     #[cfg_attr(not(mls_build_async), maybe_async::must_be_sync)]
open( &mut self, aad: Option<&[u8]>, ciphertext: &[u8], ) -> Result<Vec<u8>, Self::Error>200     async fn open(
201         &mut self,
202         aad: Option<&[u8]>,
203         ciphertext: &[u8],
204     ) -> Result<Vec<u8>, Self::Error> {
205         self.0
206             .lock()
207             .unwrap()
208             .open(ciphertext, aad.unwrap_or_default())
209             .ok_or(HpkeError::BoringsslError)
210     }
211 
212     #[cfg_attr(not(mls_build_async), maybe_async::must_be_sync)]
export(&self, exporter_context: &[u8], len: usize) -> Result<Vec<u8>, Self::Error>213     async fn export(&self, exporter_context: &[u8], len: usize) -> Result<Vec<u8>, Self::Error> {
214         Ok(self.0.lock().unwrap().export(exporter_context, len).to_vec())
215     }
216 }
217 
218 /// HPKE implementation backed by BoringSSL.
219 #[derive(Clone)]
220 pub struct Hpke(pub CipherSuite);
221 
222 impl Hpke {
223     /// Creates a new Hpke.
new(cipher_suite: CipherSuite) -> Self224     pub fn new(cipher_suite: CipherSuite) -> Self {
225         Self(cipher_suite)
226     }
227 
228     /// Sets up HPKE sender context.
229     #[cfg_attr(not(mls_build_async), maybe_async::must_be_sync)]
setup_sender( &self, remote_key: &HpkePublicKey, info: &[u8], ) -> Result<(Vec<u8>, ContextS), HpkeError>230     pub async fn setup_sender(
231         &self,
232         remote_key: &HpkePublicKey,
233         info: &[u8],
234     ) -> Result<(Vec<u8>, ContextS), HpkeError> {
235         let params = Self::cipher_suite_to_params(self.0)?;
236         match hpke::SenderContext::new(&params, remote_key, info) {
237             Some((ctx, encapsulated_key)) => Ok((encapsulated_key, ContextS(ctx.into()))),
238             None => Err(HpkeError::BoringsslError),
239         }
240     }
241 
242     /// Sets up HPKE sender context and encrypts `pt` with optional associated data `aad`.
243     #[cfg_attr(not(mls_build_async), maybe_async::must_be_sync)]
seal( &self, remote_key: &HpkePublicKey, info: &[u8], aad: Option<&[u8]>, pt: &[u8], ) -> Result<HpkeCiphertext, HpkeError>244     pub async fn seal(
245         &self,
246         remote_key: &HpkePublicKey,
247         info: &[u8],
248         aad: Option<&[u8]>,
249         pt: &[u8],
250     ) -> Result<HpkeCiphertext, HpkeError> {
251         let (kem_output, mut ctx) = self.setup_sender(remote_key, info).await?;
252         Ok(HpkeCiphertext { kem_output, ciphertext: ctx.seal(aad, pt).await? })
253     }
254 
255     /// Sets up HPKE receiver context.
256     #[cfg_attr(not(mls_build_async), maybe_async::must_be_sync)]
setup_receiver( &self, enc: &[u8], local_secret: &HpkeSecretKey, info: &[u8], ) -> Result<ContextR, HpkeError>257     pub async fn setup_receiver(
258         &self,
259         enc: &[u8],
260         local_secret: &HpkeSecretKey,
261         info: &[u8],
262     ) -> Result<ContextR, HpkeError> {
263         let params = Self::cipher_suite_to_params(self.0)?;
264         match hpke::RecipientContext::new(&params, local_secret, enc, info) {
265             Some(ctx) => Ok(ContextR(ctx.into())),
266             None => Err(HpkeError::BoringsslError),
267         }
268     }
269 
270     /// Sets up HPKE receiver context and decrypts `ciphertext` with optional associated data `aad`.
271     #[cfg_attr(not(mls_build_async), maybe_async::must_be_sync)]
open( &self, ciphertext: &HpkeCiphertext, local_secret: &HpkeSecretKey, info: &[u8], aad: Option<&[u8]>, ) -> Result<Vec<u8>, HpkeError>272     pub async fn open(
273         &self,
274         ciphertext: &HpkeCiphertext,
275         local_secret: &HpkeSecretKey,
276         info: &[u8],
277         aad: Option<&[u8]>,
278     ) -> Result<Vec<u8>, HpkeError> {
279         let mut ctx = self.setup_receiver(&ciphertext.kem_output, local_secret, info).await?;
280         ctx.open(aad, &ciphertext.ciphertext).await
281     }
282 
cipher_suite_to_params(cipher_suite: CipherSuite) -> Result<hpke::Params, HpkeError>283     fn cipher_suite_to_params(cipher_suite: CipherSuite) -> Result<hpke::Params, HpkeError> {
284         match cipher_suite {
285             CipherSuite::CURVE25519_AES128 => Ok(hpke::Params::new(
286                 hpke::Kem::X25519HkdfSha256,
287                 hpke::Kdf::HkdfSha256,
288                 hpke::Aead::Aes128Gcm,
289             )),
290             CipherSuite::CURVE25519_CHACHA => Ok(hpke::Params::new(
291                 hpke::Kem::X25519HkdfSha256,
292                 hpke::Kdf::HkdfSha256,
293                 hpke::Aead::Chacha20Poly1305,
294             )),
295             _ => Err(HpkeError::UnsupportedCipherSuite),
296         }
297     }
298 }
299 
300 #[cfg(all(not(mls_build_async), test))]
301 mod test {
302     use super::{DhKem, Hpke, KdfWrapper};
303     use crate::ecdh::Ecdh;
304     use crate::kdf::Kdf;
305     use crate::test_helpers::decode_hex;
306     use mls_rs_core::crypto::{
307         CipherSuite, HpkeContextR, HpkeContextS, HpkePublicKey, HpkeSecretKey,
308     };
309     use mls_rs_crypto_traits::{AeadId, KdfId, KemId, KemType};
310     use std::thread;
311 
312     // https://www.rfc-editor.org/rfc/rfc9180.html#section-5.1-8
hpke_suite_id(cipher_suite: CipherSuite) -> Vec<u8>313     fn hpke_suite_id(cipher_suite: CipherSuite) -> Vec<u8> {
314         [
315             b"HPKE",
316             &(KemId::new(cipher_suite).unwrap() as u16).to_be_bytes() as &[u8],
317             &(KdfId::new(cipher_suite).unwrap() as u16).to_be_bytes() as &[u8],
318             &(AeadId::new(cipher_suite).unwrap() as u16).to_be_bytes() as &[u8],
319         ]
320         .concat()
321     }
322 
323     #[test]
kdf_labeled_extract()324     fn kdf_labeled_extract() {
325         let cipher_suite = CipherSuite::CURVE25519_AES128;
326         let suite_id = hpke_suite_id(cipher_suite);
327         let kdf = KdfWrapper::new(suite_id, Kdf::new(cipher_suite).unwrap());
328 
329         // https://www.rfc-editor.org/rfc/rfc9180.html#appendix-A.1.1
330         let shared_secret: [u8; 32] =
331             decode_hex("fe0e18c9f024ce43799ae393c7e8fe8fce9d218875e8227b0187c04e7d2ea1fc");
332         let expected_secret: [u8; 32] =
333             decode_hex("12fff91991e93b48de37e7daddb52981084bd8aa64289c3788471d9a9712f397");
334         let label = b"secret";
335 
336         let secret = kdf.labeled_extract(&shared_secret, label, &[]).unwrap();
337         assert_eq!(secret, expected_secret);
338     }
339 
340     #[test]
kdf_labeled_expand()341     fn kdf_labeled_expand() {
342         let cipher_suite = CipherSuite::CURVE25519_AES128;
343         let suite_id = hpke_suite_id(cipher_suite);
344         let kdf = KdfWrapper::new(suite_id, Kdf::new(cipher_suite).unwrap());
345 
346         // https://www.rfc-editor.org/rfc/rfc9180.html#appendix-A.1.1
347         let secret: [u8; 32] =
348             decode_hex("12fff91991e93b48de37e7daddb52981084bd8aa64289c3788471d9a9712f397");
349         let key_schedule_ctx : [u8; 65] = decode_hex("00725611c9d98c07c03f60095cd32d400d8347d45ed67097bbad50fc56da742d07cb6cffde367bb0565ba28bb02c90744a20f5ef37f30523526106f637abb05449");
350         let expected_key: [u8; 16] = decode_hex("4531685d41d65f03dc48f6b8302c05b0");
351         let label = b"key";
352 
353         let key = kdf.labeled_expand(&secret, label, &key_schedule_ctx, 16).unwrap();
354         assert_eq!(key, expected_key);
355     }
356 
357     #[test]
dh_kem_kem_id()358     fn dh_kem_kem_id() {
359         let cipher_suite = CipherSuite::CURVE25519_CHACHA;
360         let dh = Ecdh::new(cipher_suite).unwrap();
361         let kdf = Kdf::new(cipher_suite).unwrap();
362         let kem = DhKem::new(cipher_suite, dh, kdf).unwrap();
363 
364         assert_eq!(kem.kem_id(), 32);
365     }
366 
367     #[test]
dh_kem_generate()368     fn dh_kem_generate() {
369         let cipher_suite = CipherSuite::CURVE25519_AES128;
370         let dh = Ecdh::new(cipher_suite).unwrap();
371         let kdf = Kdf::new(cipher_suite).unwrap();
372         let kem = DhKem::new(cipher_suite, dh, kdf).unwrap();
373 
374         assert!(kem.generate().is_ok());
375     }
376 
377     #[test]
dh_kem_derive()378     fn dh_kem_derive() {
379         let cipher_suite = CipherSuite::CURVE25519_CHACHA;
380         let dh = Ecdh::new(cipher_suite).unwrap();
381         let kdf = Kdf::new(cipher_suite).unwrap();
382         let kem = DhKem::new(cipher_suite, dh, kdf).unwrap();
383 
384         // https://www.rfc-editor.org/rfc/rfc9180.html#appendix-A.2.1
385         let ikm: [u8; 32] =
386             decode_hex("909a9b35d3dc4713a5e72a4da274b55d3d3821a37e5d099e74a647db583a904b"); // ikmE
387         let expected_sk = HpkeSecretKey::from(
388             decode_hex::<32>("f4ec9b33b792c372c1d2c2063507b684ef925b8c75a42dbcbf57d63ccd381600")
389                 .to_vec(),
390         ); // skEm
391         let expected_pk = HpkePublicKey::from(
392             decode_hex::<32>("1afa08d3dec047a643885163f1180476fa7ddb54c6a8029ea33f95796bf2ac4a")
393                 .to_vec(),
394         ); // pkEm
395 
396         let (sk, pk) = kem.derive(&ikm).unwrap();
397         assert_eq!(sk, expected_sk);
398         assert_eq!(pk, expected_pk);
399     }
400 
401     #[test]
dh_kem_public_key_validate()402     fn dh_kem_public_key_validate() {
403         let cipher_suite = CipherSuite::CURVE25519_AES128;
404         let dh = Ecdh::new(cipher_suite).unwrap();
405         let kdf = Kdf::new(cipher_suite).unwrap();
406         let kem = DhKem::new(cipher_suite, dh, kdf).unwrap();
407 
408         // https://www.rfc-editor.org/rfc/rfc7748.html#section-6.1
409         let public_key = HpkePublicKey::from(
410             decode_hex::<32>("8520f0098930a754748b7ddcb43ef75a0dbf3a0d26381af4eba4a98eaa9b4e6a")
411                 .to_vec(),
412         );
413         assert!(kem.public_key_validate(&public_key).is_ok());
414     }
415 
416     #[test]
hpke_seal_open()417     fn hpke_seal_open() {
418         let hpke = Hpke::new(CipherSuite::CURVE25519_AES128);
419 
420         // https://www.rfc-editor.org/rfc/rfc9180.html#appendix-A.1.1
421         let receiver_pub_key = HpkePublicKey::from(
422             decode_hex::<32>("3948cfe0ad1ddb695d780e59077195da6c56506b027329794ab02bca80815c4d")
423                 .to_vec(),
424         );
425         let receiver_priv_key = HpkeSecretKey::from(
426             decode_hex::<32>("4612c550263fc8ad58375df3f557aac531d26850903e55a9f23f21d8534e8ac8")
427                 .to_vec(),
428         );
429 
430         let info = b"some_info";
431         let plaintext = b"plaintext";
432         let associated_data = b"some_ad";
433 
434         let ct = hpke.seal(&receiver_pub_key, info, Some(associated_data), plaintext).unwrap();
435         assert_eq!(
436             plaintext.as_ref(),
437             hpke.open(&ct, &receiver_priv_key, info, Some(associated_data)).unwrap(),
438         );
439     }
440 
441     #[test]
hpke_context_seal_open()442     fn hpke_context_seal_open() {
443         let hpke = Hpke::new(CipherSuite::CURVE25519_AES128);
444 
445         // https://www.rfc-editor.org/rfc/rfc9180.html#appendix-A.1.1
446         let receiver_pub_key = HpkePublicKey::from(
447             decode_hex::<32>("3948cfe0ad1ddb695d780e59077195da6c56506b027329794ab02bca80815c4d")
448                 .to_vec(),
449         );
450         let receiver_priv_key = HpkeSecretKey::from(
451             decode_hex::<32>("4612c550263fc8ad58375df3f557aac531d26850903e55a9f23f21d8534e8ac8")
452                 .to_vec(),
453         );
454 
455         let info = b"some_info";
456         let plaintext = b"plaintext";
457         let associated_data = b"some_ad";
458 
459         let (enc, mut sender_ctx) = hpke.setup_sender(&receiver_pub_key, info).unwrap();
460         let mut receiver_ctx = hpke.setup_receiver(&enc, &receiver_priv_key, info).unwrap();
461         let ct = sender_ctx.seal(Some(associated_data), plaintext).unwrap();
462         assert_eq!(plaintext.as_ref(), receiver_ctx.open(Some(associated_data), &ct).unwrap(),);
463     }
464 
465     #[test]
hpke_context_seal_open_multithreaded()466     fn hpke_context_seal_open_multithreaded() {
467         let hpke = Hpke::new(CipherSuite::CURVE25519_AES128);
468 
469         // https://www.rfc-editor.org/rfc/rfc9180.html#appendix-A.1.1
470         let receiver_pub_key = HpkePublicKey::from(
471             decode_hex::<32>("3948cfe0ad1ddb695d780e59077195da6c56506b027329794ab02bca80815c4d")
472                 .to_vec(),
473         );
474         let receiver_priv_key = HpkeSecretKey::from(
475             decode_hex::<32>("4612c550263fc8ad58375df3f557aac531d26850903e55a9f23f21d8534e8ac8")
476                 .to_vec(),
477         );
478 
479         let info = b"some_info";
480         let plaintext = b"plaintext";
481         let associated_data = b"some_ad";
482 
483         let (enc, mut sender_ctx) = hpke.setup_sender(&receiver_pub_key, info).unwrap();
484         let mut receiver_ctx = hpke.setup_receiver(&enc, &receiver_priv_key, info).unwrap();
485 
486         let pool = thread::spawn(move || {
487             for _ in 1..100 {
488                 let ct = sender_ctx.seal(Some(associated_data), plaintext).unwrap();
489                 assert_eq!(
490                     plaintext.as_ref(),
491                     receiver_ctx.open(Some(associated_data), &ct).unwrap(),
492                 );
493             }
494         });
495         pool.join().unwrap();
496     }
497 
498     #[test]
hpke_context_export()499     fn hpke_context_export() {
500         let hpke = Hpke::new(CipherSuite::CURVE25519_AES128);
501 
502         // https://www.rfc-editor.org/rfc/rfc9180.html#appendix-A.1.1
503         let receiver_pub_key = HpkePublicKey::from(
504             decode_hex::<32>("3948cfe0ad1ddb695d780e59077195da6c56506b027329794ab02bca80815c4d")
505                 .to_vec(),
506         );
507         let receiver_priv_key = HpkeSecretKey::from(
508             decode_hex::<32>("4612c550263fc8ad58375df3f557aac531d26850903e55a9f23f21d8534e8ac8")
509                 .to_vec(),
510         );
511 
512         let info = b"some_info";
513         let exporter_ctx = b"export_ctx";
514 
515         let (enc, sender_ctx) = hpke.setup_sender(&receiver_pub_key, info).unwrap();
516         let receiver_ctx = hpke.setup_receiver(&enc, &receiver_priv_key, info).unwrap();
517         assert_eq!(
518             sender_ctx.export(exporter_ctx, 32).unwrap(),
519             receiver_ctx.export(exporter_ctx, 32).unwrap(),
520         );
521     }
522 
523     #[test]
hpke_unsupported_cipher_suites()524     fn hpke_unsupported_cipher_suites() {
525         // https://www.rfc-editor.org/rfc/rfc9180.html#appendix-A.1.1
526         let receiver_pub_key = HpkePublicKey::from(
527             decode_hex::<32>("3948cfe0ad1ddb695d780e59077195da6c56506b027329794ab02bca80815c4d")
528                 .to_vec(),
529         );
530 
531         for suite in vec![
532             CipherSuite::P256_AES128,
533             CipherSuite::P384_AES256,
534             CipherSuite::P521_AES256,
535             CipherSuite::CURVE448_CHACHA,
536             CipherSuite::CURVE448_AES256,
537         ] {
538             assert!(Hpke::new(suite).setup_sender(&receiver_pub_key, b"some_info").is_err());
539         }
540     }
541 }
542