From f8bb652b01d3b34a20ddbaaa35def260783ee734 Mon Sep 17 00:00:00 2001 From: Adam Langley Date: Thu, 19 Sep 2024 13:34:25 -0700 Subject: [PATCH] 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 Commit-Queue: Adam Langley --- gen/sources.bzl | 2 + gen/sources.cmake | 2 + gen/sources.gni | 2 + gen/sources.json | 2 + rust/bssl-crypto/src/lib.rs | 67 +++++- rust/bssl-crypto/src/mldsa.rs | 278 ++++++++++++++++++++++++ rust/bssl-crypto/src/mlkem.rs | 383 ++++++++++++++++++++++++++++++++++ rust/bssl-sys/wrapper.h | 2 + 8 files changed, 737 insertions(+), 1 deletion(-) create mode 100644 rust/bssl-crypto/src/mldsa.rs create mode 100644 rust/bssl-crypto/src/mlkem.rs diff --git a/gen/sources.bzl b/gen/sources.bzl index 1ca1a8bdf4..5bd088ef90 100644 --- a/gen/sources.bzl +++ b/gen/sources.bzl @@ -2673,6 +2673,8 @@ rust_bssl_crypto_sources = [ "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 94b619598d..594b417b49 100644 --- a/gen/sources.cmake +++ b/gen/sources.cmake @@ -2719,6 +2719,8 @@ set( 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 131de078fe..10a0c016fa 100644 --- a/gen/sources.gni +++ b/gen/sources.gni @@ -2673,6 +2673,8 @@ rust_bssl_crypto_sources = [ "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 f5879cd3ec..7f57e6e6c9 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 306215273f..070895e9ce 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 ed25519; 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 @@ where } } +/// 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(init: F) -> Box +where + F: FnOnce(*mut T), +{ + let mut out_uninit = Box::new(core::mem::MaybeUninit::::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(init: F) -> Option> +where + F: FnOnce(*mut T) -> bool, +{ + let mut out_uninit = Box::new(core::mem::MaybeUninit::::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 @@ impl Drop for Buffer { } } +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 @@ fn cbb_to_buffer(initial_capacity: usize, func: F 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(len: usize, func: F) -> Vec { + 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 0000000000..a1433d26ef --- /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); + +/// An ML-DSA-65 public key. +pub struct PublicKey65(Box); + +/// 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, 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 { + 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> { + 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 { + 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 { + 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 0000000000..12881bbd79 --- /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); + +/// An ML-KEM-768 private key. +pub struct PrivateKey768(Box); + +/// 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); + +/// 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); + +/// 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 { + 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 { + 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; 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, 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 { + 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 { + 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; 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, 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 bd74049556..3a578c9439 100644 --- a/rust/bssl-sys/wrapper.h +++ b/rust/bssl-sys/wrapper.h @@ -45,6 +45,8 @@ #include #include #include +#include +#include #include #include #include