Implement mls-rs-crypto-traits backed by BoringSSL.
Fix: 302021139
Test: Presubmit
Change-Id: Iaefa21d3fb69f92d735875778f3f96e1878d0876
diff --git a/mls/mls-rs-crypto-boringssl/src/hpke.rs b/mls/mls-rs-crypto-boringssl/src/hpke.rs
new file mode 100644
index 0000000..4bb4aa2
--- /dev/null
+++ b/mls/mls-rs-crypto-boringssl/src/hpke.rs
@@ -0,0 +1,541 @@
+// Copyright 2024, The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+//! Hybrid public key encryption.
+
+use bssl_crypto::hpke;
+use mls_rs_core::crypto::{
+ CipherSuite, HpkeCiphertext, HpkeContextR, HpkeContextS, HpkePublicKey, HpkeSecretKey,
+};
+use mls_rs_core::error::{AnyError, IntoAnyError};
+use mls_rs_crypto_traits::{DhType, KdfType, KemId, KemResult, KemType};
+use std::sync::Mutex;
+use thiserror::Error;
+
+/// Errors returned from HPKE.
+#[derive(Debug, Error)]
+pub enum HpkeError {
+ /// Error returned from BoringSSL.
+ #[error("BoringSSL error")]
+ BoringsslError,
+ /// Error returned from Diffie-Hellman operations.
+ #[error(transparent)]
+ DhError(AnyError),
+ /// Error returned from KDF operations.
+ #[error(transparent)]
+ KdfError(AnyError),
+ /// Error returned when unsupported cipher suite is requested.
+ #[error("unsupported cipher suite")]
+ UnsupportedCipherSuite,
+}
+
+impl IntoAnyError for HpkeError {
+ fn into_dyn_error(self) -> Result<Box<dyn std::error::Error + Send + Sync>, Self> {
+ Ok(self.into())
+ }
+}
+
+#[derive(Clone, Debug, Eq, PartialEq)]
+pub(crate) struct KdfWrapper<KDF: KdfType> {
+ suite_id: Vec<u8>,
+ kdf: KDF,
+}
+
+impl<KDF: KdfType> KdfWrapper<KDF> {
+ pub fn new(suite_id: Vec<u8>, kdf: KDF) -> Self {
+ Self { suite_id, kdf }
+ }
+
+ // https://www.rfc-editor.org/rfc/rfc9180.html#section-4-9
+ #[cfg_attr(not(mls_build_async), maybe_async::must_be_sync)]
+ pub async fn labeled_extract(
+ &self,
+ salt: &[u8],
+ label: &[u8],
+ ikm: &[u8],
+ ) -> Result<Vec<u8>, <KDF as KdfType>::Error> {
+ self.kdf.extract(salt, &[b"HPKE-v1" as &[u8], &self.suite_id, label, ikm].concat()).await
+ }
+
+ // https://www.rfc-editor.org/rfc/rfc9180.html#section-4-9
+ #[cfg_attr(not(mls_build_async), maybe_async::must_be_sync)]
+ pub async fn labeled_expand(
+ &self,
+ key: &[u8],
+ label: &[u8],
+ info: &[u8],
+ len: usize,
+ ) -> Result<Vec<u8>, <KDF as KdfType>::Error> {
+ let labeled_info =
+ [&(len as u16).to_be_bytes() as &[u8], b"HPKE-v1", &self.suite_id, label, info]
+ .concat();
+ self.kdf.expand(key, &labeled_info, len).await
+ }
+}
+
+/// KemType implementation backed by BoringSSL.
+#[derive(Clone, Debug, Eq, PartialEq)]
+pub struct DhKem<DH: DhType, KDF: KdfType> {
+ dh: DH,
+ kdf: KdfWrapper<KDF>,
+ kem_id: KemId,
+ n_secret: usize,
+}
+
+impl<DH: DhType, KDF: KdfType> DhKem<DH, KDF> {
+ /// Creates a new DhKem.
+ pub fn new(cipher_suite: CipherSuite, dh: DH, kdf: KDF) -> Option<Self> {
+ // https://www.rfc-editor.org/rfc/rfc9180.html#section-4.1-5
+ let kem_id = KemId::new(cipher_suite)?;
+ let suite_id = [b"KEM", &(kem_id as u16).to_be_bytes() as &[u8]].concat();
+
+ let kdf = KdfWrapper::new(suite_id, kdf);
+
+ Some(Self { dh, kdf, kem_id, n_secret: kem_id.n_secret() })
+ }
+}
+
+#[cfg_attr(not(mls_build_async), maybe_async::must_be_sync)]
+#[cfg_attr(all(target_arch = "wasm32", mls_build_async), maybe_async::must_be_async(?Send))]
+#[cfg_attr(all(not(target_arch = "wasm32"), mls_build_async), maybe_async::must_be_async)]
+impl<DH: DhType, KDF: KdfType> KemType for DhKem<DH, KDF> {
+ type Error = HpkeError;
+
+ fn kem_id(&self) -> u16 {
+ self.kem_id as u16
+ }
+
+ async fn generate(&self) -> Result<(HpkeSecretKey, HpkePublicKey), Self::Error> {
+ if self.kem_id != KemId::DhKemX25519Sha256 {
+ return Err(HpkeError::UnsupportedCipherSuite);
+ }
+
+ let kem = hpke::Kem::X25519HkdfSha256;
+ let (public_key, private_key) = kem.generate_keypair();
+ Ok((private_key.to_vec().into(), public_key.to_vec().into()))
+ }
+
+ // https://www.rfc-editor.org/rfc/rfc9180.html#section-7.1.3-8
+ async fn derive(&self, ikm: &[u8]) -> Result<(HpkeSecretKey, HpkePublicKey), Self::Error> {
+ let dkp_prk = match self.kdf.labeled_extract(&[], b"dkp_prk", ikm).await {
+ Ok(p) => p,
+ Err(e) => return Err(HpkeError::KdfError(e.into_any_error())),
+ };
+ let sk =
+ match self.kdf.labeled_expand(&dkp_prk, b"sk", &[], self.dh.secret_key_size()).await {
+ Ok(s) => s.into(),
+ Err(e) => return Err(HpkeError::KdfError(e.into_any_error())),
+ };
+ let pk = match self.dh.to_public(&sk).await {
+ Ok(p) => p,
+ Err(e) => return Err(HpkeError::KdfError(e.into_any_error())),
+ };
+ Ok((sk, pk))
+ }
+
+ fn public_key_validate(&self, key: &HpkePublicKey) -> Result<(), Self::Error> {
+ match self.dh.public_key_validate(key) {
+ Ok(_) => Ok(()),
+ Err(e) => Err(HpkeError::DhError(e.into_any_error())),
+ }
+ }
+
+ // Using BoringSSL's HPKE implementation so this is not needed.
+ async fn encap(&self, _remote_pk: &HpkePublicKey) -> Result<KemResult, Self::Error> {
+ unimplemented!();
+ }
+
+ // Using BoringSSL's HPKE implementation so this is not needed.
+ async fn decap(
+ &self,
+ _enc: &[u8],
+ _secret_key: &HpkeSecretKey,
+ _public_key: &HpkePublicKey,
+ ) -> Result<Vec<u8>, Self::Error> {
+ unimplemented!();
+ }
+}
+
+/// HpkeContextS implementation backed by BoringSSL.
+pub struct ContextS(pub Mutex<hpke::SenderContext>);
+
+#[cfg_attr(not(mls_build_async), maybe_async::must_be_sync)]
+#[cfg_attr(all(target_arch = "wasm32", mls_build_async), maybe_async::must_be_async(?Send))]
+#[cfg_attr(all(not(target_arch = "wasm32"), mls_build_async), maybe_async::must_be_async)]
+impl HpkeContextS for ContextS {
+ type Error = HpkeError;
+
+ #[cfg_attr(not(mls_build_async), maybe_async::must_be_sync)]
+ async fn seal(&mut self, aad: Option<&[u8]>, data: &[u8]) -> Result<Vec<u8>, Self::Error> {
+ Ok(self.0.lock().unwrap().seal(data, aad.unwrap_or_default()))
+ }
+
+ #[cfg_attr(not(mls_build_async), maybe_async::must_be_sync)]
+ async fn export(&self, exporter_context: &[u8], len: usize) -> Result<Vec<u8>, Self::Error> {
+ Ok(self.0.lock().unwrap().export(exporter_context, len).to_vec())
+ }
+}
+
+/// HpkeContextR implementation backed by BoringSSL.
+pub struct ContextR(pub Mutex<hpke::RecipientContext>);
+
+#[cfg_attr(not(mls_build_async), maybe_async::must_be_sync)]
+#[cfg_attr(all(target_arch = "wasm32", mls_build_async), maybe_async::must_be_async(?Send))]
+#[cfg_attr(all(not(target_arch = "wasm32"), mls_build_async), maybe_async::must_be_async)]
+impl HpkeContextR for ContextR {
+ type Error = HpkeError;
+
+ #[cfg_attr(not(mls_build_async), maybe_async::must_be_sync)]
+ async fn open(
+ &mut self,
+ aad: Option<&[u8]>,
+ ciphertext: &[u8],
+ ) -> Result<Vec<u8>, Self::Error> {
+ self.0
+ .lock()
+ .unwrap()
+ .open(ciphertext, aad.unwrap_or_default())
+ .ok_or(HpkeError::BoringsslError)
+ }
+
+ #[cfg_attr(not(mls_build_async), maybe_async::must_be_sync)]
+ async fn export(&self, exporter_context: &[u8], len: usize) -> Result<Vec<u8>, Self::Error> {
+ Ok(self.0.lock().unwrap().export(exporter_context, len).to_vec())
+ }
+}
+
+/// HPKE implementation backed by BoringSSL.
+#[derive(Clone)]
+pub struct Hpke(pub CipherSuite);
+
+impl Hpke {
+ /// Creates a new Hpke.
+ pub fn new(cipher_suite: CipherSuite) -> Self {
+ Self(cipher_suite)
+ }
+
+ /// Sets up HPKE sender context.
+ #[cfg_attr(not(mls_build_async), maybe_async::must_be_sync)]
+ pub async fn setup_sender(
+ &self,
+ remote_key: &HpkePublicKey,
+ info: &[u8],
+ ) -> Result<(Vec<u8>, ContextS), HpkeError> {
+ let params = Self::cipher_suite_to_params(self.0)?;
+ match hpke::SenderContext::new(¶ms, remote_key, info) {
+ Some((ctx, encapsulated_key)) => Ok((encapsulated_key, ContextS(ctx.into()))),
+ None => Err(HpkeError::BoringsslError),
+ }
+ }
+
+ /// Sets up HPKE sender context and encrypts `pt` with optional associated data `aad`.
+ #[cfg_attr(not(mls_build_async), maybe_async::must_be_sync)]
+ pub async fn seal(
+ &self,
+ remote_key: &HpkePublicKey,
+ info: &[u8],
+ aad: Option<&[u8]>,
+ pt: &[u8],
+ ) -> Result<HpkeCiphertext, HpkeError> {
+ let (kem_output, mut ctx) = self.setup_sender(remote_key, info).await?;
+ Ok(HpkeCiphertext { kem_output, ciphertext: ctx.seal(aad, pt).await? })
+ }
+
+ /// Sets up HPKE receiver context.
+ #[cfg_attr(not(mls_build_async), maybe_async::must_be_sync)]
+ pub async fn setup_receiver(
+ &self,
+ enc: &[u8],
+ local_secret: &HpkeSecretKey,
+ info: &[u8],
+ ) -> Result<ContextR, HpkeError> {
+ let params = Self::cipher_suite_to_params(self.0)?;
+ match hpke::RecipientContext::new(¶ms, local_secret, enc, info) {
+ Some(ctx) => Ok(ContextR(ctx.into())),
+ None => Err(HpkeError::BoringsslError),
+ }
+ }
+
+ /// Sets up HPKE receiver context and decrypts `ciphertext` with optional associated data `aad`.
+ #[cfg_attr(not(mls_build_async), maybe_async::must_be_sync)]
+ pub async fn open(
+ &self,
+ ciphertext: &HpkeCiphertext,
+ local_secret: &HpkeSecretKey,
+ info: &[u8],
+ aad: Option<&[u8]>,
+ ) -> Result<Vec<u8>, HpkeError> {
+ let mut ctx = self.setup_receiver(&ciphertext.kem_output, local_secret, info).await?;
+ ctx.open(aad, &ciphertext.ciphertext).await
+ }
+
+ fn cipher_suite_to_params(cipher_suite: CipherSuite) -> Result<hpke::Params, HpkeError> {
+ match cipher_suite {
+ CipherSuite::CURVE25519_AES128 => Ok(hpke::Params::new(
+ hpke::Kem::X25519HkdfSha256,
+ hpke::Kdf::HkdfSha256,
+ hpke::Aead::Aes128Gcm,
+ )),
+ CipherSuite::CURVE25519_CHACHA => Ok(hpke::Params::new(
+ hpke::Kem::X25519HkdfSha256,
+ hpke::Kdf::HkdfSha256,
+ hpke::Aead::Chacha20Poly1305,
+ )),
+ _ => Err(HpkeError::UnsupportedCipherSuite),
+ }
+ }
+}
+
+#[cfg(all(not(mls_build_async), test))]
+mod test {
+ use super::{DhKem, Hpke, KdfWrapper};
+ use crate::ecdh::Ecdh;
+ use crate::kdf::Kdf;
+ use crate::test_helpers::decode_hex;
+ use mls_rs_core::crypto::{
+ CipherSuite, HpkeContextR, HpkeContextS, HpkePublicKey, HpkeSecretKey,
+ };
+ use mls_rs_crypto_traits::{AeadId, KdfId, KemId, KemType};
+ use std::thread;
+
+ // https://www.rfc-editor.org/rfc/rfc9180.html#section-5.1-8
+ fn hpke_suite_id(cipher_suite: CipherSuite) -> Vec<u8> {
+ [
+ b"HPKE",
+ &(KemId::new(cipher_suite).unwrap() as u16).to_be_bytes() as &[u8],
+ &(KdfId::new(cipher_suite).unwrap() as u16).to_be_bytes() as &[u8],
+ &(AeadId::new(cipher_suite).unwrap() as u16).to_be_bytes() as &[u8],
+ ]
+ .concat()
+ }
+
+ #[test]
+ fn kdf_labeled_extract() {
+ let cipher_suite = CipherSuite::CURVE25519_AES128;
+ let suite_id = hpke_suite_id(cipher_suite);
+ let kdf = KdfWrapper::new(suite_id, Kdf::new(cipher_suite).unwrap());
+
+ // https://www.rfc-editor.org/rfc/rfc9180.html#appendix-A.1.1
+ let shared_secret: [u8; 32] =
+ decode_hex("fe0e18c9f024ce43799ae393c7e8fe8fce9d218875e8227b0187c04e7d2ea1fc");
+ let expected_secret: [u8; 32] =
+ decode_hex("12fff91991e93b48de37e7daddb52981084bd8aa64289c3788471d9a9712f397");
+ let label = b"secret";
+
+ let secret = kdf.labeled_extract(&shared_secret, label, &[]).unwrap();
+ assert_eq!(secret, expected_secret);
+ }
+
+ #[test]
+ fn kdf_labeled_expand() {
+ let cipher_suite = CipherSuite::CURVE25519_AES128;
+ let suite_id = hpke_suite_id(cipher_suite);
+ let kdf = KdfWrapper::new(suite_id, Kdf::new(cipher_suite).unwrap());
+
+ // https://www.rfc-editor.org/rfc/rfc9180.html#appendix-A.1.1
+ let secret: [u8; 32] =
+ decode_hex("12fff91991e93b48de37e7daddb52981084bd8aa64289c3788471d9a9712f397");
+ let key_schedule_ctx : [u8; 65] = decode_hex("00725611c9d98c07c03f60095cd32d400d8347d45ed67097bbad50fc56da742d07cb6cffde367bb0565ba28bb02c90744a20f5ef37f30523526106f637abb05449");
+ let expected_key: [u8; 16] = decode_hex("4531685d41d65f03dc48f6b8302c05b0");
+ let label = b"key";
+
+ let key = kdf.labeled_expand(&secret, label, &key_schedule_ctx, 16).unwrap();
+ assert_eq!(key, expected_key);
+ }
+
+ #[test]
+ fn dh_kem_kem_id() {
+ let cipher_suite = CipherSuite::CURVE25519_CHACHA;
+ let dh = Ecdh::new(cipher_suite).unwrap();
+ let kdf = Kdf::new(cipher_suite).unwrap();
+ let kem = DhKem::new(cipher_suite, dh, kdf).unwrap();
+
+ assert_eq!(kem.kem_id(), 32);
+ }
+
+ #[test]
+ fn dh_kem_generate() {
+ let cipher_suite = CipherSuite::CURVE25519_AES128;
+ let dh = Ecdh::new(cipher_suite).unwrap();
+ let kdf = Kdf::new(cipher_suite).unwrap();
+ let kem = DhKem::new(cipher_suite, dh, kdf).unwrap();
+
+ assert!(kem.generate().is_ok());
+ }
+
+ #[test]
+ fn dh_kem_derive() {
+ let cipher_suite = CipherSuite::CURVE25519_CHACHA;
+ let dh = Ecdh::new(cipher_suite).unwrap();
+ let kdf = Kdf::new(cipher_suite).unwrap();
+ let kem = DhKem::new(cipher_suite, dh, kdf).unwrap();
+
+ // https://www.rfc-editor.org/rfc/rfc9180.html#appendix-A.2.1
+ let ikm: [u8; 32] =
+ decode_hex("909a9b35d3dc4713a5e72a4da274b55d3d3821a37e5d099e74a647db583a904b"); // ikmE
+ let expected_sk = HpkeSecretKey::from(
+ decode_hex::<32>("f4ec9b33b792c372c1d2c2063507b684ef925b8c75a42dbcbf57d63ccd381600")
+ .to_vec(),
+ ); // skEm
+ let expected_pk = HpkePublicKey::from(
+ decode_hex::<32>("1afa08d3dec047a643885163f1180476fa7ddb54c6a8029ea33f95796bf2ac4a")
+ .to_vec(),
+ ); // pkEm
+
+ let (sk, pk) = kem.derive(&ikm).unwrap();
+ assert_eq!(sk, expected_sk);
+ assert_eq!(pk, expected_pk);
+ }
+
+ #[test]
+ fn dh_kem_public_key_validate() {
+ let cipher_suite = CipherSuite::CURVE25519_AES128;
+ let dh = Ecdh::new(cipher_suite).unwrap();
+ let kdf = Kdf::new(cipher_suite).unwrap();
+ let kem = DhKem::new(cipher_suite, dh, kdf).unwrap();
+
+ // https://www.rfc-editor.org/rfc/rfc7748.html#section-6.1
+ let public_key = HpkePublicKey::from(
+ decode_hex::<32>("8520f0098930a754748b7ddcb43ef75a0dbf3a0d26381af4eba4a98eaa9b4e6a")
+ .to_vec(),
+ );
+ assert!(kem.public_key_validate(&public_key).is_ok());
+ }
+
+ #[test]
+ fn hpke_seal_open() {
+ let hpke = Hpke::new(CipherSuite::CURVE25519_AES128);
+
+ // https://www.rfc-editor.org/rfc/rfc9180.html#appendix-A.1.1
+ let receiver_pub_key = HpkePublicKey::from(
+ decode_hex::<32>("3948cfe0ad1ddb695d780e59077195da6c56506b027329794ab02bca80815c4d")
+ .to_vec(),
+ );
+ let receiver_priv_key = HpkeSecretKey::from(
+ decode_hex::<32>("4612c550263fc8ad58375df3f557aac531d26850903e55a9f23f21d8534e8ac8")
+ .to_vec(),
+ );
+
+ let info = b"some_info";
+ let plaintext = b"plaintext";
+ let associated_data = b"some_ad";
+
+ let ct = hpke.seal(&receiver_pub_key, info, Some(associated_data), plaintext).unwrap();
+ assert_eq!(
+ plaintext.as_ref(),
+ hpke.open(&ct, &receiver_priv_key, info, Some(associated_data)).unwrap(),
+ );
+ }
+
+ #[test]
+ fn hpke_context_seal_open() {
+ let hpke = Hpke::new(CipherSuite::CURVE25519_AES128);
+
+ // https://www.rfc-editor.org/rfc/rfc9180.html#appendix-A.1.1
+ let receiver_pub_key = HpkePublicKey::from(
+ decode_hex::<32>("3948cfe0ad1ddb695d780e59077195da6c56506b027329794ab02bca80815c4d")
+ .to_vec(),
+ );
+ let receiver_priv_key = HpkeSecretKey::from(
+ decode_hex::<32>("4612c550263fc8ad58375df3f557aac531d26850903e55a9f23f21d8534e8ac8")
+ .to_vec(),
+ );
+
+ let info = b"some_info";
+ let plaintext = b"plaintext";
+ let associated_data = b"some_ad";
+
+ let (enc, mut sender_ctx) = hpke.setup_sender(&receiver_pub_key, info).unwrap();
+ let mut receiver_ctx = hpke.setup_receiver(&enc, &receiver_priv_key, info).unwrap();
+ let ct = sender_ctx.seal(Some(associated_data), plaintext).unwrap();
+ assert_eq!(plaintext.as_ref(), receiver_ctx.open(Some(associated_data), &ct).unwrap(),);
+ }
+
+ #[test]
+ fn hpke_context_seal_open_multithreaded() {
+ let hpke = Hpke::new(CipherSuite::CURVE25519_AES128);
+
+ // https://www.rfc-editor.org/rfc/rfc9180.html#appendix-A.1.1
+ let receiver_pub_key = HpkePublicKey::from(
+ decode_hex::<32>("3948cfe0ad1ddb695d780e59077195da6c56506b027329794ab02bca80815c4d")
+ .to_vec(),
+ );
+ let receiver_priv_key = HpkeSecretKey::from(
+ decode_hex::<32>("4612c550263fc8ad58375df3f557aac531d26850903e55a9f23f21d8534e8ac8")
+ .to_vec(),
+ );
+
+ let info = b"some_info";
+ let plaintext = b"plaintext";
+ let associated_data = b"some_ad";
+
+ let (enc, mut sender_ctx) = hpke.setup_sender(&receiver_pub_key, info).unwrap();
+ let mut receiver_ctx = hpke.setup_receiver(&enc, &receiver_priv_key, info).unwrap();
+
+ let pool = thread::spawn(move || {
+ for _ in 1..100 {
+ let ct = sender_ctx.seal(Some(associated_data), plaintext).unwrap();
+ assert_eq!(
+ plaintext.as_ref(),
+ receiver_ctx.open(Some(associated_data), &ct).unwrap(),
+ );
+ }
+ });
+ pool.join().unwrap();
+ }
+
+ #[test]
+ fn hpke_context_export() {
+ let hpke = Hpke::new(CipherSuite::CURVE25519_AES128);
+
+ // https://www.rfc-editor.org/rfc/rfc9180.html#appendix-A.1.1
+ let receiver_pub_key = HpkePublicKey::from(
+ decode_hex::<32>("3948cfe0ad1ddb695d780e59077195da6c56506b027329794ab02bca80815c4d")
+ .to_vec(),
+ );
+ let receiver_priv_key = HpkeSecretKey::from(
+ decode_hex::<32>("4612c550263fc8ad58375df3f557aac531d26850903e55a9f23f21d8534e8ac8")
+ .to_vec(),
+ );
+
+ let info = b"some_info";
+ let exporter_ctx = b"export_ctx";
+
+ let (enc, sender_ctx) = hpke.setup_sender(&receiver_pub_key, info).unwrap();
+ let receiver_ctx = hpke.setup_receiver(&enc, &receiver_priv_key, info).unwrap();
+ assert_eq!(
+ sender_ctx.export(exporter_ctx, 32).unwrap(),
+ receiver_ctx.export(exporter_ctx, 32).unwrap(),
+ );
+ }
+
+ #[test]
+ fn hpke_unsupported_cipher_suites() {
+ // https://www.rfc-editor.org/rfc/rfc9180.html#appendix-A.1.1
+ let receiver_pub_key = HpkePublicKey::from(
+ decode_hex::<32>("3948cfe0ad1ddb695d780e59077195da6c56506b027329794ab02bca80815c4d")
+ .to_vec(),
+ );
+
+ for suite in vec![
+ CipherSuite::P256_AES128,
+ CipherSuite::P384_AES256,
+ CipherSuite::P521_AES256,
+ CipherSuite::CURVE448_CHACHA,
+ CipherSuite::CURVE448_AES256,
+ ] {
+ assert!(Hpke::new(suite).setup_sender(&receiver_pub_key, b"some_info").is_err());
+ }
+ }
+}