1use crate::{scoped, with_output_vec, with_output_vec_fallible, FfiSlice};
77use alloc::vec::Vec;
78
79#[derive(Clone, Copy)]
81pub enum Kem {
82 #[allow(missing_docs)]
83 X25519HkdfSha256 = 32,
84}
85
86impl Kem {
87 fn as_ffi_ptr(&self) -> *const bssl_sys::EVP_HPKE_KEM {
88 unsafe {
90 match self {
91 Kem::X25519HkdfSha256 => bssl_sys::EVP_hpke_x25519_hkdf_sha256(),
92 }
93 }
94 }
95
96 pub fn generate_keypair(&self) -> (Vec<u8>, Vec<u8>) {
98 let mut key = scoped::EvpHpkeKey::new();
99 let ret =
102 unsafe { bssl_sys::EVP_HPKE_KEY_generate(key.as_mut_ffi_ptr(), self.as_ffi_ptr()) };
103 assert_eq!(ret, 1);
106
107 fn get_value_from_key(
108 key: &scoped::EvpHpkeKey,
109 accessor: unsafe extern "C" fn(
110 *const bssl_sys::EVP_HPKE_KEY,
111 *mut u8,
113 *mut usize,
115 usize,
117 ) -> core::ffi::c_int,
118 max_len: usize,
119 ) -> Vec<u8> {
120 unsafe {
121 with_output_vec(max_len, |out| {
122 let mut out_len = 0usize;
123 let ret = accessor(key.as_ffi_ptr(), out, &mut out_len, max_len);
124 assert_eq!(ret, 1);
126 assert!(out_len <= max_len);
127 out_len
129 })
130 }
131 }
132
133 let pub_key = get_value_from_key(
134 &key,
135 bssl_sys::EVP_HPKE_KEY_public_key,
136 bssl_sys::EVP_HPKE_MAX_PUBLIC_KEY_LENGTH as usize,
137 );
138 let priv_key = get_value_from_key(
139 &key,
140 bssl_sys::EVP_HPKE_KEY_private_key,
141 bssl_sys::EVP_HPKE_MAX_PRIVATE_KEY_LENGTH as usize,
142 );
143 (pub_key, priv_key)
144 }
145}
146
147#[derive(Clone, Copy)]
149pub enum Kdf {
150 #[allow(missing_docs)]
151 HkdfSha256 = 1,
152}
153
154#[derive(Clone, Copy)]
156#[allow(missing_docs)]
157pub enum Aead {
158 Aes128Gcm = 1,
159 Aes256Gcm = 2,
160 Chacha20Poly1305 = 3,
161}
162
163impl Aead {
164 fn from_rfc_id(n: u16) -> Option<Aead> {
165 let ret = match n {
166 1 => Aead::Aes128Gcm,
167 2 => Aead::Aes256Gcm,
168 3 => Aead::Chacha20Poly1305,
169 _ => return None,
170 };
171 assert_eq!(n, ret as u16);
173 Some(ret)
174 }
175
176 fn as_ffi_ptr(&self) -> *const bssl_sys::EVP_HPKE_AEAD {
177 unsafe {
179 match self {
180 Aead::Aes128Gcm => bssl_sys::EVP_hpke_aes_128_gcm(),
181 Aead::Aes256Gcm => bssl_sys::EVP_hpke_aes_256_gcm(),
182 Aead::Chacha20Poly1305 => bssl_sys::EVP_hpke_chacha20_poly1305(),
183 }
184 }
185 }
186}
187
188const MAX_ENCAPSULATED_KEY_LEN: usize = bssl_sys::EVP_HPKE_MAX_ENC_LENGTH as usize;
190
191pub struct Params {
193 kem: *const bssl_sys::EVP_HPKE_KEM,
194 kdf: *const bssl_sys::EVP_HPKE_KDF,
195 aead: *const bssl_sys::EVP_HPKE_AEAD,
196}
197
198impl Params {
199 pub fn new(_kem: Kem, _kdf: Kdf, aead: Aead) -> Self {
201 unsafe {
204 Self {
205 kem: bssl_sys::EVP_hpke_x25519_hkdf_sha256(),
207 kdf: bssl_sys::EVP_hpke_hkdf_sha256(),
208 aead: aead.as_ffi_ptr(),
209 }
210 }
211 }
212
213 pub fn new_from_rfc_ids(kem_id: u16, kdf_id: u16, aead_id: u16) -> Option<Self> {
215 let kem = Kem::X25519HkdfSha256;
216 let kdf = Kdf::HkdfSha256;
217 let aead = Aead::from_rfc_id(aead_id)?;
218
219 if kem_id != kem as u16 || kdf_id != kdf as u16 {
220 return None;
221 }
222 Some(Self::new(kem, kdf, aead))
223 }
224}
225
226pub struct SenderContext(scoped::EvpHpkeCtx);
228
229impl SenderContext {
230 pub fn new(params: &Params, recipient_pub_key: &[u8], info: &[u8]) -> Option<(Self, Vec<u8>)> {
237 let mut ctx = scoped::EvpHpkeCtx::new();
238 unsafe {
239 with_output_vec_fallible(MAX_ENCAPSULATED_KEY_LEN, |enc_key_buf| {
240 let mut enc_key_len = 0usize;
241 let ret = bssl_sys::EVP_HPKE_CTX_setup_sender(
246 ctx.as_mut_ffi_ptr(),
247 enc_key_buf,
248 &mut enc_key_len,
249 MAX_ENCAPSULATED_KEY_LEN,
250 params.kem,
251 params.kdf,
252 params.aead,
253 recipient_pub_key.as_ffi_ptr(),
254 recipient_pub_key.len(),
255 info.as_ffi_ptr(),
256 info.len(),
257 );
258 if ret == 1 {
259 Some(enc_key_len)
260 } else {
261 None
262 }
263 })
264 }
265 .map(|enc_key| (Self(ctx), enc_key))
266 }
267
268 pub fn seal(&mut self, plaintext: &[u8], aad: &[u8]) -> Vec<u8> {
276 #[allow(clippy::expect_used)]
278 let max_out_len = plaintext
279 .len()
280 .checked_add(unsafe { bssl_sys::EVP_HPKE_CTX_max_overhead(self.0.as_ffi_ptr()) })
281 .expect("Maximum output length calculation overflow");
282 unsafe {
283 with_output_vec(max_out_len, |out_buf| {
284 let mut out_len = 0usize;
285 let result = bssl_sys::EVP_HPKE_CTX_seal(
289 self.0.as_mut_ffi_ptr(),
290 out_buf,
291 &mut out_len,
292 max_out_len,
293 plaintext.as_ffi_ptr(),
294 plaintext.len(),
295 aad.as_ffi_ptr(),
296 aad.len(),
297 );
298 assert_eq!(result, 1);
299 out_len
300 })
301 }
302 }
303
304 pub fn export(&mut self, context: &[u8], out_len: usize) -> Vec<u8> {
307 unsafe {
308 with_output_vec(out_len, |out_buf| {
309 let ret = bssl_sys::EVP_HPKE_CTX_export(
314 self.0.as_mut_ffi_ptr(),
315 out_buf,
316 out_len,
317 context.as_ffi_ptr(),
318 context.len(),
319 );
320 assert_eq!(ret, 1);
321 out_len
322 })
323 }
324 }
325}
326
327pub struct RecipientContext(scoped::EvpHpkeCtx);
329
330impl RecipientContext {
331 pub fn new(
340 params: &Params,
341 recipient_priv_key: &[u8],
342 encapsulated_key: &[u8],
343 info: &[u8],
344 ) -> Option<Self> {
345 let mut hpke_key = scoped::EvpHpkeKey::new();
346
347 let result = unsafe {
349 bssl_sys::EVP_HPKE_KEY_init(
350 hpke_key.as_mut_ffi_ptr(),
351 params.kem,
352 recipient_priv_key.as_ffi_ptr(),
353 recipient_priv_key.len(),
354 )
355 };
356 if result != 1 {
357 return None;
358 }
359
360 let mut ctx = scoped::EvpHpkeCtx::new();
361
362 let result = unsafe {
368 bssl_sys::EVP_HPKE_CTX_setup_recipient(
369 ctx.as_mut_ffi_ptr(),
370 hpke_key.as_ffi_ptr(),
371 params.kdf,
372 params.aead,
373 encapsulated_key.as_ffi_ptr(),
374 encapsulated_key.len(),
375 info.as_ffi_ptr(),
376 info.len(),
377 )
378 };
379 if result == 1 {
380 Some(Self(ctx))
381 } else {
382 None
383 }
384 }
385
386 pub fn open(&mut self, ciphertext: &[u8], aad: &[u8]) -> Option<Vec<u8>> {
391 let max_out_len = ciphertext.len();
392 unsafe {
393 with_output_vec_fallible(max_out_len, |out_buf| {
394 let mut out_len = 0usize;
395 let result = bssl_sys::EVP_HPKE_CTX_open(
399 self.0.as_mut_ffi_ptr(),
400 out_buf,
401 &mut out_len,
402 max_out_len,
403 ciphertext.as_ffi_ptr(),
404 ciphertext.len(),
405 aad.as_ffi_ptr(),
406 aad.len(),
407 );
408 if result == 1 {
409 Some(out_len)
410 } else {
411 None
412 }
413 })
414 }
415 }
416
417 pub fn export(&mut self, context: &[u8], out_len: usize) -> Vec<u8> {
420 unsafe {
421 with_output_vec(out_len, |out_buf| {
422 let ret = bssl_sys::EVP_HPKE_CTX_export(
427 self.0.as_mut_ffi_ptr(),
428 out_buf,
429 out_len,
430 context.as_ffi_ptr(),
431 context.len(),
432 );
433 assert_eq!(ret, 1);
434 out_len
435 })
436 }
437 }
438}
439
440#[cfg(test)]
441mod test {
442 use super::*;
443 use crate::test_helpers::decode_hex;
444
445 struct TestVector {
446 kem_id: u16,
447 kdf_id: u16,
448 aead_id: u16,
449 info: [u8; 20],
450 seed_for_testing: [u8; 32], recipient_pub_key: [u8; 32], recipient_priv_key: [u8; 32], encapsulated_key: [u8; 32], plaintext: [u8; 29], associated_data: [u8; 7], ciphertext: [u8; 45], exporter_context: [u8; 11],
458 exported_value: [u8; 32],
459 }
460
461 fn x25519_hkdf_sha256_hkdf_sha256_aes_128_gcm() -> TestVector {
463 TestVector {
464 kem_id: 32,
465 kdf_id: 1,
466 aead_id: 1,
467 info: decode_hex("4f6465206f6e2061204772656369616e2055726e"),
468 seed_for_testing: decode_hex("52c4a758a802cd8b936eceea314432798d5baf2d7e9235dc084ab1b9cfa2f736"),
469 recipient_pub_key: decode_hex("3948cfe0ad1ddb695d780e59077195da6c56506b027329794ab02bca80815c4d"),
470 recipient_priv_key: decode_hex("4612c550263fc8ad58375df3f557aac531d26850903e55a9f23f21d8534e8ac8"),
471 encapsulated_key: decode_hex("37fda3567bdbd628e88668c3c8d7e97d1d1253b6d4ea6d44c150f741f1bf4431"),
472 plaintext: decode_hex("4265617574792069732074727574682c20747275746820626561757479"),
473 associated_data: decode_hex("436f756e742d30"),
474 ciphertext: decode_hex("f938558b5d72f1a23810b4be2ab4f84331acc02fc97babc53a52ae8218a355a96d8770ac83d07bea87e13c512a"),
475 exporter_context: decode_hex("54657374436f6e74657874"),
476 exported_value: decode_hex("e9e43065102c3836401bed8c3c3c75ae46be1639869391d62c61f1ec7af54931"),
477 }
478 }
479
480 fn x25519_hkdf_sha256_hkdf_sha256_chacha20_poly1305() -> TestVector {
482 TestVector {
483 kem_id: 32,
484 kdf_id: 1,
485 aead_id: 3,
486 info: decode_hex("4f6465206f6e2061204772656369616e2055726e"),
487 seed_for_testing: decode_hex("f4ec9b33b792c372c1d2c2063507b684ef925b8c75a42dbcbf57d63ccd381600"),
488 recipient_pub_key: decode_hex("4310ee97d88cc1f088a5576c77ab0cf5c3ac797f3d95139c6c84b5429c59662a"),
489 recipient_priv_key: decode_hex("8057991eef8f1f1af18f4a9491d16a1ce333f695d4db8e38da75975c4478e0fb"),
490 encapsulated_key: decode_hex("1afa08d3dec047a643885163f1180476fa7ddb54c6a8029ea33f95796bf2ac4a"),
491 plaintext: decode_hex("4265617574792069732074727574682c20747275746820626561757479"),
492 associated_data: decode_hex("436f756e742d30"),
493 ciphertext: decode_hex("1c5250d8034ec2b784ba2cfd69dbdb8af406cfe3ff938e131f0def8c8b60b4db21993c62ce81883d2dd1b51a28"),
494 exporter_context: decode_hex("54657374436f6e74657874"),
495 exported_value: decode_hex("5acb09211139c43b3090489a9da433e8a30ee7188ba8b0a9a1ccf0c229283e53"),
496 }
497 }
498
499 #[test]
500 fn all_algorithms() {
501 let kems = vec![Kem::X25519HkdfSha256];
502 let kdfs = vec![Kdf::HkdfSha256];
503 let aeads = vec![Aead::Aes128Gcm, Aead::Aes256Gcm, Aead::Chacha20Poly1305];
504 let plaintext: &[u8] = b"plaintext";
505 let aad: &[u8] = b"aad";
506 let info: &[u8] = b"info";
507
508 for kem in &kems {
509 let (pub_key, priv_key) = kem.generate_keypair();
510 for kdf in &kdfs {
511 for aead in &aeads {
512 let params =
513 Params::new_from_rfc_ids(*kem as u16, *kdf as u16, *aead as u16).unwrap();
514
515 let (mut send_ctx, encapsulated_key) =
516 SenderContext::new(¶ms, &pub_key, info).unwrap();
517 let mut recv_ctx =
518 RecipientContext::new(¶ms, &priv_key, &encapsulated_key, info).unwrap();
519 assert_eq!(
520 plaintext,
521 recv_ctx
522 .open(send_ctx.seal(plaintext, aad).as_ref(), aad)
523 .unwrap()
524 );
525 assert_eq!(
526 plaintext,
527 recv_ctx
528 .open(send_ctx.seal(plaintext, aad).as_ref(), aad)
529 .unwrap()
530 );
531 assert!(recv_ctx.open(b"nonsense", aad).is_none());
532 }
533 }
534 }
535 }
536
537 fn new_sender_context_for_testing(
538 params: &Params,
539 recipient_pub_key: &[u8],
540 info: &[u8],
541 seed_for_testing: &[u8],
542 ) -> (SenderContext, Vec<u8>) {
543 let mut ctx = scoped::EvpHpkeCtx::new();
544
545 let encapsulated_key = unsafe {
546 with_output_vec_fallible(MAX_ENCAPSULATED_KEY_LEN, |enc_key_buf| {
547 let mut enc_key_len = 0usize;
548 let result = bssl_sys::EVP_HPKE_CTX_setup_sender_with_seed_for_testing(
553 ctx.as_mut_ffi_ptr(),
554 enc_key_buf,
555 &mut enc_key_len,
556 MAX_ENCAPSULATED_KEY_LEN,
557 params.kem,
558 params.kdf,
559 params.aead,
560 recipient_pub_key.as_ffi_ptr(),
561 recipient_pub_key.len(),
562 info.as_ffi_ptr(),
563 info.len(),
564 seed_for_testing.as_ffi_ptr(),
565 seed_for_testing.len(),
566 );
567 if result == 1 {
568 Some(enc_key_len)
569 } else {
570 None
571 }
572 })
573 }
574 .unwrap();
575 (SenderContext(ctx), encapsulated_key)
576 }
577
578 #[test]
579 fn seal_with_vector() {
580 for test in vec![
581 x25519_hkdf_sha256_hkdf_sha256_aes_128_gcm(),
582 x25519_hkdf_sha256_hkdf_sha256_chacha20_poly1305(),
583 ] {
584 let params = Params::new_from_rfc_ids(test.kem_id, test.kdf_id, test.aead_id).unwrap();
585
586 let (mut ctx, encapsulated_key) = new_sender_context_for_testing(
587 ¶ms,
588 &test.recipient_pub_key,
589 &test.info,
590 &test.seed_for_testing,
591 );
592
593 assert_eq!(encapsulated_key, test.encapsulated_key.to_vec());
594
595 let ciphertext = ctx.seal(&test.plaintext, &test.associated_data);
596 assert_eq!(&ciphertext, test.ciphertext.as_ref());
597 }
598 }
599
600 #[test]
601 fn open_with_vector() {
602 for test in vec![
603 x25519_hkdf_sha256_hkdf_sha256_aes_128_gcm(),
604 x25519_hkdf_sha256_hkdf_sha256_chacha20_poly1305(),
605 ] {
606 let params = Params::new_from_rfc_ids(test.kem_id, test.kdf_id, test.aead_id).unwrap();
607
608 let mut ctx = RecipientContext::new(
609 ¶ms,
610 &test.recipient_priv_key,
611 &test.encapsulated_key,
612 &test.info,
613 )
614 .unwrap();
615
616 let plaintext = ctx.open(&test.ciphertext, &test.associated_data).unwrap();
617 assert_eq!(&plaintext, test.plaintext.as_ref());
618 }
619 }
620
621 #[test]
622 fn export_with_vector() {
623 for test in vec![
624 x25519_hkdf_sha256_hkdf_sha256_aes_128_gcm(),
625 x25519_hkdf_sha256_hkdf_sha256_chacha20_poly1305(),
626 ] {
627 let params = Params::new_from_rfc_ids(test.kem_id, test.kdf_id, test.aead_id).unwrap();
628
629 let (mut sender_ctx, _encapsulated_key) = new_sender_context_for_testing(
630 ¶ms,
631 &test.recipient_pub_key,
632 &test.info,
633 &test.seed_for_testing,
634 );
635 assert_eq!(
636 test.exported_value.as_ref(),
637 sender_ctx.export(&test.exporter_context, test.exported_value.len())
638 );
639
640 let mut recipient_ctx = RecipientContext::new(
641 ¶ms,
642 &test.recipient_priv_key,
643 &test.encapsulated_key,
644 &test.info,
645 ).unwrap();
646 assert_eq!(
647 test.exported_value.as_ref(),
648 recipient_ctx.export(&test.exporter_context, test.exported_value.len())
649 );
650 }
651 }
652
653 #[test]
654 fn disallowed_params_fail() {
655 let vec: TestVector = x25519_hkdf_sha256_hkdf_sha256_aes_128_gcm();
656
657 assert!(Params::new_from_rfc_ids(0, vec.kdf_id, vec.aead_id).is_none());
658 assert!(Params::new_from_rfc_ids(vec.kem_id, 0, vec.aead_id).is_none());
659 assert!(Params::new_from_rfc_ids(vec.kem_id, vec.kdf_id, 0).is_none());
660 }
661
662 #[test]
663 fn bad_recipient_pub_key_fails() {
664 let vec: TestVector = x25519_hkdf_sha256_hkdf_sha256_aes_128_gcm();
665 let params = Params::new_from_rfc_ids(vec.kem_id, vec.kdf_id, vec.aead_id).unwrap();
666
667 assert!(SenderContext::new(¶ms, b"", &vec.info).is_none());
668 }
669
670 #[test]
671 fn bad_recipient_priv_key_fails() {
672 let vec: TestVector = x25519_hkdf_sha256_hkdf_sha256_aes_128_gcm();
673 let params = Params::new_from_rfc_ids(vec.kem_id, vec.kdf_id, vec.aead_id).unwrap();
674
675 assert!(RecipientContext::new(¶ms, b"", &vec.encapsulated_key, &vec.info).is_none());
676 }
677}