rust: add wrappings for ML-KEM and ML-DSA.

Change-Id: Iff8547da5905e72648c4bf36f83fd2b5fc82ec30
Reviewed-on: https://boringssl-review.googlesource.com/c/boringssl/+/71469
Reviewed-by: Bob Beck <bbe@google.com>
Commit-Queue: Adam Langley <agl@google.com>
diff --git a/gen/sources.bzl b/gen/sources.bzl
index 1ca1a8b..5bd088e 100644
--- a/gen/sources.bzl
+++ b/gen/sources.bzl
@@ -2673,6 +2673,8 @@
     "rust/bssl-crypto/src/lib.rs",
     "rust/bssl-crypto/src/macros.rs",
     "rust/bssl-crypto/src/mem.rs",
+    "rust/bssl-crypto/src/mldsa.rs",
+    "rust/bssl-crypto/src/mlkem.rs",
     "rust/bssl-crypto/src/rand.rs",
     "rust/bssl-crypto/src/rsa.rs",
     "rust/bssl-crypto/src/scoped.rs",
diff --git a/gen/sources.cmake b/gen/sources.cmake
index 94b6195..594b417 100644
--- a/gen/sources.cmake
+++ b/gen/sources.cmake
@@ -2719,6 +2719,8 @@
   rust/bssl-crypto/src/lib.rs
   rust/bssl-crypto/src/macros.rs
   rust/bssl-crypto/src/mem.rs
+  rust/bssl-crypto/src/mldsa.rs
+  rust/bssl-crypto/src/mlkem.rs
   rust/bssl-crypto/src/rand.rs
   rust/bssl-crypto/src/rsa.rs
   rust/bssl-crypto/src/scoped.rs
diff --git a/gen/sources.gni b/gen/sources.gni
index 131de07..10a0c01 100644
--- a/gen/sources.gni
+++ b/gen/sources.gni
@@ -2673,6 +2673,8 @@
   "rust/bssl-crypto/src/lib.rs",
   "rust/bssl-crypto/src/macros.rs",
   "rust/bssl-crypto/src/mem.rs",
+  "rust/bssl-crypto/src/mldsa.rs",
+  "rust/bssl-crypto/src/mlkem.rs",
   "rust/bssl-crypto/src/rand.rs",
   "rust/bssl-crypto/src/rsa.rs",
   "rust/bssl-crypto/src/scoped.rs",
diff --git a/gen/sources.json b/gen/sources.json
index f5879cd..7f57e6e 100644
--- a/gen/sources.json
+++ b/gen/sources.json
@@ -2655,6 +2655,8 @@
       "rust/bssl-crypto/src/lib.rs",
       "rust/bssl-crypto/src/macros.rs",
       "rust/bssl-crypto/src/mem.rs",
+      "rust/bssl-crypto/src/mldsa.rs",
+      "rust/bssl-crypto/src/mlkem.rs",
       "rust/bssl-crypto/src/rand.rs",
       "rust/bssl-crypto/src/rsa.rs",
       "rust/bssl-crypto/src/scoped.rs",
diff --git a/rust/bssl-crypto/src/lib.rs b/rust/bssl-crypto/src/lib.rs
index 3062152..070895e 100644
--- a/rust/bssl-crypto/src/lib.rs
+++ b/rust/bssl-crypto/src/lib.rs
@@ -28,7 +28,7 @@
 extern crate alloc;
 extern crate core;
 
-use alloc::vec::Vec;
+use alloc::{boxed::Box, vec::Vec};
 use core::ffi::c_void;
 
 #[macro_use]
@@ -48,6 +48,8 @@
 pub mod hkdf;
 pub mod hmac;
 pub mod hpke;
+pub mod mldsa;
+pub mod mlkem;
 pub mod rsa;
 pub mod x25519;
 
@@ -240,6 +242,37 @@
     }
 }
 
