blob: 4bb4aa23c3f5511e2e7ec8921543d2d318e84e30 [file] [log] [blame]
Cindy Lin6ec3c2b2024-05-16 07:39:23 +00001// 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
17use bssl_crypto::hpke;
18use mls_rs_core::crypto::{
19 CipherSuite, HpkeCiphertext, HpkeContextR, HpkeContextS, HpkePublicKey, HpkeSecretKey,
20};
21use mls_rs_core::error::{AnyError, IntoAnyError};
22use mls_rs_crypto_traits::{DhType, KdfType, KemId, KemResult, KemType};
23use std::sync::Mutex;
24use thiserror::Error;
25
26/// Errors returned from HPKE.
27#[derive(Debug, Error)]
28pub 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
43impl 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)]
50pub(crate) struct KdfWrapper<KDF: KdfType> {
51 suite_id: Vec<u8>,
52 kdf: KDF,
53}
54
55impl<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)]
89pub struct DhKem<DH: DhType, KDF: KdfType> {
90 dh: DH,
91 kdf: KdfWrapper<KDF>,
92 kem_id: KemId,
93 n_secret: usize,
94}
95
96impl<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)]
112impl<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.
171pub 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)]
176impl 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.
191pub 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)]
196impl 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)]
220pub struct Hpke(pub CipherSuite);
221
222impl 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(&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)]
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(&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)]
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))]
301mod 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}