Cindy Lin | 6ec3c2b | 2024-05-16 07:39:23 +0000 | [diff] [blame^] | 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 { |
| 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> { |
| 56 | 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)] |
| 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)] |
| 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. |
| 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 | |
| 115 | fn kem_id(&self) -> u16 { |
| 116 | self.kem_id as u16 |
| 117 | } |
| 118 | |
| 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 |
| 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 | |
| 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. |
| 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. |
| 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)] |
| 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)] |
| 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)] |
| 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)] |
| 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. |
| 224 | 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)] |
| 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(¶ms, 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)] |
| 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)] |
| 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(¶ms, 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)] |
| 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 | |
| 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 |
| 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] |
| 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] |
| 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] |
| 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] |
| 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] |
| 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] |
| 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] |
| 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] |
| 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] |
| 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] |
| 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] |
| 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 | } |