+/// Returns a boxed BoringSSL structure that is initialized by some function.
+/// Requires that the given function completely initializes the value.
+///
+/// Safety: the argument must fully initialize the pointed-to `T`.
+unsafe fn initialized_boxed_struct<T, F>(init: F) -> Box<T>
+where
+    F: FnOnce(*mut T),
+{
+    let mut out_uninit = Box::new(core::mem::MaybeUninit::<T>::uninit());
+    init(out_uninit.as_mut_ptr());
+    unsafe { out_uninit.assume_init() }
+}
+
+/// Returns a boxed BoringSSL structure that is initialized by some function.
+/// Requires that the given function completely initializes the value or else
+/// returns false.
+///
+/// Safety: the argument must fully initialize the pointed-to `T` if it returns
+/// true. If it returns false then there are no safety requirements.
+unsafe fn initialized_boxed_struct_fallible<T, F>(init: F) -> Option<Box<T>>
+where
+    F: FnOnce(*mut T) -> bool,
+{
+    let mut out_uninit = Box::new(core::mem::MaybeUninit::<T>::uninit());
+    if init(out_uninit.as_mut_ptr()) {
+        Some(unsafe { out_uninit.assume_init() })
+    } else {
+        None
+    }
+}
+
 /// Wrap a closure that initializes an output buffer and return that buffer as
 /// an array. Requires that the closure fully initialize the given buffer.
 ///
@@ -364,6 +397,13 @@
     }
 }
 
+fn as_cbs(buf: &[u8]) -> bssl_sys::CBS {
+    bssl_sys::CBS {
+        data: buf.as_ffi_ptr(),
+        len: buf.len(),
+    }
+}
+
 /// Calls `parse_func` with a `CBS` structure pointing at `data`.
 /// If that returns a null pointer then it returns [None].
 /// Otherwise, if there's still data left in CBS, it calls `free_func` on the
@@ -414,6 +454,31 @@
     unsafe { Buffer::new(ptr, len) }
 }
 
