Add HPKE secret export and implement Send for EvpHpkeCtx.

Change-Id: I929b31f996c8b67b77286fe9f8eb1af73d2bbc72
Reviewed-on: https://boringssl-review.googlesource.com/c/boringssl/+/68307
Commit-Queue: Adam Langley <agl@google.com>
Reviewed-by: Adam Langley <agl@google.com>
diff --git a/rust/bssl-crypto/src/hpke.rs b/rust/bssl-crypto/src/hpke.rs
index 2725b3f..e3a61bc 100644
--- a/rust/bssl-crypto/src/hpke.rs
+++ b/rust/bssl-crypto/src/hpke.rs
@@ -65,6 +65,12 @@
 //!
 //! let received_plaintext2 = recipient_ctx.open(&msg2, aad);
 //! assert!(received_plaintext2.is_none());
+//!
+//! // There is also an interface for exporting secrets from both sender
+//! // and recipient contexts.
+//! let sender_export = sender_ctx.export(b"ctx", 32);
+//! let recipient_export = recipient_ctx.export(b"ctx", 32);
+//! assert_eq!(sender_export, recipient_export);
 //! ```
 
 use crate::{scoped, with_output_vec, with_output_vec_fallible, FfiSlice};
@@ -294,6 +300,28 @@
             })
         }
     }
+
+    /// Exports a secret of length `out_len` from the HPKE context using `context` as the context
+    /// string.
+    pub fn export(&mut self, context: &[u8], out_len: usize) -> Vec<u8> {
+        unsafe {
+            with_output_vec(out_len, |out_buf| {
+                // Safety: EVP_HPKE_CTX_export
+                // - is called with context created from EVP_HPKE_CTX_new,
+                // - is called with valid buffers with corresponding pointer and length, and
+                // - returns 0 on error, which only occurs when OOM.
+                let ret = bssl_sys::EVP_HPKE_CTX_export(
+                    self.0.as_mut_ffi_ptr(),
+                    out_buf,
+                    out_len,
+                    context.as_ffi_ptr(),
+                    context.len(),
+                );
+                assert_eq!(ret, 1);
+                out_len
+            })
+        }
+    }
 }
 
 /// HPKE recipient context. Callers may use `open()` to decrypt messages from the sender.
@@ -385,6 +413,28 @@
             })
         }
     }
+
+    /// Exports a secret of length `out_len` from the HPKE context using `context` as the context
+    /// string.
+    pub fn export(&mut self, context: &[u8], out_len: usize) -> Vec<u8> {
+        unsafe {
+            with_output_vec(out_len, |out_buf| {
+                // Safety: EVP_HPKE_CTX_export
+                // - is called with context created from EVP_HPKE_CTX_new,
+                // - is called with valid buffers with corresponding pointer and length, and
+                // - returns 0 on error, which only occurs when OOM.
+                let ret = bssl_sys::EVP_HPKE_CTX_export(
+                    self.0.as_mut_ffi_ptr(),
+                    out_buf,
+                    out_len,
+                    context.as_ffi_ptr(),
+                    context.len(),
+                );
+                assert_eq!(ret, 1);
+                out_len
+            })
+        }
+    }
 }
 
 #[cfg(test)]
@@ -404,6 +454,8 @@
         plaintext: [u8; 29],          // pt
         associated_data: [u8; 7],     // aad
         ciphertext: [u8; 45],         // ct
+        exporter_context: [u8; 11],
+        exported_value: [u8; 32],
     }
 
     // https://www.rfc-editor.org/rfc/rfc9180.html#appendix-A.1
@@ -420,6 +472,8 @@
             plaintext: decode_hex("4265617574792069732074727574682c20747275746820626561757479"),
             associated_data: decode_hex("436f756e742d30"),
             ciphertext: decode_hex("f938558b5d72f1a23810b4be2ab4f84331acc02fc97babc53a52ae8218a355a96d8770ac83d07bea87e13c512a"),
+            exporter_context: decode_hex("54657374436f6e74657874"),
+            exported_value: decode_hex("e9e43065102c3836401bed8c3c3c75ae46be1639869391d62c61f1ec7af54931"),
         }
     }
 
@@ -437,6 +491,8 @@
             plaintext: decode_hex("4265617574792069732074727574682c20747275746820626561757479"),
             associated_data: decode_hex("436f756e742d30"),
             ciphertext: decode_hex("1c5250d8034ec2b784ba2cfd69dbdb8af406cfe3ff938e131f0def8c8b60b4db21993c62ce81883d2dd1b51a28"),
+            exporter_context: decode_hex("54657374436f6e74657874"),
+            exported_value: decode_hex("5acb09211139c43b3090489a9da433e8a30ee7188ba8b0a9a1ccf0c229283e53"),
         }
     }
 
@@ -563,6 +619,38 @@
     }
 
     #[test]
+    fn export_with_vector() {
+        for test in vec![
+            x25519_hkdf_sha256_hkdf_sha256_aes_128_gcm(),
+            x25519_hkdf_sha256_hkdf_sha256_chacha20_poly1305(),
+        ] {
+            let params = Params::new_from_rfc_ids(test.kem_id, test.kdf_id, test.aead_id).unwrap();
+
+            let (mut sender_ctx, _encapsulated_key) = new_sender_context_for_testing(
+                &params,
+                &test.recipient_pub_key,
+                &test.info,
+                &test.seed_for_testing,
+            );
+            assert_eq!(
+                test.exported_value.as_ref(),
+                sender_ctx.export(&test.exporter_context, test.exported_value.len())
+            );
+
+            let mut recipient_ctx = RecipientContext::new(
+                &params,
+                &test.recipient_priv_key,
+                &test.encapsulated_key,
+                &test.info,
+            ).unwrap();
+            assert_eq!(
+                test.exported_value.as_ref(),
+                recipient_ctx.export(&test.exporter_context, test.exported_value.len())
+            );
+        }
+    }
+
+    #[test]
     fn disallowed_params_fail() {
         let vec: TestVector = x25519_hkdf_sha256_hkdf_sha256_aes_128_gcm();
 
diff --git a/rust/bssl-crypto/src/scoped.rs b/rust/bssl-crypto/src/scoped.rs
index 8c6b21b..67bf301 100644
--- a/rust/bssl-crypto/src/scoped.rs
+++ b/rust/bssl-crypto/src/scoped.rs
@@ -69,6 +69,9 @@
 
 /// A scoped `EVP_HPKE_CTX`.
 pub struct EvpHpkeCtx(*mut bssl_sys::EVP_HPKE_CTX);
+// bssl_sys::EVP_HPKE_CTX is heap-allocated and safe to transfer
+// between threads.
+unsafe impl Send for EvpHpkeCtx {}
 
 impl EvpHpkeCtx {
     pub fn new() -> Self {