Skip to main content

bssl_crypto/
hpke.rs

1// Copyright 2024 The BoringSSL Authors
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//     https://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//! HPKE provides public key encryption of arbitrary-length messages. It
18//! establishes contexts that produce/consume an ordered sequence of
19//! ciphertexts that are both encrypted and authenticated.
20//!
21//! See RFC 9180 for more details.
22//!
23//! ```
24//! use bssl_crypto::hpke;
25//!
26//! let kem = hpke::Kem::X25519HkdfSha256;
27//! let (pub_key, priv_key) = kem.generate_keypair();
28//! // Distribute `pub_key` to people who want to send you messages.
29//!
30//! // On the sending side...
31//! let params = hpke::Params::new(kem, hpke::Kdf::HkdfSha256, hpke::Aead::Aes128Gcm);
32//! let info : &[u8] = b"mutual context";
33//! let (mut sender_ctx, encapsulated_key) =
34//!     hpke::SenderContext::new(&params, &pub_key, info).unwrap();
35//! // Transmit the `encapsulated_key` to the receiver, followed by one or
36//! // more ciphertexts...
37//! let aad = b"associated_data";
38//! let plaintext1 : &[u8] = b"plaintext1";
39//! let msg1 = sender_ctx.seal(plaintext1, aad);
40//! let plaintext2 : &[u8] = b"plaintext2";
41//! let msg2 = sender_ctx.seal(plaintext2, aad);
42//!
43//! // On the receiving side...
44//! let mut recipient_ctx = hpke::RecipientContext::new(
45//!     &params,
46//!     &priv_key,
47//!     &encapsulated_key,
48//!     info,
49//! ).unwrap();
50//!
51//! let received_plaintext1 = recipient_ctx.open(&msg1, aad).unwrap();
52//! assert_eq!(plaintext1, &received_plaintext1);
53//! let received_plaintext2 = recipient_ctx.open(&msg2, aad).unwrap();
54//! assert_eq!(plaintext2, &received_plaintext2);
55//!
56//! // Messages must be processed in order, so trying to `open` the second
57//! // message first will fail.
58//! let mut recipient_ctx = hpke::RecipientContext::new(
59//!     &params,
60//!     &priv_key,
61//!     &encapsulated_key,
62//!     info,
63//! ).unwrap();
64//!
65//! let received_plaintext2 = recipient_ctx.open(&msg2, aad);
66//! assert!(received_plaintext2.is_none());
67//!
68//! // There is also an interface for exporting secrets from both sender
69//! // and recipient contexts.
70//! let sender_export = sender_ctx.export(b"ctx", 32);
71//! let recipient_export = recipient_ctx.export(b"ctx", 32);
72//! assert_eq!(sender_export, recipient_export);
73//! ```
74
75use crate::{scoped, with_output_vec, with_output_vec_fallible, FfiSlice};
76use alloc::vec::Vec;
77
78use internal::HpkeKey;
79
80/// Supported KEM algorithms with values detailed in RFC 9180.
81#[derive(Clone, Copy)]
82#[repr(u16)]
83pub enum Kem {
84    /// KEM using DHKEM P-256 and HKDF-SHA256.
85    P256HkdfSha256 = 16, // 0x0010
86    /// KEM using DHKEM X25519 and HKDF-SHA256.
87    X25519HkdfSha256 = 32, // 0x0020
88    /// X-Wing hybrid KEM.
89    XWing = 25722, // 0x647a
90    /// ML-KEM-768.
91    MlKem768 = 65, // 0x0041
92    /// ML-KEM-1024.
93    MlKem1024 = 66, // 0x0042
94}
95
96impl Kem {
97    fn as_ffi_ptr(&self) -> *const bssl_sys::EVP_HPKE_KEM {
98        // Safety: this function returns a pointer to static data.
99        unsafe {
100            match self {
101                Kem::P256HkdfSha256 => bssl_sys::EVP_hpke_p256_hkdf_sha256(),
102                Kem::X25519HkdfSha256 => bssl_sys::EVP_hpke_x25519_hkdf_sha256(),
103                Kem::XWing => bssl_sys::EVP_hpke_xwing(),
104                Kem::MlKem768 => bssl_sys::EVP_hpke_mlkem768(),
105                Kem::MlKem1024 => bssl_sys::EVP_hpke_mlkem1024(),
106            }
107        }
108    }
109
110    fn from_rfc_id(n: u16) -> Option<Kem> {
111        match n {
112            n if n == Kem::P256HkdfSha256 as u16 => Some(Self::P256HkdfSha256),
113            n if n == Kem::X25519HkdfSha256 as u16 => Some(Self::X25519HkdfSha256),
114            n if n == Kem::XWing as u16 => Some(Self::XWing),
115            n if n == Kem::MlKem768 as u16 => Some(Self::MlKem768),
116            n if n == Kem::MlKem1024 as u16 => Some(Self::MlKem1024),
117            _ => None,
118        }
119    }
120
121    /// Generate a public and private key for this KEM.
122    pub fn generate_keypair(&self) -> (Vec<u8>, Vec<u8>) {
123        let mut key = scoped::EvpHpkeKey::new();
124        // Safety: `key` and `self` must be valid and this function doesn't
125        // take ownership of either.
126        let ret =
127            unsafe { bssl_sys::EVP_HPKE_KEY_generate(key.as_mut_ffi_ptr(), self.as_ffi_ptr()) };
128        // Key generation currently never fails, and out-of-memory is not
129        // handled by this crate.
130        assert_eq!(ret, 1);
131
132        let pub_key = Self::get_value_from_key(
133            &key,
134            bssl_sys::EVP_HPKE_KEY_public_key,
135            bssl_sys::EVP_HPKE_MAX_PUBLIC_KEY_LENGTH as usize,
136        );
137        let priv_key = Self::get_value_from_key(
138            &key,
139            bssl_sys::EVP_HPKE_KEY_private_key,
140            bssl_sys::EVP_HPKE_MAX_PRIVATE_KEY_LENGTH as usize,
141        );
142        (pub_key, priv_key)
143    }
144
145    /// Get a private key's corresponding public key, or `None` if the private
146    /// key is invalid.
147    pub fn public_from_private(&self, priv_key: &[u8]) -> Option<Vec<u8>> {
148        let HpkeKey { key } = self.parse_from_private_key(priv_key)?;
149
150        let pub_key = Self::get_value_from_key(
151            &key,
152            bssl_sys::EVP_HPKE_KEY_public_key,
153            bssl_sys::EVP_HPKE_MAX_PUBLIC_KEY_LENGTH as usize,
154        );
155        Some(pub_key)
156    }
157
158    /// Parse a private key in accordance to the given KEM scheme.
159    ///
160    /// The call returns `None` if `priv_key` is invalid.
161    pub fn parse_from_private_key(&self, priv_key: &[u8]) -> Option<HpkeKey> {
162        let mut key = scoped::EvpHpkeKey::new();
163        // Safety: `key`, `self`, and `priv_key` must be valid and this function
164        // doesn't take ownership of any of them.
165        let ret = unsafe {
166            bssl_sys::EVP_HPKE_KEY_init(
167                key.as_mut_ffi_ptr(),
168                self.as_ffi_ptr(),
169                priv_key.as_ffi_ptr(),
170                priv_key.len(),
171            )
172        };
173        (ret == 1).then_some(HpkeKey { key })
174    }
175
176    fn get_value_from_key(
177        key: &scoped::EvpHpkeKey,
178        accessor: unsafe extern "C" fn(
179            *const bssl_sys::EVP_HPKE_KEY,
180            // Output buffer.
181            *mut u8,
182            // Number of bytes written.
183            *mut usize,
184            // Maximum output size.
185            usize,
186        ) -> core::ffi::c_int,
187        max_len: usize,
188    ) -> Vec<u8> {
189        unsafe {
190            with_output_vec(max_len, |out| {
191                let mut out_len = 0usize;
192                let ret = accessor(key.as_ffi_ptr(), out, &mut out_len, max_len);
193                // If `max_len` is correct then these functions never fail.
194                assert_eq!(ret, 1);
195                assert!(out_len <= max_len);
196                // Safety: `out_len` bytes have been written, as required.
197                out_len
198            })
199        }
200    }
201}
202
203#[doc(hidden)]
204pub mod internal {
205    use crate::scoped;
206
207    /// HPKE key suitable for interfacing with TLS stack.
208    pub struct HpkeKey {
209        pub(crate) key: scoped::EvpHpkeKey,
210    }
211
212    impl HpkeKey {
213        /// Safety: the handle to the underlying key **shall not** be used for mutating access.
214        pub unsafe fn as_ffi_ptr(&self) -> *const bssl_sys::EVP_HPKE_KEY {
215            self.key.as_ffi_ptr()
216        }
217    }
218}
219
220/// Supported KDF algorithms with values detailed in §7.2 of [RFC 9180].
221///
222/// [RFC 9180]: <https://datatracker.ietf.org/doc/html/rfc9180#section-7.2>
223#[derive(Clone, Copy)]
224#[repr(u16)]
225pub enum Kdf {
226    /// HKDF-SHA256 as defined in [RFC 5869]
227    /// [RFC 5869]: <https://datatracker.ietf.org/doc/html/rfc5869>
228    HkdfSha256 = 1,
229}
230
231/// Supported AEAD algorithms with values detailed in §7.3 of [RFC 9180].
232///
233/// [RFC 9180]: <https://datatracker.ietf.org/doc/html/rfc9180#section-7.3>
234#[derive(Clone, Copy)]
235pub enum Aead {
236    /// AES-GCM-128 defined by [NIST](https://doi.org/10.6028/nist.sp.800-38d)
237    Aes128Gcm = 1,
238    /// AES-GCM-256 defined by [NIST](https://doi.org/10.6028/nist.sp.800-38d)
239    Aes256Gcm = 2,
240    /// ChaCha20-Poly1305 defined by [RFC 8439](https://datatracker.ietf.org/doc/html/rfc8439)
241    Chacha20Poly1305 = 3,
242}
243
244impl Aead {
245    fn from_rfc_id(n: u16) -> Option<Aead> {
246        let ret = match n {
247            1 => Aead::Aes128Gcm,
248            2 => Aead::Aes256Gcm,
249            3 => Aead::Chacha20Poly1305,
250            _ => return None,
251        };
252        // The mapping above must agree with the values in the enum.
253        assert_eq!(n, ret as u16);
254        Some(ret)
255    }
256
257    fn as_ffi_ptr(&self) -> *const bssl_sys::EVP_HPKE_AEAD {
258        // Safety: these functions all return pointers to static data.
259        unsafe {
260            match self {
261                Aead::Aes128Gcm => bssl_sys::EVP_hpke_aes_128_gcm(),
262                Aead::Aes256Gcm => bssl_sys::EVP_hpke_aes_256_gcm(),
263                Aead::Chacha20Poly1305 => bssl_sys::EVP_hpke_chacha20_poly1305(),
264            }
265        }
266    }
267}
268
269/// Maximum length of the encapsulated key for all currently supported KEMs.
270const MAX_ENCAPSULATED_KEY_LEN: usize = bssl_sys::EVP_HPKE_MAX_ENC_LENGTH as usize;
271
272/// HPKE parameters, including KEM, KDF, and AEAD.
273pub struct Params {
274    kem: *const bssl_sys::EVP_HPKE_KEM,
275    kdf: *const bssl_sys::EVP_HPKE_KDF,
276    aead: *const bssl_sys::EVP_HPKE_AEAD,
277}
278
279impl Params {
280    /// New `Params` from KEM, KDF, and AEAD enums.
281    pub fn new(kem: Kem, _kdf: Kdf, aead: Aead) -> Self {
282        Self {
283            kem: kem.as_ffi_ptr(),
284            // Only one KDF is supported thus far.
285            kdf: unsafe {
286                // Safety: EVP_hpke_hkdf_sha256 just returns pointer to static data.
287                bssl_sys::EVP_hpke_hkdf_sha256()
288            },
289            aead: aead.as_ffi_ptr(),
290        }
291    }
292
293    /// New `Params` from KEM, KDF, and AEAD IDs as detailed in RFC 9180.
294    pub fn new_from_rfc_ids(kem_id: u16, kdf_id: u16, aead_id: u16) -> Option<Self> {
295        let kem = Kem::from_rfc_id(kem_id)?;
296        let kdf = Kdf::HkdfSha256;
297        let aead = Aead::from_rfc_id(aead_id)?;
298
299        if kdf_id != kdf as u16 {
300            return None;
301        }
302        Some(Self::new(kem, kdf, aead))
303    }
304}
305
306/// HPKE sender context. Callers may use `seal()` to encrypt messages for the recipient.
307pub struct SenderContext(scoped::EvpHpkeCtx);
308
309impl SenderContext {
310    /// Performs the SetupBaseS HPKE operation and returns a sender context
311    /// plus an encapsulated shared secret for `recipient_pub_key`.
312    ///
313    /// Returns `None` if `recipient_pub_key` is invalid.
314    ///
315    /// On success, callers may use `seal()` to encrypt messages for the recipient.
316    pub fn new(params: &Params, recipient_pub_key: &[u8], info: &[u8]) -> Option<(Self, Vec<u8>)> {
317        let mut ctx = scoped::EvpHpkeCtx::new();
318        unsafe {
319            with_output_vec_fallible(MAX_ENCAPSULATED_KEY_LEN, |enc_key_buf| {
320                let mut enc_key_len = 0usize;
321                // Safety: EVP_HPKE_CTX_setup_sender
322                // - is called with context created from EVP_HPKE_CTX_new,
323                // - is called with valid buffers with corresponding pointer and length, and
324                // - returns 0 on error.
325                let ret = bssl_sys::EVP_HPKE_CTX_setup_sender(
326                    ctx.as_mut_ffi_ptr(),
327                    enc_key_buf,
328                    &mut enc_key_len,
329                    MAX_ENCAPSULATED_KEY_LEN,
330                    params.kem,
331                    params.kdf,
332                    params.aead,
333                    recipient_pub_key.as_ffi_ptr(),
334                    recipient_pub_key.len(),
335                    info.as_ffi_ptr(),
336                    info.len(),
337                );
338                if ret == 1 {
339                    Some(enc_key_len)
340                } else {
341                    None
342                }
343            })
344        }
345        .map(|enc_key| (Self(ctx), enc_key))
346    }
347
348    /// Seal encrypts `plaintext`, and authenticates `aad`, returning the resulting ciphertext.
349    ///
350    /// Note that HPKE encryption is stateful and ordered. The sender's first call to `seal()` must
351    /// correspond to the recipient's first call to `open()`, etc.
352    ///
353    /// This function panics if adding the `plaintext` length and
354    /// `bssl_sys::EVP_HPKE_CTX_max_overhead` overflows.
355    pub fn seal(&mut self, plaintext: &[u8], aad: &[u8]) -> Vec<u8> {
356        // Safety: EVP_HPKE_CTX_max_overhead panics if ctx is not set up as a sender.
357        #[allow(clippy::expect_used)]
358        let max_out_len = plaintext
359            .len()
360            .checked_add(unsafe { bssl_sys::EVP_HPKE_CTX_max_overhead(self.0.as_ffi_ptr()) })
361            .expect("Maximum output length calculation overflow");
362        unsafe {
363            with_output_vec(max_out_len, |out_buf| {
364                let mut out_len = 0usize;
365                // Safety: EVP_HPKE_CTX_seal
366                // - is called with context created from EVP_HPKE_CTX_new and
367                // - is called with valid buffers with corresponding pointer and length.
368                let result = bssl_sys::EVP_HPKE_CTX_seal(
369                    self.0.as_mut_ffi_ptr(),
370                    out_buf,
371                    &mut out_len,
372                    max_out_len,
373                    plaintext.as_ffi_ptr(),
374                    plaintext.len(),
375                    aad.as_ffi_ptr(),
376                    aad.len(),
377                );
378                assert_eq!(result, 1);
379                out_len
380            })
381        }
382    }
383
384    /// Exports a secret of length `out_len` from the HPKE context using `context` as the context
385    /// string.
386    pub fn export(&mut self, context: &[u8], out_len: usize) -> Vec<u8> {
387        unsafe {
388            with_output_vec(out_len, |out_buf| {
389                // Safety: EVP_HPKE_CTX_export
390                // - is called with context created from EVP_HPKE_CTX_new,
391                // - is called with valid buffers with corresponding pointer and length, and
392                // - returns 0 on error, which only occurs when OOM.
393                let ret = bssl_sys::EVP_HPKE_CTX_export(
394                    self.0.as_mut_ffi_ptr(),
395                    out_buf,
396                    out_len,
397                    context.as_ffi_ptr(),
398                    context.len(),
399                );
400                assert_eq!(ret, 1);
401                out_len
402            })
403        }
404    }
405}
406
407/// HPKE recipient context. Callers may use `open()` to decrypt messages from the sender.
408pub struct RecipientContext(scoped::EvpHpkeCtx);
409
410impl RecipientContext {
411    /// New implements the SetupBaseR HPKE operation, which decapsulates the shared secret in
412    /// `encapsulated_key` with `recipient_priv_key` and sets up a recipient context. These are
413    /// stored and returned in the newly created RecipientContext.
414    ///
415    /// Note that `encapsulated_key` may be invalid, in which case this function will return an
416    /// error.
417    ///
418    /// On success, callers may use `open()` to decrypt messages from the sender.
419    pub fn new(
420        params: &Params,
421        recipient_priv_key: &[u8],
422        encapsulated_key: &[u8],
423        info: &[u8],
424    ) -> Option<Self> {
425        let mut hpke_key = scoped::EvpHpkeKey::new();
426
427        // Safety: EVP_HPKE_KEY_init returns 0 on error.
428        let result = unsafe {
429            bssl_sys::EVP_HPKE_KEY_init(
430                hpke_key.as_mut_ffi_ptr(),
431                params.kem,
432                recipient_priv_key.as_ffi_ptr(),
433                recipient_priv_key.len(),
434            )
435        };
436        if result != 1 {
437            return None;
438        }
439
440        let mut ctx = scoped::EvpHpkeCtx::new();
441
442        // Safety: EVP_HPKE_CTX_setup_recipient
443        // - is called with context created from EVP_HPKE_CTX_new,
444        // - is called with HPKE key created from EVP_HPKE_KEY_init,
445        // - is called with valid buffers with corresponding pointer and length, and
446        // - returns 0 on error.
447        let result = unsafe {
448            bssl_sys::EVP_HPKE_CTX_setup_recipient(
449                ctx.as_mut_ffi_ptr(),
450                hpke_key.as_ffi_ptr(),
451                params.kdf,
452                params.aead,
453                encapsulated_key.as_ffi_ptr(),
454                encapsulated_key.len(),
455                info.as_ffi_ptr(),
456                info.len(),
457            )
458        };
459        if result == 1 {
460            Some(Self(ctx))
461        } else {
462            None
463        }
464    }
465
466    /// Open authenticates `aad` and decrypts `ciphertext`. It returns an error on failure.
467    ///
468    /// Note that HPKE encryption is stateful and ordered. The sender's first call to `seal()` must
469    /// correspond to the recipient's first call to `open()`, etc.
470    pub fn open(&mut self, ciphertext: &[u8], aad: &[u8]) -> Option<Vec<u8>> {
471        let max_out_len = ciphertext.len();
472        unsafe {
473            with_output_vec_fallible(max_out_len, |out_buf| {
474                let mut out_len = 0usize;
475                // Safety: EVP_HPKE_CTX_open
476                // - is called with context created from EVP_HPKE_CTX_new and
477                // - is called with valid buffers with corresponding pointer and length.
478                let result = bssl_sys::EVP_HPKE_CTX_open(
479                    self.0.as_mut_ffi_ptr(),
480                    out_buf,
481                    &mut out_len,
482                    max_out_len,
483                    ciphertext.as_ffi_ptr(),
484                    ciphertext.len(),
485                    aad.as_ffi_ptr(),
486                    aad.len(),
487                );
488                if result == 1 {
489                    Some(out_len)
490                } else {
491                    None
492                }
493            })
494        }
495    }
496
497    /// Exports a secret of length `out_len` from the HPKE context using `context` as the context
498    /// string.
499    pub fn export(&mut self, context: &[u8], out_len: usize) -> Vec<u8> {
500        unsafe {
501            with_output_vec(out_len, |out_buf| {
502                // Safety: EVP_HPKE_CTX_export
503                // - is called with context created from EVP_HPKE_CTX_new,
504                // - is called with valid buffers with corresponding pointer and length, and
505                // - returns 0 on error, which only occurs when OOM.
506                let ret = bssl_sys::EVP_HPKE_CTX_export(
507                    self.0.as_mut_ffi_ptr(),
508                    out_buf,
509                    out_len,
510                    context.as_ffi_ptr(),
511                    context.len(),
512                );
513                assert_eq!(ret, 1);
514                out_len
515            })
516        }
517    }
518}
519
520#[cfg(test)]
521mod test {
522    use super::*;
523    use crate::test_helpers::{decode_hex, decode_hex_into_vec};
524
525    struct TestVector {
526        kem_id: u16,
527        kdf_id: u16,
528        aead_id: u16,
529        info: [u8; 20],
530        seed_for_testing: [u8; 32],   // skEm
531        recipient_pub_key: Vec<u8>,   // pkRm
532        recipient_priv_key: [u8; 32], // skRm
533        encapsulated_key: Vec<u8>,    // enc
534        plaintext: [u8; 29],          // pt
535        associated_data: [u8; 7],     // aad
536        ciphertext: [u8; 45],         // ct
537        exporter_context: [u8; 11],
538        exported_value: [u8; 32],
539    }
540
541    // https://www.rfc-editor.org/rfc/rfc9180.html#appendix-A.1
542    fn x25519_hkdf_sha256_hkdf_sha256_aes_128_gcm() -> TestVector {
543        TestVector {
544            kem_id: 32,
545            kdf_id: 1,
546            aead_id: 1,
547            info: decode_hex("4f6465206f6e2061204772656369616e2055726e"),
548            seed_for_testing: decode_hex("52c4a758a802cd8b936eceea314432798d5baf2d7e9235dc084ab1b9cfa2f736"),
549            recipient_pub_key: decode_hex_into_vec("3948cfe0ad1ddb695d780e59077195da6c56506b027329794ab02bca80815c4d"),
550            recipient_priv_key: decode_hex("4612c550263fc8ad58375df3f557aac531d26850903e55a9f23f21d8534e8ac8"),
551            encapsulated_key: decode_hex_into_vec("37fda3567bdbd628e88668c3c8d7e97d1d1253b6d4ea6d44c150f741f1bf4431"),
552            plaintext: decode_hex("4265617574792069732074727574682c20747275746820626561757479"),
553            associated_data: decode_hex("436f756e742d30"),
554            ciphertext: decode_hex("f938558b5d72f1a23810b4be2ab4f84331acc02fc97babc53a52ae8218a355a96d8770ac83d07bea87e13c512a"),
555            exporter_context: decode_hex("54657374436f6e74657874"),
556            exported_value: decode_hex("e9e43065102c3836401bed8c3c3c75ae46be1639869391d62c61f1ec7af54931"),
557        }
558    }
559
560    // https://www.rfc-editor.org/rfc/rfc9180.html#appendix-A.2
561    fn x25519_hkdf_sha256_hkdf_sha256_chacha20_poly1305() -> TestVector {
562        TestVector {
563            kem_id: 32,
564            kdf_id: 1,
565            aead_id: 3,
566            info: decode_hex("4f6465206f6e2061204772656369616e2055726e"),
567            seed_for_testing: decode_hex("f4ec9b33b792c372c1d2c2063507b684ef925b8c75a42dbcbf57d63ccd381600"),
568            recipient_pub_key: decode_hex_into_vec("4310ee97d88cc1f088a5576c77ab0cf5c3ac797f3d95139c6c84b5429c59662a"),
569            recipient_priv_key: decode_hex("8057991eef8f1f1af18f4a9491d16a1ce333f695d4db8e38da75975c4478e0fb"),
570            encapsulated_key: decode_hex_into_vec("1afa08d3dec047a643885163f1180476fa7ddb54c6a8029ea33f95796bf2ac4a"),
571            plaintext: decode_hex("4265617574792069732074727574682c20747275746820626561757479"),
572            associated_data: decode_hex("436f756e742d30"),
573            ciphertext: decode_hex("1c5250d8034ec2b784ba2cfd69dbdb8af406cfe3ff938e131f0def8c8b60b4db21993c62ce81883d2dd1b51a28"),
574            exporter_context: decode_hex("54657374436f6e74657874"),
575            exported_value: decode_hex("5acb09211139c43b3090489a9da433e8a30ee7188ba8b0a9a1ccf0c229283e53"),
576        }
577    }
578
579    // https://www.rfc-editor.org/rfc/rfc9180.html#appendix-A.3
580    fn p256_hkdf_sha256_hkdf_sha256_aes_128_gcm() -> TestVector {
581        TestVector {
582            kem_id: 16,
583            kdf_id: 1,
584            aead_id: 1,
585            info: decode_hex("4f6465206f6e2061204772656369616e2055726e"),
586            seed_for_testing: decode_hex("4270e54ffd08d79d5928020af4686d8f6b7d35dbe470265f1f5aa22816ce860e"),
587            recipient_pub_key: decode_hex_into_vec("04fe8c19ce0905191ebc298a9245792531f26f0cece2460639e8bc39cb7f706a826a779b4cf969b8a0e539c7f62fb3d30ad6aa8f80e30f1d128aafd68a2ce72ea0"),
588            recipient_priv_key: decode_hex("f3ce7fdae57e1a310d87f1ebbde6f328be0a99cdbcadf4d6589cf29de4b8ffd2"),
589            encapsulated_key: decode_hex_into_vec("04a92719c6195d5085104f469a8b9814d5838ff72b60501e2c4466e5e67b325ac98536d7b61a1af4b78e5b7f951c0900be863c403ce65c9bfcb9382657222d18c4"),
590            plaintext: decode_hex("4265617574792069732074727574682c20747275746820626561757479"),
591            associated_data: decode_hex("436f756e742d30"),
592            ciphertext: decode_hex("5ad590bb8baa577f8619db35a36311226a896e7342a6d836d8b7bcd2f20b6c7f9076ac232e3ab2523f39513434"),
593            exporter_context: decode_hex("54657374436f6e74657874"),
594            exported_value: decode_hex("d8f1ea7942adbba7412c6d431c62d01371ea476b823eb697e1f6e6cae1dab85a"),
595        }
596    }
597
598    #[test]
599    fn all_algorithms() {
600        let kems = vec![
601            Kem::X25519HkdfSha256,
602            Kem::P256HkdfSha256,
603            Kem::XWing,
604            Kem::MlKem768,
605            Kem::MlKem1024,
606        ];
607        let kdfs = vec![Kdf::HkdfSha256];
608        let aeads = vec![Aead::Aes128Gcm, Aead::Aes256Gcm, Aead::Chacha20Poly1305];
609        let plaintext: &[u8] = b"plaintext";
610        let aad: &[u8] = b"aad";
611        let info: &[u8] = b"info";
612
613        for kem in &kems {
614            let (pub_key, priv_key) = kem.generate_keypair();
615            for kdf in &kdfs {
616                for aead in &aeads {
617                    let params =
618                        Params::new_from_rfc_ids(*kem as u16, *kdf as u16, *aead as u16).unwrap();
619
620                    let (mut send_ctx, encapsulated_key) =
621                        SenderContext::new(&params, &pub_key, info).unwrap();
622                    let mut recv_ctx =
623                        RecipientContext::new(&params, &priv_key, &encapsulated_key, info).unwrap();
624                    assert_eq!(
625                        plaintext,
626                        recv_ctx
627                            .open(send_ctx.seal(plaintext, aad).as_ref(), aad)
628                            .unwrap()
629                    );
630                    assert_eq!(
631                        plaintext,
632                        recv_ctx
633                            .open(send_ctx.seal(plaintext, aad).as_ref(), aad)
634                            .unwrap()
635                    );
636                    assert!(recv_ctx.open(b"nonsense", aad).is_none());
637                }
638            }
639        }
640    }
641
642    #[test]
643    fn kem_public_from_private() {
644        let kems = vec![
645            Kem::X25519HkdfSha256,
646            Kem::P256HkdfSha256,
647            Kem::XWing,
648            Kem::MlKem768,
649            Kem::MlKem1024,
650        ];
651        for kem in &kems {
652            let (pub_key, priv_key) = kem.generate_keypair();
653            assert_eq!(kem.public_from_private(&priv_key), Some(pub_key));
654
655            assert_eq!(kem.public_from_private(b"invalid"), None);
656        }
657    }
658
659    fn new_sender_context_for_testing(
660        params: &Params,
661        recipient_pub_key: &[u8],
662        info: &[u8],
663        seed_for_testing: &[u8],
664    ) -> (SenderContext, Vec<u8>) {
665        let mut ctx = scoped::EvpHpkeCtx::new();
666
667        let encapsulated_key = unsafe {
668            with_output_vec_fallible(MAX_ENCAPSULATED_KEY_LEN, |enc_key_buf| {
669                let mut enc_key_len = 0usize;
670                // Safety: EVP_HPKE_CTX_setup_sender_with_seed_for_testing
671                // - is called with context created from EVP_HPKE_CTX_new,
672                // - is called with valid buffers with corresponding pointer and length, and
673                // - returns 0 on error.
674                let result = bssl_sys::EVP_HPKE_CTX_setup_sender_with_seed_for_testing(
675                    ctx.as_mut_ffi_ptr(),
676                    enc_key_buf,
677                    &mut enc_key_len,
678                    MAX_ENCAPSULATED_KEY_LEN,
679                    params.kem,
680                    params.kdf,
681                    params.aead,
682                    recipient_pub_key.as_ffi_ptr(),
683                    recipient_pub_key.len(),
684                    info.as_ffi_ptr(),
685                    info.len(),
686                    seed_for_testing.as_ffi_ptr(),
687                    seed_for_testing.len(),
688                );
689                if result == 1 {
690                    Some(enc_key_len)
691                } else {
692                    None
693                }
694            })
695        }
696        .unwrap();
697        (SenderContext(ctx), encapsulated_key)
698    }
699
700    #[test]
701    fn seal_with_vector() {
702        for test in vec![
703            x25519_hkdf_sha256_hkdf_sha256_aes_128_gcm(),
704            x25519_hkdf_sha256_hkdf_sha256_chacha20_poly1305(),
705            p256_hkdf_sha256_hkdf_sha256_aes_128_gcm(),
706        ] {
707            let params = Params::new_from_rfc_ids(test.kem_id, test.kdf_id, test.aead_id).unwrap();
708
709            let (mut ctx, encapsulated_key) = new_sender_context_for_testing(
710                &params,
711                &test.recipient_pub_key,
712                &test.info,
713                &test.seed_for_testing,
714            );
715
716            assert_eq!(encapsulated_key, test.encapsulated_key.to_vec());
717
718            let ciphertext = ctx.seal(&test.plaintext, &test.associated_data);
719            assert_eq!(&ciphertext, test.ciphertext.as_ref());
720        }
721    }
722
723    #[test]
724    fn open_with_vector() {
725        for test in vec![
726            x25519_hkdf_sha256_hkdf_sha256_aes_128_gcm(),
727            x25519_hkdf_sha256_hkdf_sha256_chacha20_poly1305(),
728            p256_hkdf_sha256_hkdf_sha256_aes_128_gcm(),
729        ] {
730            let params = Params::new_from_rfc_ids(test.kem_id, test.kdf_id, test.aead_id).unwrap();
731
732            let mut ctx = RecipientContext::new(
733                &params,
734                &test.recipient_priv_key,
735                &test.encapsulated_key,
736                &test.info,
737            )
738            .unwrap();
739
740            let plaintext = ctx.open(&test.ciphertext, &test.associated_data).unwrap();
741            assert_eq!(&plaintext, test.plaintext.as_ref());
742        }
743    }
744
745    #[test]
746    fn export_with_vector() {
747        for test in vec![
748            x25519_hkdf_sha256_hkdf_sha256_aes_128_gcm(),
749            x25519_hkdf_sha256_hkdf_sha256_chacha20_poly1305(),
750            p256_hkdf_sha256_hkdf_sha256_aes_128_gcm(),
751        ] {
752            let params = Params::new_from_rfc_ids(test.kem_id, test.kdf_id, test.aead_id).unwrap();
753
754            let (mut sender_ctx, _encapsulated_key) = new_sender_context_for_testing(
755                &params,
756                &test.recipient_pub_key,
757                &test.info,
758                &test.seed_for_testing,
759            );
760            assert_eq!(
761                test.exported_value.as_ref(),
762                sender_ctx.export(&test.exporter_context, test.exported_value.len())
763            );
764
765            let mut recipient_ctx = RecipientContext::new(
766                &params,
767                &test.recipient_priv_key,
768                &test.encapsulated_key,
769                &test.info,
770            )
771            .unwrap();
772            assert_eq!(
773                test.exported_value.as_ref(),
774                recipient_ctx.export(&test.exporter_context, test.exported_value.len())
775            );
776        }
777    }
778
779    #[test]
780    fn disallowed_params_fail() {
781        let vec: TestVector = x25519_hkdf_sha256_hkdf_sha256_aes_128_gcm();
782
783        assert!(Params::new_from_rfc_ids(0, vec.kdf_id, vec.aead_id).is_none());
784        assert!(Params::new_from_rfc_ids(vec.kem_id, 0, vec.aead_id).is_none());
785        assert!(Params::new_from_rfc_ids(vec.kem_id, vec.kdf_id, 0).is_none());
786    }
787
788    #[test]
789    fn bad_recipient_pub_key_fails() {
790        let vec: TestVector = x25519_hkdf_sha256_hkdf_sha256_aes_128_gcm();
791        let params = Params::new_from_rfc_ids(vec.kem_id, vec.kdf_id, vec.aead_id).unwrap();
792
793        assert!(SenderContext::new(&params, b"", &vec.info).is_none());
794    }
795
796    #[test]
797    fn bad_recipient_priv_key_fails() {
798        let vec: TestVector = x25519_hkdf_sha256_hkdf_sha256_aes_128_gcm();
799        let params = Params::new_from_rfc_ids(vec.kem_id, vec.kdf_id, vec.aead_id).unwrap();
800
801        assert!(RecipientContext::new(&params, b"", &vec.encapsulated_key, &vec.info).is_none());
802    }
803}