+/// Calls `func` with a `CBB` pointer that has been initialized to a vector
+/// of `len` bytes. That function must write exactly `len` bytes to the
+/// `CBB`. Those bytes are then returned as a vector.
+#[allow(clippy::unwrap_used)]
+fn cbb_to_vec<F: FnOnce(*mut bssl_sys::CBB)>(len: usize, func: F) -> Vec<u8> {
+    let mut boxed = Box::new_uninit_slice(len);
+    // Safety: type checking ensures that `cbb` is the correct size.
+    let mut cbb = unsafe {
+        initialized_struct_fallible(|cbb| {
+            bssl_sys::CBB_init_fixed(cbb, boxed.as_mut_ptr() as *mut u8, len) == 1
+        })
+    }
+    // `CBB_init` only fails if out of memory, which isn't something that this
+    // crate handles.
+    .unwrap();
+
+    func(&mut cbb);
+
+    unsafe {
+        assert_eq!(bssl_sys::CBB_len(&cbb), len);
+        // `boxed` has been fully written, as checked on the previous line.
+        boxed.assume_init().into()
+    }
+}
+
 /// Used to prevent external implementations of internal traits.
 mod sealed {
     pub struct Sealed;
diff --git a/rust/bssl-crypto/src/mldsa.rs b/rust/bssl-crypto/src/mldsa.rs
new file mode 100644
index 0000000..a1433d2
--- /dev/null
+++ b/rust/bssl-crypto/src/mldsa.rs
@@ -0,0 +1,278 @@
+/* Copyright (c) 2024, Google Inc.
+ *
+ * Permission to use, copy, modify, and/or distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY
+ * SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION
+ * OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN
+ * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */
+
+//! ML-DSA
+//!
+//! ML-DSA is a post-quantum key signature scheme, specified in
+//! [FIPS 204](https://csrc.nist.gov/pubs/fips/204/final).
+//!
+//! ```
+//! use bssl_crypto::mldsa;
+//!
+//! // Generate a key pair.
+//! let (serialized_public_key, private_key, _private_seed) = mldsa::PrivateKey65::generate();
+//!
+//! // Send `serialized_public_key` to the verifier. The verifier parses it:
+//! let public_key = mldsa::PublicKey65::parse(&serialized_public_key).unwrap();
+//!
+//! // The signer signs a message.
+//! let message = &[0u8, 1, 2, 3];
+//! let signature = private_key.sign(message);
+//!
+//! // Send `message` and `signature` to the verifier. The verifier checks the signature.
+//! assert!(public_key.verify(message, &signature).is_ok());
+//! ```
+
+use crate::{
+    as_cbs, cbb_to_vec, initialized_boxed_struct, initialized_boxed_struct_fallible,
+    with_output_vec, with_output_vec_fallible, FfiSlice, InvalidSignatureError,
+};
+use alloc::{boxed::Box, vec::Vec};
+use core::mem::MaybeUninit;
+
+/// An ML-DSA-65 private key.
+pub struct PrivateKey65(Box<bssl_sys::MLDSA65_private_key>);
+
+/// An ML-DSA-65 public key.
+pub struct PublicKey65(Box<bssl_sys::MLDSA65_public_key>);
+
+/// The number of bytes in an encoded ML-DSA-65 public key.
+pub const PUBLIC_KEY_BYTES_65: usize = bssl_sys::MLDSA65_PUBLIC_KEY_BYTES as usize;
+
+/// The number of bytes in an encoded ML-DSA-65 signature.
+pub const SIGNATURE_BYTES_65: usize = bssl_sys::MLDSA65_SIGNATURE_BYTES as usize;
+
+/// The number of bytes in an ML-DSA seed value.
+pub const SEED_BYTES: usize = bssl_sys::MLDSA_SEED_BYTES as usize;
+
+impl PrivateKey65 {
+    /// Generates a random public/private key pair returning a serialized public
+    /// key, a private key, and a private seed value that can be used to
+    /// regenerate the same private key in the future.
+    pub fn generate() -> (Vec<u8>, Self, [u8; SEED_BYTES]) {
+        let mut public_key_bytes = Box::new_uninit_slice(PUBLIC_KEY_BYTES_65);
+        let mut seed = MaybeUninit::<[u8; SEED_BYTES]>::uninit();
+
+        let private_key = unsafe {
+            // Safety: the buffers are the sizes that the FFI code requires.
+            initialized_boxed_struct(|priv_key| {
+                let ok = bssl_sys::MLDSA65_generate_key(
+                    public_key_bytes.as_mut_ptr() as *mut u8,
+                    seed.as_mut_ptr() as *mut u8,
+                    priv_key,
+                );
+                // This function can only fail if out of memory, which is not a
+                // case that this crate handles.
+                assert_eq!(ok, 1);
+            })
+        };
+
+        unsafe {
+            (
+                // Safety: the buffers are always fully initialized by
+                // `MLDSA65_generate_key`.
+                public_key_bytes.assume_init().into(),
+                Self(private_key),
+                seed.assume_init(),
+            )
+        }
+    }
+
+    /// Regenerates a private key from a seed value.
+    pub fn from_seed(seed: &[u8; SEED_BYTES]) -> Self {
+        Self(unsafe {
+            // Safety: `priv_key` is the correct size via the type system and
+            // is always fully written.
+            initialized_boxed_struct(|priv_key| {
+                let ok = bssl_sys::MLDSA65_private_key_from_seed(
+                    priv_key,
+                    seed.as_ffi_ptr(),
+                    seed.len(),
+                );
+                // Since the seed value has the correct length, this function can
+                // never fail.
+                assert_eq!(ok, 1);
+            })
+        })
+    }
+
+    /// Derives the public key corresponding to this private key.
+    pub fn to_public_key(&self) -> PublicKey65 {
+        PublicKey65(unsafe {
+            // Safety: `pub_key` is the correct size via the type system and
+            // is always fully written.
+            initialized_boxed_struct(|pub_key| {
+                bssl_sys::MLDSA65_public_from_private(pub_key, &*self.0);
+            })
+        })
+    }
+
+    /// Signs a message using this private key.
+    pub fn sign(&self, msg: &[u8]) -> Vec<u8> {
+        unsafe {
+            // Safety: `signature` is the correct size via the type system and
+            // is always fully written.
+            with_output_vec(SIGNATURE_BYTES_65, |signature| {
+                let ok = bssl_sys::MLDSA65_sign(
+                    signature,
+                    &*self.0,
+                    msg.as_ffi_ptr(),
+                    msg.len(),
+                    core::ptr::null(),
+                    0,
+                );
+                // This function can only fail if out of memory, which is not a
+                // case that this crate handles.
+                assert_eq!(ok, 1);
+                SIGNATURE_BYTES_65
+            })
+        }
+    }
+
+    /// Signs a message using this private key and the given context.
+    ///
+    /// This function returns None if `context` is longer than 255 bytes.
+    pub fn sign_with_context(&self, msg: &[u8], context: &[u8]) -> Option<Vec<u8>> {
+        unsafe {
+            // Safety: `signature` is the correct size via the type system and
+            // is always fully written.
+            with_output_vec_fallible(SIGNATURE_BYTES_65, |signature| {
+                if bssl_sys::MLDSA65_sign(
+                    signature,
+                    &*self.0,
+                    msg.as_ffi_ptr(),
+                    msg.len(),
+                    context.as_ffi_ptr(),
+                    context.len(),
+                ) == 1
+                {
+                    Some(SIGNATURE_BYTES_65)
+                } else {
+                    None
+                }
+            })
+        }
+    }
+}
+
+impl PublicKey65 {
+    /// Parses a public key from a byte slice.
+    pub fn parse(encoded: &[u8]) -> Option<Self> {
+        let mut cbs = as_cbs(encoded);
+        unsafe {
+            // Safety: `pub_key` is the correct size via the type system and
+            // is fully written if this function returns 1.
+            initialized_boxed_struct_fallible(|pub_key| {
+                bssl_sys::MLDSA65_parse_public_key(pub_key, &mut cbs) == 1 && cbs.len == 0
+            })
+        }
+        .map(Self)
+    }
+
+    /// Return the serialization of this public key.
+    pub fn to_bytes(&self) -> Vec<u8> {
+        unsafe {
+            cbb_to_vec(PUBLIC_KEY_BYTES_65, |buf| {
+                let ok = bssl_sys::MLDSA65_marshal_public_key(buf, &*self.0);
+                // `MLKEM768_marshal_public_key` only fails if it cannot
+                // allocate memory, but `cbb_to_vec` allocates a fixed
+                // amount of memory.
+                assert_eq!(ok, 1);
+            })
+        }
+    }
+
+    /// Verifies a signature for a given message using this public key.
+    pub fn verify(&self, msg: &[u8], signature: &[u8]) -> Result<(), InvalidSignatureError> {
+        unsafe {
+            let ok = bssl_sys::MLDSA65_verify(
+                &*self.0,
+                signature.as_ffi_ptr(),
+                signature.len(),
+                msg.as_ffi_ptr(),
+                msg.len(),
+                core::ptr::null(),
+                0,
+            );
+            if ok == 1 {
+                Ok(())
+            } else {
+                Err(InvalidSignatureError)
+            }
+        }
+    }
+
+    /// Verifies a signature for a given message using this public key and the given context.
+    pub fn verify_with_context(
+        &self,
+        msg: &[u8],
+        signature: &[u8],
+        context: &[u8],
+    ) -> Result<(), InvalidSignatureError> {
+        unsafe {
+            let ok = bssl_sys::MLDSA65_verify(
+                &*self.0,
+                signature.as_ffi_ptr(),
+                signature.len(),
+                msg.as_ffi_ptr(),
+                msg.len(),
+                context.as_ffi_ptr(),
+                context.len(),
+            );
+            if ok == 1 {
+                Ok(())
+            } else {
+                Err(InvalidSignatureError)
+            }
+        }
+    }
+}
+
+#[cfg(test)]
+mod test {
+    use super::*;
+
+    #[test]
+    fn basic() {
+        let (serialized_public_key, private_key, private_seed) = PrivateKey65::generate();
+        let public_key = PublicKey65::parse(&serialized_public_key).unwrap();
+        let message = &[0u8, 1, 2, 3];
+        let signature = private_key.sign(message);
+        let private_key2 = PrivateKey65::from_seed(&private_seed);
+        let mut signature2 = private_key2.sign(message);
+        assert!(public_key.verify(message, &signature).is_ok());
+        assert!(public_key.verify(message, &signature2).is_ok());
+
+        signature2[5] ^= 1;
+        assert!(public_key.verify(message, &signature2).is_err());
+
+        let context = b"context";
+        let signature3 = private_key.sign_with_context(message, context).unwrap();
+        assert!(public_key.verify(message, &signature3).is_err());
+        assert!(public_key
+            .verify_with_context(message, &signature3, context)
+            .is_ok());
+    }
+
+    #[test]
+    fn marshal_public_key() {
+        let (serialized_public_key, private_key, _) = PrivateKey65::generate();
+        let public_key = PublicKey65::parse(&serialized_public_key).unwrap();
+        assert_eq!(serialized_public_key, public_key.to_bytes());
+        assert_eq!(
+            serialized_public_key,
+            private_key.to_public_key().to_bytes()
+        );
+    }
+}
diff --git a/rust/bssl-crypto/src/mlkem.rs b/rust/bssl-crypto/src/mlkem.rs
new file mode 100644
index 0000000..12881bb
--- /dev/null
+++ b/rust/bssl-crypto/src/mlkem.rs
@@ -0,0 +1,383 @@
+/* Copyright (c) 2024, Google Inc.
+ *
+ * Permission to use, copy, modify, and/or distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY
+ * SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION
+ * OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN
+ * CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+//! ML-KEM
+//!
+//! ML-KEM is a post-quantum key encapsulation mechanism, specified in
+//! [FIPS 203](https://csrc.nist.gov/pubs/fips/203/final).
+//! A KEM works like public-key encryption, except that the encrypted message is
+//! always a random key chosen by the algorithm.
+//!
+//! ```
+//! use bssl_crypto::mlkem;
+//!
+//! // Generate a key pair.
+//! let (serialized_public_key, private_key, private_seed) = mlkem::PrivateKey768::generate();
+//!
+//! // Send `serialized_public_key` to the sender. The sender parses it:
+//! let public_key = mlkem::PublicKey768::parse(&serialized_public_key).unwrap();
+//!
+//! // Generate and encrypt a shared secret key to that public key.
+//! let (ciphertext, shared_key) = public_key.encapsulate();
+//!
+//! // Send `ciphertext` to the holder of the private key.
+//! let shared_key2 = private_key.decapsulate(&ciphertext).unwrap();
+//! assert_eq!(shared_key, shared_key2);
+//!
+//! // `shared_key` would then be used with an algorithm from the `aead` module
+//! // to encrypt and authenticate data. The two keys may not match and so it's
+//! // critical to use an authenticated encryption mechanism to confirm the key.
+//! ```
+
+use crate::{
+    as_cbs, cbb_to_vec, initialized_boxed_struct, initialized_boxed_struct_fallible,
+    with_output_array_fallible, FfiSlice,
+};
+use alloc::{boxed::Box, vec::Vec};
+use core::mem::MaybeUninit;
+
+/// An ML-KEM-768 public key.
+pub struct PublicKey768(Box<bssl_sys::MLKEM768_public_key>);
+
+/// An ML-KEM-768 private key.
+pub struct PrivateKey768(Box<bssl_sys::MLKEM768_private_key>);
+
+/// An ML-KEM-1024 public key.
+///
+/// Use ML-KEM-768 unless you have a good reason to need this larger size.
+pub struct PublicKey1024(Box<bssl_sys::MLKEM1024_public_key>);
+
+/// An ML-KEM-1024 private key.
+///
+/// Use ML-KEM-768 unless you have a good reason to need this larger size.
+pub struct PrivateKey1024(Box<bssl_sys::MLKEM1024_private_key>);
+
+/// The number of bytes in an encoded ML-KEM-768 public key.
+pub const PUBLIC_KEY_BYTES_768: usize = bssl_sys::MLKEM768_PUBLIC_KEY_BYTES as usize;
+
+/// The number of bytes in an ML-KEM seed.
+pub const SEED_BYTES: usize = bssl_sys::MLKEM_SEED_BYTES as usize;
+
+/// The number of bytes in the ML-KEM-768 ciphertext.
+pub const CIPHERTEXT_BYTES_768: usize = bssl_sys::MLKEM768_CIPHERTEXT_BYTES as usize;
+
+/// The number of bytes in an ML-KEM shared secret.
+pub const SHARED_SECRET_BYTES: usize = bssl_sys::MLKEM_SHARED_SECRET_BYTES as usize;
+
+/// The number of bytes in an encoded ML-KEM-1024 public key.
+pub const PUBLIC_KEY_BYTES_1024: usize = bssl_sys::MLKEM1024_PUBLIC_KEY_BYTES as usize;
+
+/// The number of bytes in the ML-KEM-1024 ciphertext.
+pub const CIPHERTEXT_BYTES_1024: usize = bssl_sys::MLKEM1024_CIPHERTEXT_BYTES as usize;
+
+impl PublicKey768 {
+    /// Parse a public key from NIST's defined format.
+    pub fn parse(encoded: &[u8]) -> Option<Self> {
+        let mut cbs = as_cbs(encoded);
+        unsafe {
+            initialized_boxed_struct_fallible(|pub_key| {
+                // Safety: `pub_key` is the correct size via the type system and
+                // is fully written if this function returns 1.
+                bssl_sys::MLKEM768_parse_public_key(pub_key, &mut cbs) == 1 && cbs.len == 0
+            })
+        }
+        .map(Self)
+    }
+
+    /// Return the serialization of this public key.
+    pub fn to_bytes(&self) -> Vec<u8> {
+        unsafe {
+            cbb_to_vec(PUBLIC_KEY_BYTES_768, |cbb| {
+                let ok = bssl_sys::MLKEM768_marshal_public_key(cbb, &*self.0);
+                // `MLKEM768_marshal_public_key` only fails if it cannot
+                // allocate memory, but `cbb_to_vec` handles allocation.
+                assert_eq!(ok, 1);
+            })
+        }
+    }
+
+    /// Generate a secret key and encrypt it to this public key, returning the
+    /// ciphertext and the shared secret key.
+    pub fn encapsulate(&self) -> (Vec<u8>, [u8; SHARED_SECRET_BYTES]) {
+        let mut ciphertext = Box::new_uninit_slice(CIPHERTEXT_BYTES_768);
+        let mut shared_secret = MaybeUninit::<[u8; SHARED_SECRET_BYTES]>::uninit();
+
+        unsafe {
+            // Safety: the two buffer arguments are sized correctly and
+            // always fully written.
+            bssl_sys::MLKEM768_encap(
+                ciphertext.as_mut_ptr() as *mut u8,
+                shared_secret.as_mut_ptr() as *mut u8,
+                &*self.0,
+            );
+            (ciphertext.assume_init().into(), shared_secret.assume_init())
+        }
+    }
+}
+
+impl PrivateKey768 {
+    /// Generates a random public/private key pair returning a serialized public
+    /// key, a private key, and a private seed value that can be used to
+    /// regenerate the same private key in the future.
+    pub fn generate() -> (Vec<u8>, PrivateKey768, [u8; SEED_BYTES]) {
+        let mut public_key = Box::new_uninit_slice(PUBLIC_KEY_BYTES_768);
+        let mut private_key = Box::new(MaybeUninit::uninit());
+        let mut seed = MaybeUninit::<[u8; SEED_BYTES]>::uninit();
+
+        unsafe {
+            // Safety: the two buffer arguments are sized correctly and
+            // always fully written. `private_key` is sized correctly via
+            // the type system.
+            bssl_sys::MLKEM768_generate_key(
+                public_key.as_mut_ptr() as *mut u8,
+                seed.as_mut_ptr() as *mut u8,
+                private_key.as_mut_ptr(),
+            );
+
+            (
+                public_key.assume_init().into(),
+                Self(private_key.assume_init()),
+                seed.assume_init(),
+            )
+        }
+    }
+
+    /// Regenerate a private key from a seed value.
+    pub fn from_seed(seed: &[u8; SEED_BYTES]) -> Self {
+        Self(unsafe {
+            initialized_boxed_struct(|priv_key| {
+                // Safety: `priv_key` is correctly sized by the type system and
+                // is always fully written.
+                let ok = bssl_sys::MLKEM768_private_key_from_seed(
+                    priv_key,
+                    seed.as_ffi_ptr(),
+                    seed.len(),
+                );
+                // Since the seed value has the correct length, this function can
+                // never fail.
+                assert_eq!(ok, 1);
+            })
+        })
+    }
+
+    /// Derives the public key corresponding to this private key.
+    pub fn to_public_key(&self) -> PublicKey768 {
+        PublicKey768(unsafe {
+            initialized_boxed_struct(|pub_key| {
+                bssl_sys::MLKEM768_public_from_private(pub_key, &*self.0);
+            })
+        })
+    }
+
+    /// Decapsulates a shared secret from a ciphertext. This function only
+    /// returns `None` if ciphertext is the wrong length. For invalid
+    /// ciphertexts it returns a key that will always be the same for the
+    /// same `ciphertext` and private key, but which appears to be random
+    /// unless one has access to the private key. These alternatives occur in
+    /// constant time. Any subsequent symmetric encryption using the result
+    /// must use an authenticated encryption scheme in order to discover the
+    /// decapsulation failure.
+    pub fn decapsulate(&self, ciphertext: &[u8]) -> Option<[u8; SHARED_SECRET_BYTES]> {
+        unsafe {
+            with_output_array_fallible(|out, _| {
+                // Safety: `out` is the correct size via the type system and is
+                // always fully written if the return value is one.
+                bssl_sys::MLKEM768_decap(out, ciphertext.as_ffi_ptr(), ciphertext.len(), &*self.0)
+                    == 1
+            })
+        }
+    }
+}
+
+impl PublicKey1024 {
+    /// Parse a public key from NIST's defined format.
+    pub fn parse(encoded: &[u8]) -> Option<Self> {
+        let mut cbs = as_cbs(encoded);
+        unsafe {
+            initialized_boxed_struct_fallible(|pub_key| {
+                // Safety: `pub_key` is the correct size via the type system and
+                // is fully written if this function returns 1.
+                bssl_sys::MLKEM1024_parse_public_key(pub_key, &mut cbs) == 1 && cbs.len == 0
+            })
+        }
+        .map(Self)
+    }
+
+    /// Return the serialization of this public key.
+    pub fn to_bytes(&self) -> Vec<u8> {
+        unsafe {
+            cbb_to_vec(PUBLIC_KEY_BYTES_1024, |cbb| {
+                let ok = bssl_sys::MLKEM1024_marshal_public_key(cbb, &*self.0);
+                // `MLKEM1024_marshal_public_key` only fails if it cannot
+                // allocate memory, but `cbb_to_vec` handles allocation.
+                assert_eq!(ok, 1);
+            })
+        }
+    }
+
+    /// Generate a secret key and encrypt it to this public key, returning the
+    /// ciphertext and the shared secret key.
+    pub fn encapsulate(&self) -> (Vec<u8>, [u8; SHARED_SECRET_BYTES]) {
+        let mut ciphertext = Box::new_uninit_slice(CIPHERTEXT_BYTES_1024);
+        let mut shared_secret = MaybeUninit::<[u8; SHARED_SECRET_BYTES]>::uninit();
+
+        unsafe {
+            // Safety: the two buffer arguments are sized correctly and
+            // always fully written.
+            bssl_sys::MLKEM1024_encap(
+                ciphertext.as_mut_ptr() as *mut u8,
+                shared_secret.as_mut_ptr() as *mut u8,
+                &*self.0,
+            );
+            (ciphertext.assume_init().into(), shared_secret.assume_init())
+        }
+    }
+}
+
+impl PrivateKey1024 {
+    /// Generates a random public/private key pair returning a serialized public
+    /// key, a private key, and a private seed value that can be used to
+    /// regenerate the same private key in the future.
+    pub fn generate() -> (Vec<u8>, PrivateKey1024, [u8; SEED_BYTES]) {
+        let mut public_key = Box::new_uninit_slice(PUBLIC_KEY_BYTES_1024);
+        let mut private_key = Box::new(MaybeUninit::uninit());
+        let mut seed = MaybeUninit::<[u8; SEED_BYTES]>::uninit();
+
+        unsafe {
+            // Safety: the two buffer arguments are sized correctly and
+            // always fully written. `private_key` is sized correctly via
+            // the type system.
+            bssl_sys::MLKEM1024_generate_key(
+                public_key.as_mut_ptr() as *mut u8,
+                seed.as_mut_ptr() as *mut u8,
+                private_key.as_mut_ptr(),
+            );
+
+            (
+                public_key.assume_init().into(),
+                Self(private_key.assume_init()),
+                seed.assume_init(),
+            )
+        }
+    }
+
+    /// Regenerate a private key from a seed value.
+    pub fn from_seed(seed: &[u8; SEED_BYTES]) -> Self {
+        Self(unsafe {
+            initialized_boxed_struct(|priv_key| {
+                // Safety: `priv_key` is correctly sized by the type system and
+                // is always fully written.
+                let ok = bssl_sys::MLKEM1024_private_key_from_seed(
+                    priv_key,
+                    seed.as_ffi_ptr(),
+                    seed.len(),
+                );
+                // Since the seed value has the correct length, this function can
+                // never fail.
+                assert_eq!(ok, 1);
+            })
+        })
+    }
+
+    /// Derives the public key corresponding to this private key.
+    pub fn to_public_key(&self) -> PublicKey1024 {
+        PublicKey1024(unsafe {
+            initialized_boxed_struct(|pub_key| {
+                bssl_sys::MLKEM1024_public_from_private(pub_key, &*self.0);
+            })
+        })
+    }
+
+    /// Decapsulates a shared secret from a ciphertext. This function only
+    /// returns `None` if ciphertext is the wrong length. For invalid
+    /// ciphertexts it returns a key that will always be the same for the
+    /// same `ciphertext` and private key, but which appears to be random
+    /// unless one has access to the private key. These alternatives occur in
+    /// constant time. Any subsequent symmetric encryption using the result
+    /// must use an authenticated encryption scheme in order to discover the
+    /// decapsulation failure.
+    pub fn decapsulate(&self, ciphertext: &[u8]) -> Option<[u8; SHARED_SECRET_BYTES]> {
+        unsafe {
+            with_output_array_fallible(|out, _| {
+                // Safety: `out` is the correct size via the type system and is
+                // always fully written if the return value is one.
+                bssl_sys::MLKEM1024_decap(out, ciphertext.as_ffi_ptr(), ciphertext.len(), &*self.0)
+                    == 1
+            })
+        }
+    }
+}
+
+#[cfg(test)]
+mod test {
+    use super::*;
+
+    #[test]
+    fn basic_768() {
+        let (serialized_public_key, _private_key, private_seed) = PrivateKey768::generate();
+        let public_key = PublicKey768::parse(&serialized_public_key).unwrap();
+        let (ciphertext, shared_key) = public_key.encapsulate();
+        let private_key2 = PrivateKey768::from_seed(&private_seed);
+        let shared_key2 = private_key2.decapsulate(&ciphertext).unwrap();
+        assert_eq!(shared_key, shared_key2);
+    }
+
+    #[test]
+    fn basic_1024() {
+        let (serialized_public_key, _private_key, private_seed) = PrivateKey1024::generate();
+        let public_key = PublicKey1024::parse(&serialized_public_key).unwrap();
+        let (ciphertext, shared_key) = public_key.encapsulate();
+        let private_key2 = PrivateKey1024::from_seed(&private_seed);
+        let shared_key2 = private_key2.decapsulate(&ciphertext).unwrap();
+        assert_eq!(shared_key, shared_key2);
+    }
+
+    #[test]
+    fn wrong_length_ciphertext() {
+        let (_serialized_public_key, private_key, _private_seed) = PrivateKey768::generate();
+        assert!(matches!(private_key.decapsulate(&[0u8, 1, 2, 3]), None));
+
+        let (_serialized_public_key, private_key, _private_seed) = PrivateKey1024::generate();
+        assert!(matches!(private_key.decapsulate(&[0u8, 1, 2, 3]), None));
+    }
+
+    #[test]
+    fn wrong_length_public_key() {
+        assert!(matches!(PublicKey768::parse(&[0u8, 1, 2, 3]), None));
+        assert!(matches!(PublicKey1024::parse(&[0u8, 1, 2, 3]), None));
+    }
+
+    #[test]
+    fn marshal_public_key_768() {
+        let (serialized_public_key, private_key, _) = PrivateKey768::generate();
+        let public_key = PublicKey768::parse(&serialized_public_key).unwrap();
+        assert_eq!(serialized_public_key, public_key.to_bytes());
+        assert_eq!(
+            serialized_public_key,
+            private_key.to_public_key().to_bytes()
+        );
+    }
+
+    #[test]
+    fn marshal_public_key_1024() {
+        let (serialized_public_key, private_key, _) = PrivateKey1024::generate();
+        let public_key = PublicKey1024::parse(&serialized_public_key).unwrap();
+        assert_eq!(serialized_public_key, public_key.to_bytes());
+        assert_eq!(
+            serialized_public_key,
+            private_key.to_public_key().to_bytes()
+        );
+    }
+}
diff --git a/rust/bssl-sys/wrapper.h b/rust/bssl-sys/wrapper.h
index bd74049..3a578c9 100644
--- a/rust/bssl-sys/wrapper.h
+++ b/rust/bssl-sys/wrapper.h
@@ -45,6 +45,8 @@
 #include <openssl/md4.h>
 #include <openssl/md5.h>
 #include <openssl/mem.h>
+#include <openssl/mldsa.h>
+#include <openssl/mlkem.h>
 #include <openssl/obj.h>
 #include <openssl/obj_mac.h>
 #include <openssl/objects.h>