From 7d0667d1c0eec5486ed29148ec23ca1bc4600abd Mon Sep 17 00:00:00 2001 From: Aaron Kaiser Date: Wed, 19 Mar 2025 11:07:26 +0100 Subject: [PATCH] feat: add bindings for level 2 and 4; generate randomness from provided CSRNG --- Cargo.lock | 60 ++++----- Cargo.toml | 4 +- build.rs | 49 ++++--- src/lib.rs | 240 +++++++++++++++++++++++++++++----- src/unsafe_bindings.rs | 14 -- src/unsafe_bindings_level2.rs | 6 + src/unsafe_bindings_level3.rs | 6 + src/unsafe_bindings_level4.rs | 6 + 8 files changed, 285 insertions(+), 100 deletions(-) delete mode 100644 src/unsafe_bindings.rs create mode 100644 src/unsafe_bindings_level2.rs create mode 100644 src/unsafe_bindings_level3.rs create mode 100644 src/unsafe_bindings_level4.rs diff --git a/Cargo.lock b/Cargo.lock index 700d395..d628958 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -69,18 +69,6 @@ version = "1.15.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "48c757948c5ede0e46177b7add2e67155f70e33c07fea8284df6576da70b3719" -[[package]] -name = "getrandom" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "43a49c392881ce6d5c3b8cb70f98717b7c07aabbdff06687b9030dbfbe2725f8" -dependencies = [ - "cfg-if", - "libc", - "wasi", - "windows-targets", -] - [[package]] name = "glob" version = "0.3.2" @@ -141,9 +129,9 @@ name = "mlkem-native-rs" version = "0.1.0" dependencies = [ "bindgen", - "getrandom", - "libc", "make-cmd", + "rand_core", + "thiserror", ] [[package]] @@ -184,6 +172,12 @@ dependencies = [ "proc-macro2", ] +[[package]] +name = "rand_core" +version = "0.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "99d9a13982dcf210057a8a78572b2217b667c3beacbf3a0d8b454f6f82837d38" + [[package]] name = "regex" version = "1.11.1" @@ -236,21 +230,32 @@ dependencies = [ "unicode-ident", ] +[[package]] +name = "thiserror" +version = "2.0.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "567b8a2dae586314f7be2a752ec7474332959c6460e02bde30d702a66d488708" +dependencies = [ + "thiserror-impl", +] + +[[package]] +name = "thiserror-impl" +version = "2.0.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f7cf42b4507d8ea322120659672cf1b9dbb93f8f2d4ecfd6e51350ff5b17a1d" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "unicode-ident" version = "1.0.18" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5a5f39404a5da50712a4c1eecf25e90dd62b613502b7e925fd4e4d19b5c96512" -[[package]] -name = "wasi" -version = "0.13.3+wasi-0.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "26816d2e1a4a36a2940b96c5296ce403917633dff8f3440e9b236ed6f6bacad2" -dependencies = [ - "wit-bindgen-rt", -] - [[package]] name = "windows-targets" version = "0.52.6" @@ -314,12 +319,3 @@ name = "windows_x86_64_msvc" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" - -[[package]] -name = "wit-bindgen-rt" -version = "0.33.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3268f3d866458b787f390cf61f4bbb563b922d091359f9608842999eaee3943c" -dependencies = [ - "bitflags", -] diff --git a/Cargo.toml b/Cargo.toml index ed03ba2..212a537 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -5,8 +5,8 @@ version = "0.1.0" edition = "2021" [dependencies] -getrandom = "0.3.1" -libc = "0.2.171" +rand_core = "0.9.3" +thiserror = "2.0.12" [build-dependencies] bindgen = "0.71.1" diff --git a/build.rs b/build.rs index ea60ec5..eb6e0fc 100644 --- a/build.rs +++ b/build.rs @@ -2,25 +2,19 @@ use std::env; use std::path::PathBuf; fn main() { - // This is the directory where the `c` library is located. let libdir_path = PathBuf::from("mlkem-native") - // Canonicalize the path as `rustc-link-search` requires an absolute - // path. .canonicalize() .expect("cannot canonicalize path"); - // This is the path to the `c` headers file. let headers_path = libdir_path.join("mlkem/mlkem_native.h"); let headers_path_str = headers_path.to_str().expect("Path is not a valid string"); - // Tell cargo to look for shared libraries in the specified directory println!( "cargo:rustc-link-search={}", libdir_path.join("test/build").to_str().unwrap() ); - // Tell cargo to tell rustc to link our `hello` library. Cargo will - // automatically know it must look for a `libhello.a` file. + println!("cargo:rustc-link-lib=mlkem512"); println!("cargo:rustc-link-lib=mlkem768"); println!("cargo:rustc-link-lib=mlkem1024"); @@ -37,24 +31,39 @@ fn main() { panic!("could not compile mlkem-native"); } - // The bindgen::Builder is the main entry point - // to bindgen, and lets you build up options for - // the resulting bindings. - let bindings = bindgen::Builder::default() - // The input header we would like to generate - // bindings for. + let bindings_level2 = bindgen::Builder::default() .header(headers_path_str) - // Tell cargo to invalidate the built crate whenever any of the - // included header files changed. + .clang_arg("-DMLKEM_K=2") .parse_callbacks(Box::new(bindgen::CargoCallbacks::new())) - // Finish the builder and generate the bindings. .generate() - // Unwrap the Result and panic on failure. .expect("Unable to generate bindings"); - // Write the bindings to the $OUT_DIR/bindings.rs file. - let out_path = PathBuf::from(env::var("OUT_DIR").unwrap()).join("bindings.rs"); - bindings + let bindings_level3 = bindgen::Builder::default() + .header(headers_path_str) + .clang_arg("-DMLKEM_K=3") + .parse_callbacks(Box::new(bindgen::CargoCallbacks::new())) + .generate() + .expect("Unable to generate bindings"); + + let bindings_level4 = bindgen::Builder::default() + .header(headers_path_str) + .clang_arg("-DMLKEM_K=4") + .parse_callbacks(Box::new(bindgen::CargoCallbacks::new())) + .generate() + .expect("Unable to generate bindings"); + + let out_path = PathBuf::from(env::var("OUT_DIR").unwrap()).join("bindings_level2.rs"); + bindings_level2 + .write_to_file(out_path) + .expect("Couldn't write bindings!"); + + let out_path = PathBuf::from(env::var("OUT_DIR").unwrap()).join("bindings_level3.rs"); + bindings_level3 + .write_to_file(out_path) + .expect("Couldn't write bindings!"); + + let out_path = PathBuf::from(env::var("OUT_DIR").unwrap()).join("bindings_level4.rs"); + bindings_level4 .write_to_file(out_path) .expect("Couldn't write bindings!"); } diff --git a/src/lib.rs b/src/lib.rs index 29d1d94..5063464 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,10 +1,22 @@ +use rand_core::TryCryptoRng; use std::default::Default; +use thiserror::Error; -mod unsafe_bindings; +mod unsafe_bindings_level2; +mod unsafe_bindings_level3; +mod unsafe_bindings_level4; + +#[derive(Error, Debug)] +pub enum MLKEMNativeError { + #[error("the CSRNG failed due to insufficent entropy")] + InsufficentEntropy, + #[error("the library function encountered an internal error")] + LibraryError, +} macro_rules! reexport_const { ( $type_name:ident, $struct_name:ident ) => { - pub const $type_name: usize = unsafe_bindings::$type_name as usize; + pub const $type_name: usize = unsafe_bindings_level2::$type_name as usize; #[derive(Debug, PartialEq)] pub struct $struct_name([u8; $type_name]); @@ -17,7 +29,7 @@ macro_rules! reexport_const { }; } -reexport_const!(MLKEM512_SECRETKEYBYTES, MLKEM512Secretkey); +reexport_const!(MLKEM512_SECRETKEYBYTES, MLKEM512SecretKey); reexport_const!(MLKEM512_PUBLICKEYBYTES, MLKEM512PublicKey); reexport_const!(MLKEM512_CIPHERTEXTBYTES, MLKEM512Ciphertext); reexport_const!(MLKEM512_BYTES, MLKEM512SharedSecret); @@ -32,48 +44,66 @@ reexport_const!(MLKEM1024_PUBLICKEYBYTES, MLKEM1024PublicKey); reexport_const!(MLKEM1024_CIPHERTEXTBYTES, MLKEM1024Ciphertext); reexport_const!(MLKEM1024_BYTES, MLKEM1024SharedSecret); -pub fn mlkem768_keypair() -> Option<(MLKEM768SecretKey, MLKEM768PublicKey)> { - let mut sk = MLKEM768SecretKey::default(); - let mut pk = MLKEM768PublicKey::default(); +pub fn mlkem512_keypair( + rng: &mut impl TryCryptoRng, +) -> Result<(MLKEM512SecretKey, MLKEM512PublicKey), MLKEMNativeError> { + let mut sk = MLKEM512SecretKey::default(); + let mut pk = MLKEM512PublicKey::default(); + + let mut coins = [0u8; 2 * (unsafe_bindings_level2::MLKEM512_SYMBYTES as usize)]; + rng.try_fill_bytes(&mut coins) + .map_err(|_| MLKEMNativeError::InsufficentEntropy)?; let success = unsafe { - unsafe_bindings::PQCP_MLKEM_NATIVE_MLKEM768_keypair(pk.0.as_mut_ptr(), sk.0.as_mut_ptr()) - }; - - if success == 0 { - Some((sk, pk)) - } else { - None - } -} - -pub fn mlkem768_enc(pk: &MLKEM768PublicKey) -> Option<(MLKEM768Ciphertext, MLKEM768SharedSecret)> { - let mut ct = MLKEM768Ciphertext::default(); - let mut ss = MLKEM768SharedSecret::default(); - - let success = unsafe { - unsafe_bindings::PQCP_MLKEM_NATIVE_MLKEM768_enc( - ct.0.as_mut_ptr(), - ss.0.as_mut_ptr(), - pk.0.as_ptr(), + unsafe_bindings_level2::PQCP_MLKEM_NATIVE_MLKEM512_keypair_derand( + pk.0.as_mut_ptr(), + sk.0.as_mut_ptr(), + coins.as_ptr(), ) }; if success == 0 { - Some((ct, ss)) + Ok((sk, pk)) } else { - None + Err(MLKEMNativeError::LibraryError) } } -pub fn mlkem768_dec( +pub fn mlkem512_enc( + rng: &mut impl TryCryptoRng, + pk: &MLKEM768PublicKey, +) -> Result<(MLKEM768Ciphertext, MLKEM768SharedSecret), MLKEMNativeError> { + let mut ct = MLKEM768Ciphertext::default(); + let mut ss = MLKEM768SharedSecret::default(); + + let mut coins = [0u8; unsafe_bindings_level2::MLKEM768_SYMBYTES as usize]; + rng.try_fill_bytes(&mut coins) + .map_err(|_| MLKEMNativeError::InsufficentEntropy)?; + + let success = unsafe { + unsafe_bindings_level2::PQCP_MLKEM_NATIVE_MLKEM512_enc_derand( + ct.0.as_mut_ptr(), + ss.0.as_mut_ptr(), + pk.0.as_ptr(), + coins.as_ptr(), + ) + }; + + if success == 0 { + Ok((ct, ss)) + } else { + Err(MLKEMNativeError::LibraryError) + } +} + +pub fn mlkem512_dec( sk: &MLKEM768SecretKey, ct: &MLKEM768Ciphertext, -) -> Option { +) -> Result { let mut ss = MLKEM768SharedSecret::default(); let success = unsafe { - unsafe_bindings::PQCP_MLKEM_NATIVE_MLKEM768_dec( + unsafe_bindings_level2::PQCP_MLKEM_NATIVE_MLKEM512_dec( ss.0.as_mut_ptr(), ct.0.as_ptr(), sk.0.as_ptr(), @@ -81,8 +111,154 @@ pub fn mlkem768_dec( }; if success == 0 { - Some(ss) + Ok(ss) } else { - None + Err(MLKEMNativeError::LibraryError) + } +} + +pub fn mlkem768_keypair( + rng: &mut impl TryCryptoRng, +) -> Result<(MLKEM768SecretKey, MLKEM768PublicKey), MLKEMNativeError> { + let mut sk = MLKEM768SecretKey::default(); + let mut pk = MLKEM768PublicKey::default(); + + let mut coins = [0u8; 2 * (unsafe_bindings_level2::MLKEM768_SYMBYTES as usize)]; + rng.try_fill_bytes(&mut coins) + .map_err(|_| MLKEMNativeError::InsufficentEntropy)?; + + let success = unsafe { + unsafe_bindings_level3::PQCP_MLKEM_NATIVE_MLKEM768_keypair_derand( + pk.0.as_mut_ptr(), + sk.0.as_mut_ptr(), + coins.as_ptr(), + ) + }; + + if success == 0 { + Ok((sk, pk)) + } else { + Err(MLKEMNativeError::LibraryError) + } +} + +pub fn mlkem768_enc( + rng: &mut impl TryCryptoRng, + pk: &MLKEM768PublicKey, +) -> Result<(MLKEM768Ciphertext, MLKEM768SharedSecret), MLKEMNativeError> { + let mut ct = MLKEM768Ciphertext::default(); + let mut ss = MLKEM768SharedSecret::default(); + + let mut coins = [0u8; unsafe_bindings_level2::MLKEM768_SYMBYTES as usize]; + rng.try_fill_bytes(&mut coins) + .map_err(|_| MLKEMNativeError::InsufficentEntropy)?; + + let success = unsafe { + unsafe_bindings_level3::PQCP_MLKEM_NATIVE_MLKEM768_enc_derand( + ct.0.as_mut_ptr(), + ss.0.as_mut_ptr(), + pk.0.as_ptr(), + coins.as_ptr(), + ) + }; + + if success == 0 { + Ok((ct, ss)) + } else { + Err(MLKEMNativeError::LibraryError) + } +} + +pub fn mlkem768_dec( + sk: &MLKEM768SecretKey, + ct: &MLKEM768Ciphertext, +) -> Result { + let mut ss = MLKEM768SharedSecret::default(); + + let success = unsafe { + unsafe_bindings_level3::PQCP_MLKEM_NATIVE_MLKEM768_dec( + ss.0.as_mut_ptr(), + ct.0.as_ptr(), + sk.0.as_ptr(), + ) + }; + + if success == 0 { + Ok(ss) + } else { + Err(MLKEMNativeError::LibraryError) + } +} + +pub fn mlkem1024_keypair( + rng: &mut impl TryCryptoRng, +) -> Result<(MLKEM768SecretKey, MLKEM768PublicKey), MLKEMNativeError> { + let mut sk = MLKEM768SecretKey::default(); + let mut pk = MLKEM768PublicKey::default(); + + let mut coins = [0u8; 2 * (unsafe_bindings_level2::MLKEM1024_SYMBYTES as usize)]; + rng.try_fill_bytes(&mut coins) + .map_err(|_| MLKEMNativeError::InsufficentEntropy)?; + + let success = unsafe { + unsafe_bindings_level4::PQCP_MLKEM_NATIVE_MLKEM1024_keypair_derand( + pk.0.as_mut_ptr(), + sk.0.as_mut_ptr(), + coins.as_ptr(), + ) + }; + + if success == 0 { + Ok((sk, pk)) + } else { + Err(MLKEMNativeError::LibraryError) + } +} + +pub fn mlkem1024_enc( + rng: &mut impl TryCryptoRng, + pk: &MLKEM768PublicKey, +) -> Result<(MLKEM768Ciphertext, MLKEM768SharedSecret), MLKEMNativeError> { + let mut ct = MLKEM768Ciphertext::default(); + let mut ss = MLKEM768SharedSecret::default(); + + let mut coins = [0u8; unsafe_bindings_level2::MLKEM1024_SYMBYTES as usize]; + rng.try_fill_bytes(&mut coins) + .map_err(|_| MLKEMNativeError::InsufficentEntropy)?; + + let success = unsafe { + unsafe_bindings_level4::PQCP_MLKEM_NATIVE_MLKEM1024_enc_derand( + ct.0.as_mut_ptr(), + ss.0.as_mut_ptr(), + pk.0.as_ptr(), + coins.as_ptr(), + ) + }; + + if success == 0 { + Ok((ct, ss)) + } else { + Err(MLKEMNativeError::LibraryError) + } +} + +pub fn mlkem1024_dec( + sk: &MLKEM768SecretKey, + ct: &MLKEM768Ciphertext, +) -> Result { + let mut ss = MLKEM768SharedSecret::default(); + + let success = unsafe { + unsafe_bindings_level4::PQCP_MLKEM_NATIVE_MLKEM1024_dec( + ss.0.as_mut_ptr(), + ct.0.as_ptr(), + sk.0.as_ptr(), + ) + }; + + if success == 0 { + Ok(ss) + } else { + Err(MLKEMNativeError::LibraryError) } } diff --git a/src/unsafe_bindings.rs b/src/unsafe_bindings.rs deleted file mode 100644 index 5a29265..0000000 --- a/src/unsafe_bindings.rs +++ /dev/null @@ -1,14 +0,0 @@ -#![allow(non_upper_case_globals)] -#![allow(non_camel_case_types)] -#![allow(non_snake_case)] - -use std::slice; - -include!(concat!(env!("OUT_DIR"), "/bindings.rs")); - -#[no_mangle] -pub unsafe extern "C" fn randombytes(buf: *mut u8, len: libc::size_t) -> libc::c_int { - let buf = slice::from_raw_parts_mut(buf, len); - getrandom::fill(buf).expect("RNG Failed"); - 0 -} diff --git a/src/unsafe_bindings_level2.rs b/src/unsafe_bindings_level2.rs new file mode 100644 index 0000000..1618754 --- /dev/null +++ b/src/unsafe_bindings_level2.rs @@ -0,0 +1,6 @@ +#![allow(non_upper_case_globals)] +#![allow(non_camel_case_types)] +#![allow(non_snake_case)] +#![allow(unused)] + +include!(concat!(env!("OUT_DIR"), "/bindings_level2.rs")); diff --git a/src/unsafe_bindings_level3.rs b/src/unsafe_bindings_level3.rs new file mode 100644 index 0000000..8a72b8b --- /dev/null +++ b/src/unsafe_bindings_level3.rs @@ -0,0 +1,6 @@ +#![allow(non_upper_case_globals)] +#![allow(non_camel_case_types)] +#![allow(non_snake_case)] +#![allow(unused)] + +include!(concat!(env!("OUT_DIR"), "/bindings_level3.rs")); diff --git a/src/unsafe_bindings_level4.rs b/src/unsafe_bindings_level4.rs new file mode 100644 index 0000000..210b9e6 --- /dev/null +++ b/src/unsafe_bindings_level4.rs @@ -0,0 +1,6 @@ +#![allow(non_upper_case_globals)] +#![allow(non_camel_case_types)] +#![allow(non_snake_case)] +#![allow(unused)] + +include!(concat!(env!("OUT_DIR"), "/bindings_level4.rs"));