From 0307d7cbdf1b0461e3f39074746c696ba40bebbd Mon Sep 17 00:00:00 2001 From: Jakub Jelen Date: Thu, 13 Mar 2025 11:31:20 +0100 Subject: [PATCH 01/27] Add internal module to simplify working with OSSL_PARAM structure We discussed that this API is not well suitable for the end users but still, it required for several operations in OpenSSL 3.* so instead of calling to FFI for every use of this API, this introduces simple wrappers that allow building of the params and their usage. Signed-off-by: Jakub Jelen --- openssl-sys/build/run_bindgen.rs | 1 + openssl-sys/src/handwritten/mod.rs | 6 + openssl-sys/src/handwritten/param_build.rs | 32 ++++ openssl-sys/src/handwritten/params.rs | 23 ++- openssl-sys/src/handwritten/types.rs | 3 + openssl/src/lib.rs | 2 + openssl/src/ossl_param.rs | 182 +++++++++++++++++++++ systest/build.rs | 3 +- 8 files changed, 248 insertions(+), 4 deletions(-) create mode 100644 openssl-sys/src/handwritten/param_build.rs create mode 100644 openssl/src/ossl_param.rs diff --git a/openssl-sys/build/run_bindgen.rs b/openssl-sys/build/run_bindgen.rs index cc0efd8b5..e3a155142 100644 --- a/openssl-sys/build/run_bindgen.rs +++ b/openssl-sys/build/run_bindgen.rs @@ -58,6 +58,7 @@ const INCLUDES: &str = " #endif #if OPENSSL_VERSION_NUMBER >= 0x30000000 +#include #include #endif diff --git a/openssl-sys/src/handwritten/mod.rs b/openssl-sys/src/handwritten/mod.rs index 47b3360fd..33e405221 100644 --- a/openssl-sys/src/handwritten/mod.rs +++ b/openssl-sys/src/handwritten/mod.rs @@ -15,6 +15,9 @@ pub use self::hmac::*; pub use self::kdf::*; pub use self::object::*; pub use self::ocsp::*; +#[cfg(ossl300)] +pub use self::param_build::*; +#[cfg(ossl300)] pub use self::params::*; pub use self::pem::*; pub use self::pkcs12::*; @@ -54,6 +57,9 @@ mod hmac; mod kdf; mod object; mod ocsp; +#[cfg(ossl300)] +mod param_build; +#[cfg(ossl300)] mod params; mod pem; mod pkcs12; diff --git a/openssl-sys/src/handwritten/param_build.rs b/openssl-sys/src/handwritten/param_build.rs new file mode 100644 index 000000000..7efbdb99c --- /dev/null +++ b/openssl-sys/src/handwritten/param_build.rs @@ -0,0 +1,32 @@ +use super::super::*; +use libc::*; + +/* OpenSSL 3.* only */ + +extern "C" { + pub fn OSSL_PARAM_BLD_new() -> *mut OSSL_PARAM_BLD; + pub fn OSSL_PARAM_BLD_free(bld: *mut OSSL_PARAM_BLD); + pub fn OSSL_PARAM_BLD_push_BN( + bld: *mut OSSL_PARAM_BLD, + key: *const c_char, + bn: *const BIGNUM, + ) -> c_int; + pub fn OSSL_PARAM_BLD_push_utf8_string( + bld: *mut OSSL_PARAM_BLD, + key: *const c_char, + buf: *const c_char, + bsize: usize, + ) -> c_int; + pub fn OSSL_PARAM_BLD_push_octet_string( + bld: *mut OSSL_PARAM_BLD, + key: *const c_char, + buf: *const c_void, + bsize: usize, + ) -> c_int; + pub fn OSSL_PARAM_BLD_push_uint( + bld: *mut OSSL_PARAM_BLD, + key: *const c_char, + buf: c_uint, + ) -> c_int; + pub fn OSSL_PARAM_BLD_to_param(bld: *mut OSSL_PARAM_BLD) -> *mut OSSL_PARAM; +} diff --git a/openssl-sys/src/handwritten/params.rs b/openssl-sys/src/handwritten/params.rs index 542cef337..913cc0e23 100644 --- a/openssl-sys/src/handwritten/params.rs +++ b/openssl-sys/src/handwritten/params.rs @@ -2,15 +2,32 @@ use super::super::*; use libc::*; extern "C" { - #[cfg(ossl300)] + pub fn OSSL_PARAM_free(p: *mut OSSL_PARAM); pub fn OSSL_PARAM_construct_uint(key: *const c_char, buf: *mut c_uint) -> OSSL_PARAM; - #[cfg(ossl300)] pub fn OSSL_PARAM_construct_end() -> OSSL_PARAM; - #[cfg(ossl300)] pub fn OSSL_PARAM_construct_octet_string( key: *const c_char, buf: *mut c_void, bsize: size_t, ) -> OSSL_PARAM; + pub fn OSSL_PARAM_locate(p: *mut OSSL_PARAM, key: *const c_char) -> *mut OSSL_PARAM; + pub fn OSSL_PARAM_get_BN(p: *const OSSL_PARAM, val: *mut *mut BIGNUM) -> c_int; + pub fn OSSL_PARAM_get_utf8_string( + p: *const OSSL_PARAM, + val: *mut *mut c_char, + max_len: usize, + ) -> c_int; + pub fn OSSL_PARAM_get_utf8_string_ptr(p: *const OSSL_PARAM, val: *mut *const c_char) -> c_int; + pub fn OSSL_PARAM_get_octet_string( + p: *const OSSL_PARAM, + val: *mut *mut c_void, + max_len: usize, + used_len: *mut usize, + ) -> c_int; + pub fn OSSL_PARAM_get_octet_string_ptr( + p: *const OSSL_PARAM, + val: *mut *const c_void, + used_len: *mut usize, + ) -> c_int; } diff --git a/openssl-sys/src/handwritten/types.rs b/openssl-sys/src/handwritten/types.rs index d465a4414..6fda6fa6e 100644 --- a/openssl-sys/src/handwritten/types.rs +++ b/openssl-sys/src/handwritten/types.rs @@ -1140,6 +1140,9 @@ pub struct OSSL_PARAM { return_size: size_t, } +#[cfg(ossl300)] +pub enum OSSL_PARAM_BLD {} + #[cfg(ossl300)] pub enum EVP_KDF {} #[cfg(ossl300)] diff --git a/openssl/src/lib.rs b/openssl/src/lib.rs index 1afe5de38..5ae46337e 100644 --- a/openssl/src/lib.rs +++ b/openssl/src/lib.rs @@ -177,6 +177,8 @@ pub mod memcmp; pub mod nid; #[cfg(not(osslconf = "OPENSSL_NO_OCSP"))] pub mod ocsp; +#[cfg(ossl300)] +mod ossl_param; pub mod pkcs12; pub mod pkcs5; #[cfg(not(any(boringssl, awslc)))] diff --git a/openssl/src/ossl_param.rs b/openssl/src/ossl_param.rs new file mode 100644 index 000000000..daaeadc5b --- /dev/null +++ b/openssl/src/ossl_param.rs @@ -0,0 +1,182 @@ +//! OSSL_PARAM management for OpenSSL 3.* +//! +//! The OSSL_PARAM structure represents generic attribute that can represent various +//! properties in OpenSSL, including keys and operations. +//! +//! For convinience, the OSSL_PARAM_BLD builder can be used to dynamically construct +//! these structure. +//! +//! Note, that this module is available only in OpenSSL 3.* and +//! only internally for this crate! +//! +use crate::bn::{BigNum, BigNumRef}; +use crate::error::ErrorStack; +use crate::util; +use crate::{cvt, cvt_p}; +use foreign_types::{ForeignType, ForeignTypeRef}; +use libc::{c_char, c_uint, c_void}; +use openssl_macros::corresponds; +use std::ffi::CStr; +use std::ptr; + +foreign_type_and_impl_send_sync! { + type CType = ffi::OSSL_PARAM; + fn drop = ffi::OSSL_PARAM_free; + + /// `OsslParam` constructed using `OsslParamBuilder`. + pub struct OsslParam; + /// Reference to `OsslParam`. + pub struct OsslParamRef; +} + +impl OsslParam {} + +impl OsslParamRef { + /// Locates the `OsslParam` in the `OsslParam` array + #[corresponds(OSSL_PARAM_locate)] + pub fn locate(&self, key: &[u8]) -> Result<&OsslParamRef, ErrorStack> { + unsafe { + let param = cvt_p(ffi::OSSL_PARAM_locate( + self.as_ptr(), + key.as_ptr() as *const c_char, + ))?; + Ok(OsslParamRef::from_ptr(param)) + } + } + + /// Get `BigNum` from the current `OsslParam` + #[allow(dead_code)] + #[corresponds(OSSL_PARAM_get_BN)] + pub fn get_bn(&self) -> Result { + unsafe { + let mut bn: *mut ffi::BIGNUM = ptr::null_mut(); + cvt(ffi::OSSL_PARAM_get_BN(self.as_ptr(), &mut bn))?; + Ok(BigNum::from_ptr(bn)) + } + } + + /// Get `&str` from the current `OsslParam` + #[allow(dead_code)] + #[corresponds(OSSL_PARAM_get_utf8_string)] + pub fn get_utf8_string(&self) -> Result<&str, ErrorStack> { + unsafe { + let mut val: *const c_char = ptr::null_mut(); + cvt(ffi::OSSL_PARAM_get_utf8_string_ptr(self.as_ptr(), &mut val))?; + Ok(CStr::from_ptr(val).to_str().unwrap()) + } + } + + /// Get octet string (as `&[u8]) from the current `OsslParam` + #[corresponds(OSSL_PARAM_get_octet_string)] + pub fn get_octet_string(&self) -> Result<&[u8], ErrorStack> { + unsafe { + let mut val: *const c_void = ptr::null_mut(); + let mut val_len: usize = 0; + cvt(ffi::OSSL_PARAM_get_octet_string_ptr( + self.as_ptr(), + &mut val, + &mut val_len, + ))?; + Ok(util::from_raw_parts(val as *const u8, val_len)) + } + } +} + +foreign_type_and_impl_send_sync! { + type CType = ffi::OSSL_PARAM_BLD; + fn drop = ffi::OSSL_PARAM_BLD_free; + + /// Builder used to construct `OsslParam`. + pub struct OsslParamBuilder; + /// Reference to `OsslParamBuilder`. + pub struct OsslParamBuilderRef; +} + +impl OsslParamBuilder { + /// Returns a builder for a OsslParam arrays. + /// + /// The array is initially empty. + #[corresponds(OSSL_PARAM_BLD_new)] + pub fn new() -> Result { + unsafe { + ffi::init(); + + cvt_p(ffi::OSSL_PARAM_BLD_new()).map(OsslParamBuilder) + } + } + + /// Constructs the `OsslParam`. + #[corresponds(OSSL_PARAM_BLD_to_param)] + pub fn to_param(&self) -> Result { + unsafe { + let params = cvt_p(ffi::OSSL_PARAM_BLD_to_param(self.0))?; + Ok(OsslParam::from_ptr(params)) + } + } +} + +impl OsslParamBuilderRef { + /// Adds a `BigNum` to `OsslParamBuilder`. + /// + /// Note, that both key and bn need to exist until the `to_param` is called! + #[allow(dead_code)] + #[corresponds(OSSL_PARAM_BLD_push_BN)] + pub fn add_bn(&self, key: &[u8], bn: &BigNumRef) -> Result<(), ErrorStack> { + unsafe { + cvt(ffi::OSSL_PARAM_BLD_push_BN( + self.as_ptr(), + key.as_ptr() as *const c_char, + bn.as_ptr(), + )) + .map(|_| ()) + } + } + + /// Adds a utf8 string to `OsslParamBuilder`. + /// + /// Note, that both `key` and `buf` need to exist until the `to_param` is called! + #[allow(dead_code)] + #[corresponds(OSSL_PARAM_BLD_push_utf8_string)] + pub fn add_utf8_string(&self, key: &[u8], buf: &str) -> Result<(), ErrorStack> { + unsafe { + cvt(ffi::OSSL_PARAM_BLD_push_utf8_string( + self.as_ptr(), + key.as_ptr() as *const c_char, + buf.as_ptr() as *const c_char, + buf.len(), + )) + .map(|_| ()) + } + } + + /// Adds a octet string to `OsslParamBuilder`. + /// + /// Note, that both `key` and `buf` need to exist until the `to_param` is called! + #[corresponds(OSSL_PARAM_BLD_push_octet_string)] + pub fn add_octet_string(&self, key: &[u8], buf: &[u8]) -> Result<(), ErrorStack> { + unsafe { + cvt(ffi::OSSL_PARAM_BLD_push_octet_string( + self.as_ptr(), + key.as_ptr() as *const c_char, + buf.as_ptr() as *const c_void, + buf.len(), + )) + .map(|_| ()) + } + } + + /// Adds a unsigned int to `OsslParamBuilder`. + /// + /// Note, that both `key` and `buf` need to exist until the `to_param` is called! + #[corresponds(OSSL_PARAM_BLD_push_uint)] + pub fn add_uint(&self, key: &[u8], val: u32) -> Result<(), ErrorStack> { + unsafe { + cvt(ffi::OSSL_PARAM_BLD_push_uint( + self.as_ptr(), + key.as_ptr() as *const c_char, + val as c_uint, + )) + .map(|_| ()) + } + } +} diff --git a/systest/build.rs b/systest/build.rs index 22fc6b836..1b653cd16 100644 --- a/systest/build.rs +++ b/systest/build.rs @@ -83,7 +83,8 @@ fn main() { } if version >= 0x30000000 { - cfg.header("openssl/provider.h"); + cfg.header("openssl/provider.h") + .header("openssl/param_build.h"); } if version >= 0x30200000 { cfg.header("openssl/thread.h"); From 88e9baa8bc91484cfbf83ef7d6ec27129d98bf64 Mon Sep 17 00:00:00 2001 From: Jakub Jelen Date: Thu, 13 Mar 2025 13:01:33 +0100 Subject: [PATCH 02/27] kdf: Use the OsslParam API to simplify building OSSL_PARAM Signed-off-by: Jakub Jelen --- openssl/src/kdf.rs | 107 ++++++++++++++++++--------------------------- 1 file changed, 42 insertions(+), 65 deletions(-) diff --git a/openssl/src/kdf.rs b/openssl/src/kdf.rs index a5da35250..13ad2899e 100644 --- a/openssl/src/kdf.rs +++ b/openssl/src/kdf.rs @@ -25,14 +25,23 @@ impl Drop for EvpKdfCtx { cfg_if::cfg_if! { if #[cfg(all(ossl320, not(osslconf = "OPENSSL_NO_ARGON2")))] { use std::cmp; - use std::ffi::c_void; - use std::mem::MaybeUninit; use std::ptr; use foreign_types::ForeignTypeRef; use libc::c_char; use crate::{cvt, cvt_p}; use crate::lib_ctx::LibCtxRef; use crate::error::ErrorStack; + use crate::ossl_param::OsslParamBuilder; + + const OSSL_KDF_PARAM_PASSWORD: &[u8; 5] = b"pass\0"; + const OSSL_KDF_PARAM_SALT: &[u8; 5] = b"salt\0"; + const OSSL_KDF_PARAM_SECRET: &[u8; 7] = b"secret\0"; + const OSSL_KDF_PARAM_ITER: &[u8; 5] = b"iter\0"; + const OSSL_KDF_PARAM_SIZE: &[u8; 5] = b"size\0"; + const OSSL_KDF_PARAM_THREADS: &[u8; 8] = b"threads\0"; + const OSSL_KDF_PARAM_ARGON2_AD: &[u8; 3] = b"ad\0"; + const OSSL_KDF_PARAM_ARGON2_LANES: &[u8; 6] = b"lanes\0"; + const OSSL_KDF_PARAM_ARGON2_MEMCOST: &[u8; 8] = b"memcost\0"; /// Derives a key using the argon2id algorithm. /// @@ -48,72 +57,40 @@ cfg_if::cfg_if! { salt: &[u8], ad: Option<&[u8]>, secret: Option<&[u8]>, - mut iter: u32, - mut lanes: u32, - mut memcost: u32, + iter: u32, + lanes: u32, + memcost: u32, out: &mut [u8], ) -> Result<(), ErrorStack> { - unsafe { + let libctx = ctx.map_or(ptr::null_mut(), ForeignTypeRef::as_ptr); + let max_threads = unsafe { ffi::init(); - let libctx = ctx.map_or(ptr::null_mut(), ForeignTypeRef::as_ptr); - - let max_threads = ffi::OSSL_get_max_threads(libctx); - let mut threads = 1; - // If max_threads is 0, then this isn't a threaded build. - // If max_threads is > u32::MAX we need to clamp since - // argon2id's threads parameter is a u32. - if max_threads > 0 { - threads = cmp::min(lanes, cmp::min(max_threads, u32::MAX as u64) as u32); - } - let mut params: [ffi::OSSL_PARAM; 10] = - core::array::from_fn(|_| MaybeUninit::::zeroed().assume_init()); - let mut idx = 0; - params[idx] = ffi::OSSL_PARAM_construct_octet_string( - b"pass\0".as_ptr() as *const c_char, - pass.as_ptr() as *mut c_void, - pass.len(), - ); - idx += 1; - params[idx] = ffi::OSSL_PARAM_construct_octet_string( - b"salt\0".as_ptr() as *const c_char, - salt.as_ptr() as *mut c_void, - salt.len(), - ); - idx += 1; - params[idx] = - ffi::OSSL_PARAM_construct_uint(b"threads\0".as_ptr() as *const c_char, &mut threads); - idx += 1; - params[idx] = - ffi::OSSL_PARAM_construct_uint(b"lanes\0".as_ptr() as *const c_char, &mut lanes); - idx += 1; - params[idx] = - ffi::OSSL_PARAM_construct_uint(b"memcost\0".as_ptr() as *const c_char, &mut memcost); - idx += 1; - params[idx] = - ffi::OSSL_PARAM_construct_uint(b"iter\0".as_ptr() as *const c_char, &mut iter); - idx += 1; - let mut size = out.len() as u32; - params[idx] = - ffi::OSSL_PARAM_construct_uint(b"size\0".as_ptr() as *const c_char, &mut size); - idx += 1; - if let Some(ad) = ad { - params[idx] = ffi::OSSL_PARAM_construct_octet_string( - b"ad\0".as_ptr() as *const c_char, - ad.as_ptr() as *mut c_void, - ad.len(), - ); - idx += 1; - } - if let Some(secret) = secret { - params[idx] = ffi::OSSL_PARAM_construct_octet_string( - b"secret\0".as_ptr() as *const c_char, - secret.as_ptr() as *mut c_void, - secret.len(), - ); - idx += 1; - } - params[idx] = ffi::OSSL_PARAM_construct_end(); - + ffi::OSSL_get_max_threads(libctx) + }; + let mut threads = 1; + // If max_threads is 0, then this isn't a threaded build. + // If max_threads is > u32::MAX we need to clamp since + // argon2id's threads parameter is a u32. + if max_threads > 0 { + threads = cmp::min(lanes, cmp::min(max_threads, u32::MAX as u64) as u32); + } + let bld = OsslParamBuilder::new()?; + bld.add_octet_string(OSSL_KDF_PARAM_PASSWORD, pass)?; + bld.add_octet_string(OSSL_KDF_PARAM_SALT, salt)?; + bld.add_uint(OSSL_KDF_PARAM_THREADS, threads)?; + bld.add_uint(OSSL_KDF_PARAM_ARGON2_LANES, lanes)?; + bld.add_uint(OSSL_KDF_PARAM_ARGON2_MEMCOST, memcost)?; + bld.add_uint(OSSL_KDF_PARAM_ITER, iter)?; + let size = out.len() as u32; + bld.add_uint(OSSL_KDF_PARAM_SIZE, size)?; + if let Some(ad) = ad { + bld.add_octet_string(OSSL_KDF_PARAM_ARGON2_AD, ad)?; + } + if let Some(secret) = secret { + bld.add_octet_string(OSSL_KDF_PARAM_SECRET, secret)?; + } + let params = bld.to_param()?; + unsafe { let argon2 = EvpKdf(cvt_p(ffi::EVP_KDF_fetch( libctx, b"ARGON2ID\0".as_ptr() as *const c_char, From 7ceae78ddb47b053384a52b2d9d3da9a7f415488 Mon Sep 17 00:00:00 2001 From: Jakub Jelen Date: Thu, 13 Mar 2025 11:50:47 +0100 Subject: [PATCH 03/27] Use the new OpenSSL 3.* API for managing EVP_PKEY The OpenSSL 3.* users now do not have a way to use non-deprecated API by using this rust bindings, which is not sustainable in the long term as either distributions will stop building with the deprecated API or it will be eventually removed. This is partially based on #2051 which was abandoned. Signed-off-by: Jakub Jelen --- openssl-sys/src/core_dispatch.rs | 11 ++++++ openssl-sys/src/evp.rs | 9 +++++ openssl-sys/src/handwritten/evp.rs | 27 ++++++++++++++ openssl-sys/src/lib.rs | 4 +++ openssl/src/pkey_ctx.rs | 56 ++++++++++++++++++++++++++++++ 5 files changed, 107 insertions(+) create mode 100644 openssl-sys/src/core_dispatch.rs diff --git a/openssl-sys/src/core_dispatch.rs b/openssl-sys/src/core_dispatch.rs new file mode 100644 index 000000000..446dfc96e --- /dev/null +++ b/openssl-sys/src/core_dispatch.rs @@ -0,0 +1,11 @@ +use super::*; +use libc::*; + +/* OpenSSL 3.* only */ + +pub const OSSL_KEYMGMT_SELECT_PRIVATE_KEY: c_int = 0x01; +pub const OSSL_KEYMGMT_SELECT_PUBLIC_KEY: c_int = 0x02; +pub const OSSL_KEYMGMT_SELECT_DOMAIN_PARAMETERS: c_int = 0x04; +pub const OSSL_KEYMGMT_SELECT_OTHER_PARAMETERS: c_int = 0x80; +pub const OSSL_KEYMGMT_SELECT_ALL_PARAMETERS: c_int = + OSSL_KEYMGMT_SELECT_DOMAIN_PARAMETERS | OSSL_KEYMGMT_SELECT_OTHER_PARAMETERS; diff --git a/openssl-sys/src/evp.rs b/openssl-sys/src/evp.rs index 5fad4b977..ec0af3376 100644 --- a/openssl-sys/src/evp.rs +++ b/openssl-sys/src/evp.rs @@ -38,6 +38,15 @@ pub const EVP_CTRL_GCM_SET_IVLEN: c_int = 0x9; pub const EVP_CTRL_GCM_GET_TAG: c_int = 0x10; pub const EVP_CTRL_GCM_SET_TAG: c_int = 0x11; +#[cfg(ossl300)] +pub const EVP_PKEY_KEY_PARAMETERS: c_int = OSSL_KEYMGMT_SELECT_ALL_PARAMETERS; +#[cfg(ossl300)] +pub const EVP_PKEY_PRIVATE_KEY: c_int = EVP_PKEY_KEY_PARAMETERS | OSSL_KEYMGMT_SELECT_PRIVATE_KEY; +#[cfg(ossl300)] +pub const EVP_PKEY_PUBLIC_KEY: c_int = EVP_PKEY_KEY_PARAMETERS | OSSL_KEYMGMT_SELECT_PUBLIC_KEY; +#[cfg(ossl300)] +pub const EVP_PKEY_KEYPAIR: c_int = EVP_PKEY_PUBLIC_KEY | OSSL_KEYMGMT_SELECT_PRIVATE_KEY; + pub unsafe fn EVP_get_digestbynid(type_: c_int) -> *const EVP_MD { EVP_get_digestbyname(OBJ_nid2sn(type_)) } diff --git a/openssl-sys/src/handwritten/evp.rs b/openssl-sys/src/handwritten/evp.rs index a1be1da68..6fe190b3f 100644 --- a/openssl-sys/src/handwritten/evp.rs +++ b/openssl-sys/src/handwritten/evp.rs @@ -489,6 +489,27 @@ extern "C" { #[cfg(any(ossl110, libressl270))] pub fn EVP_PKEY_up_ref(pkey: *mut EVP_PKEY) -> c_int; + #[cfg(ossl300)] + pub fn EVP_PKEY_fromdata_init(ctx: *mut EVP_PKEY_CTX) -> c_int; + + #[cfg(ossl300)] + pub fn EVP_PKEY_fromdata( + ctx: *mut EVP_PKEY_CTX, + ppkey: *mut *mut EVP_PKEY, + selection: c_int, + param: *mut OSSL_PARAM, + ) -> c_int; + + #[cfg(ossl300)] + pub fn EVP_PKEY_todata( + ppkey: *const EVP_PKEY, + selection: c_int, + param: *mut *mut OSSL_PARAM, + ) -> c_int; + + #[cfg(ossl300)] + pub fn EVP_PKEY_generate(ctx: *mut EVP_PKEY_CTX, k: *mut *mut EVP_PKEY) -> c_int; + pub fn d2i_AutoPrivateKey( a: *mut *mut EVP_PKEY, pp: *mut *const c_uchar, @@ -535,6 +556,12 @@ extern "C" { pub fn EVP_PKEY_CTX_new(k: *mut EVP_PKEY, e: *mut ENGINE) -> *mut EVP_PKEY_CTX; pub fn EVP_PKEY_CTX_new_id(id: c_int, e: *mut ENGINE) -> *mut EVP_PKEY_CTX; + #[cfg(ossl300)] + pub fn EVP_PKEY_CTX_new_from_name( + libctx: *mut OSSL_LIB_CTX, + name: *const c_char, + propquery: *const c_char, + ) -> *mut EVP_PKEY_CTX; pub fn EVP_PKEY_CTX_free(ctx: *mut EVP_PKEY_CTX); pub fn EVP_PKEY_CTX_ctrl( diff --git a/openssl-sys/src/lib.rs b/openssl-sys/src/lib.rs index 0e8923bac..aa869a81a 100644 --- a/openssl-sys/src/lib.rs +++ b/openssl-sys/src/lib.rs @@ -69,6 +69,8 @@ mod openssl { pub use self::bio::*; pub use self::bn::*; pub use self::cms::*; + #[cfg(ossl300)] + pub use self::core_dispatch::*; pub use self::crypto::*; pub use self::dtls1::*; pub use self::ec::*; @@ -99,6 +101,8 @@ mod openssl { mod bio; mod bn; mod cms; + #[cfg(ossl300)] + mod core_dispatch; mod crypto; mod dtls1; mod ec; diff --git a/openssl/src/pkey_ctx.rs b/openssl/src/pkey_ctx.rs index aa39a0f9c..069136edb 100644 --- a/openssl/src/pkey_ctx.rs +++ b/openssl/src/pkey_ctx.rs @@ -67,6 +67,8 @@ let cmac_key = ctx.keygen().unwrap(); #[cfg(not(any(boringssl, awslc)))] use crate::cipher::CipherRef; use crate::error::ErrorStack; +#[cfg(ossl300)] +use crate::lib_ctx::LibCtxRef; use crate::md::MdRef; use crate::pkey::{HasPrivate, HasPublic, Id, PKey, PKeyRef, Private}; use crate::rsa::Padding; @@ -81,6 +83,8 @@ use openssl_macros::corresponds; use std::convert::TryFrom; #[cfg(ossl320)] use std::ffi::CStr; +#[cfg(ossl300)] +use std::ffi::CString; use std::ptr; /// HKDF modes of operation. @@ -156,6 +160,26 @@ impl PkeyCtx<()> { Ok(PkeyCtx::from_ptr(ptr)) } } + + /// Creates a new pkey context from the algorithm name. + #[corresponds(EVP_PKEY_CTX_new_from_name)] + #[cfg(ossl300)] + pub fn new_from_name( + libctx: Option<&LibCtxRef>, + name: &str, + propquery: Option<&str>, + ) -> Result { + unsafe { + let propquery = propquery.map(|s| CString::new(s).unwrap()); + let name = CString::new(name).unwrap(); + let ptr = cvt_p(ffi::EVP_PKEY_CTX_new_from_name( + libctx.map_or(ptr::null_mut(), ForeignTypeRef::as_ptr), + name.as_ptr(), + propquery.map_or(ptr::null_mut(), |s| s.as_ptr()), + ))?; + Ok(PkeyCtx::from_ptr(ptr)) + } + } } impl PkeyCtxRef @@ -756,6 +780,20 @@ impl PkeyCtxRef { Ok(()) } + /// Generates a new public/private keypair. + /// + /// New OpenSSL 3.0 function, that should do the same thing as keygen() + #[corresponds(EVP_PKEY_generate)] + #[cfg(ossl300)] + #[inline] + pub fn generate(&mut self) -> Result, ErrorStack> { + unsafe { + let mut key = ptr::null_mut(); + cvt(ffi::EVP_PKEY_generate(self.as_ptr(), &mut key))?; + Ok(PKey::from_ptr(key)) + } + } + /// Gets the nonce type for a private key context. /// /// The nonce for DSA and ECDSA can be either random (the default) or deterministic (as defined by RFC 6979). @@ -780,6 +818,14 @@ impl PkeyCtxRef { } Ok(NonceType(nonce_type)) } + + /// Initializes a conversion from `OsslParam` to `PKey` on given `PkeyCtx`. + #[corresponds(EVP_PKEY_fromdata_init)] + #[cfg(ossl300)] + pub fn fromdata_init(&mut self) -> Result<(), ErrorStack> { + unsafe { cvt(ffi::EVP_PKEY_fromdata_init(self.as_ptr()))? }; + Ok(()) + } } #[cfg(test)] @@ -1107,4 +1153,14 @@ mxJ7imIrEg9nIQ== assert_eq!(output, expected_output); assert!(ErrorStack::get().errors().is_empty()); } + + #[test] + #[cfg(ossl300)] + fn test_pkeyctx_from_name() { + let lib_ctx = crate::lib_ctx::LibCtx::new().unwrap(); + let _: PkeyCtx<()> = PkeyCtx::new_from_name(Some(lib_ctx.as_ref()), "RSA", None).unwrap(); + + /* no libctx is ok */ + let _: PkeyCtx<()> = PkeyCtx::new_from_name(None, "RSA", None).unwrap(); + } } From d26c91b19256ce5dc1307c09576cdae5f5d9848d Mon Sep 17 00:00:00 2001 From: Justus Winter Date: Mon, 14 Apr 2025 15:00:29 +0200 Subject: [PATCH 04/27] Expose encapsulation and decapsulation. --- openssl-sys/src/handwritten/evp.rs | 28 +++++++++ openssl/src/pkey_ctx.rs | 91 ++++++++++++++++++++++++++++++ 2 files changed, 119 insertions(+) diff --git a/openssl-sys/src/handwritten/evp.rs b/openssl-sys/src/handwritten/evp.rs index 6fe190b3f..66f556cc2 100644 --- a/openssl-sys/src/handwritten/evp.rs +++ b/openssl-sys/src/handwritten/evp.rs @@ -649,6 +649,34 @@ extern "C" { sig: *const c_uchar, siglen: size_t, ) -> c_int; + + #[cfg(ossl300)] + pub fn EVP_PKEY_encapsulate_init( + ctx: *mut EVP_PKEY_CTX, + params: *const OSSL_PARAM, + ) -> c_int; + #[cfg(ossl300)] + pub fn EVP_PKEY_encapsulate( + ctx: *mut EVP_PKEY_CTX, + wrappedkey: *mut c_uchar, + wrappedkeylen: *mut size_t, + genkey: *mut c_uchar, + genkeylen: *mut size_t, + ) -> c_int; + + #[cfg(ossl300)] + pub fn EVP_PKEY_decapsulate_init( + ctx: *mut EVP_PKEY_CTX, + params: *const OSSL_PARAM, + ) -> c_int; + #[cfg(ossl300)] + pub fn EVP_PKEY_decapsulate( + ctx: *mut EVP_PKEY_CTX, + genkey: *mut c_uchar, + genkeylen: *mut size_t, + wrappedkey: *const c_uchar, + wrappedkeylen: size_t, + ) -> c_int; } const_ptr_api! { diff --git a/openssl/src/pkey_ctx.rs b/openssl/src/pkey_ctx.rs index 069136edb..1bdd6d16e 100644 --- a/openssl/src/pkey_ctx.rs +++ b/openssl/src/pkey_ctx.rs @@ -197,6 +197,18 @@ where Ok(()) } + /// Prepares the context for encapsulateion using the public key. + #[corresponds(EVP_PKEY_encapsulate_init)] + #[inline] + pub fn encapsulate_init(&mut self) -> Result<(), ErrorStack> { + unsafe { + cvt(ffi::EVP_PKEY_encapsulate_init(self.as_ptr(), + ptr::null()))?; + } + + Ok(()) + } + /// Prepares the context for signature verification using the public key. #[corresponds(EVP_PKEY_verify_init)] #[inline] @@ -250,6 +262,43 @@ where Ok(len) } + /// Performs a public key encapsulation operation. + #[corresponds(EVP_PKEY_encapsulate)] + pub fn encapsulate(&mut self, wrappedkey: Option<&mut [u8]>, genkey: Option<&mut [u8]>) + -> Result<(usize, usize), ErrorStack> + { + let mut wrappedkey_len = wrappedkey.as_ref().map_or(0, |b| b.len()); + let mut genkey_len = genkey.as_ref().map_or(0, |b| b.len()); + unsafe { + cvt(ffi::EVP_PKEY_encapsulate( + self.as_ptr(), + wrappedkey.map_or(ptr::null_mut(), |b| b.as_mut_ptr()), + &mut wrappedkey_len, + genkey.map_or(ptr::null_mut(), |b| b.as_mut_ptr()), + &mut genkey_len, + ))?; + } + + Ok((wrappedkey_len, genkey_len)) + } + + /// Like [`Self::encapsulate`] but appends ciphertext and key to a [`Vec`]. + pub fn encapsulate_to_vec(&mut self, wrappedkey: &mut Vec, genkey: &mut Vec) + -> Result<(usize, usize), ErrorStack> + { + let wrappedkey_base = wrappedkey.len(); + let genkey_base = genkey.len(); + let (wrappedkey_len, genkey_len) = self.encapsulate(None, None)?; + wrappedkey.resize(wrappedkey_base + wrappedkey_len, 0); + genkey.resize(genkey_base + genkey_len, 0); + let (wrappedkey_len, genkey_len) = + self.encapsulate(Some(&mut wrappedkey[wrappedkey_base..]), + Some(&mut genkey[genkey_base..]))?; + wrappedkey.truncate(wrappedkey_base + wrappedkey_len); + genkey.truncate(genkey_base + genkey_len); + Ok((wrappedkey_len, genkey_len)) + } + /// Verifies the signature of data using the public key. /// /// Returns `Ok(true)` if the signature is valid, `Ok(false)` if the signature is invalid, and `Err` if an error @@ -329,6 +378,17 @@ where Ok(()) } + /// Prepares the context for decapsulation using the private key. + #[corresponds(EVP_PKEY_decapsulate_init)] + #[inline] + pub fn decapsulate_init(&mut self) -> Result<(), ErrorStack> { + unsafe { + cvt(ffi::EVP_PKEY_decapsulate_init(self.as_ptr(), ptr::null()))?; + } + + Ok(()) + } + /// Prepares the context for signing using the private key. #[corresponds(EVP_PKEY_sign_init)] #[inline] @@ -384,6 +444,37 @@ where Ok(len) } + /// Performs a decapsulation operation using the private key. + #[corresponds(EVP_PKEY_decapsulate)] + pub fn decapsulate(&mut self, from: &[u8], to: Option<&mut [u8]>) + -> Result + { + let mut written = to.as_ref().map_or(0, |b| b.len()); + unsafe { + cvt(ffi::EVP_PKEY_decapsulate( + self.as_ptr(), + to.map_or(ptr::null_mut(), |b| b.as_mut_ptr()), + &mut written, + from.as_ptr(), + from.len(), + ))?; + } + + Ok(written) + } + + /// Like [`Self::decapsulate`] but appends plaintext to a [`Vec`]. + pub fn decapsulate_to_vec(&mut self, from: &[u8], out: &mut Vec) + -> Result + { + let base = out.len(); + let len = self.decapsulate(from, None)?; + out.resize(base + len, 0); + let len = self.decapsulate(from, Some(&mut out[base..]))?; + out.truncate(base + len); + Ok(len) + } + /// Signs the contents of `data`. /// /// If `sig` is set to `None`, an upper bound on the number of bytes required for the output buffer will be From a3984e7b25121ed9594909b7b4175f843b1a7465 Mon Sep 17 00:00:00 2001 From: Justus Winter Date: Mon, 14 Apr 2025 15:03:12 +0200 Subject: [PATCH 05/27] Add support for ML-KEM. See #2393. --- openssl/build.rs | 4 + openssl/src/lib.rs | 2 + openssl/src/pkey_ml_kem.rs | 263 +++++++++++++++++++++++++++++++++++++ 3 files changed, 269 insertions(+) create mode 100644 openssl/src/pkey_ml_kem.rs diff --git a/openssl/build.rs b/openssl/build.rs index d6d65798f..8c6a3a6f8 100644 --- a/openssl/build.rs +++ b/openssl/build.rs @@ -46,6 +46,7 @@ fn main() { println!("cargo:rustc-check-cfg=cfg(ossl310)"); println!("cargo:rustc-check-cfg=cfg(ossl320)"); println!("cargo:rustc-check-cfg=cfg(ossl330)"); + println!("cargo:rustc-check-cfg=cfg(ossl350)"); if env::var("DEP_OPENSSL_LIBRESSL").is_ok() { println!("cargo:rustc-cfg=libressl"); @@ -169,5 +170,8 @@ fn main() { if version >= 0x3_03_00_00_0 { println!("cargo:rustc-cfg=ossl330"); } + if version >= 0x3_05_00_00_0 { + println!("cargo:rustc-cfg=ossl350"); + } } } diff --git a/openssl/src/lib.rs b/openssl/src/lib.rs index 5ae46337e..c12f05a0e 100644 --- a/openssl/src/lib.rs +++ b/openssl/src/lib.rs @@ -185,6 +185,8 @@ pub mod pkcs5; pub mod pkcs7; pub mod pkey; pub mod pkey_ctx; +#[cfg(ossl350)] +pub mod pkey_ml_kem; #[cfg(ossl300)] pub mod provider; pub mod rand; diff --git a/openssl/src/pkey_ml_kem.rs b/openssl/src/pkey_ml_kem.rs new file mode 100644 index 000000000..59d25339b --- /dev/null +++ b/openssl/src/pkey_ml_kem.rs @@ -0,0 +1,263 @@ +//! Module-Lattice-Based Key-Encapsulation Mechanism. +//! +//! ML-KEM is a Key-Encapsulation Mechanism that is believed to be +//! secure against adversaries with quantum computers. It has been +//! standardized by NIST as [FIPS 203]. +//! +//! [FIPS 203]: https://csrc.nist.gov/pubs/fips/203/final + +use foreign_types::ForeignType; +use libc::c_int; +use std::ptr; + +use crate::error::ErrorStack; +use crate::ossl_param::{OsslParam, OsslParamBuilder}; +use crate::pkey::{PKey, Private, Public}; +use crate::pkey_ctx::PkeyCtx; +use crate::{cvt, cvt_p}; +use openssl_macros::corresponds; + +const OSSL_PKEY_PARAM_SEED: &[u8; 5] = b"seed\0"; +const OSSL_PKEY_PARAM_PUB_KEY: &[u8; 4] = b"pub\0"; +const OSSL_PKEY_PARAM_PRIV_KEY: &[u8; 5] = b"priv\0"; + +#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, PartialOrd, Ord)] +pub enum Variant { + MlKem512, + MlKem768, + MlKem1024, +} + +impl Variant { + pub(crate) fn as_str(&self) -> &'static str { + match self { + Variant::MlKem512 => "ML-KEM-512", + Variant::MlKem768 => "ML-KEM-768", + Variant::MlKem1024 => "ML-KEM-1024", + } + } +} + +pub struct PKeyMlKemBuilder { + bld: OsslParamBuilder, + variant: Variant, + _m: ::std::marker::PhantomData, +} + +impl PKeyMlKemBuilder { + /// Creates a new `PKeyMlKemBuilder` to build ML-KEM private or + /// public keys. + pub fn new( + variant: Variant, + public: &[u8], + private: Option<&[u8]>, + ) -> Result, ErrorStack> { + let bld = OsslParamBuilder::new()?; + bld.add_octet_string(OSSL_PKEY_PARAM_PUB_KEY, public)?; + if let Some(private) = private { + bld.add_octet_string(OSSL_PKEY_PARAM_PRIV_KEY, private)? + }; + Ok(PKeyMlKemBuilder:: { + bld, + variant, + _m: ::std::marker::PhantomData, + }) + } + + /// Creates a new `PKeyMlKemBuilder` to build ML-KEM private keys + /// from a seed. + pub fn from_seed( + variant: Variant, + seed: &[u8], + ) -> Result, ErrorStack> { + let bld = OsslParamBuilder::new()?; + bld.add_octet_string(OSSL_PKEY_PARAM_SEED, seed)?; + Ok(PKeyMlKemBuilder:: { + bld, + variant, + _m: ::std::marker::PhantomData, + }) + } + + /// Build PKey. Internal. + #[corresponds(EVP_PKEY_fromdata)] + fn build_internal(self, selection: c_int) -> Result, ErrorStack> { + let mut ctx = PkeyCtx::new_from_name( + None, self.variant.as_str(), None)?; + ctx.fromdata_init()?; + let params = self.bld.to_param()?; + unsafe { + let evp = cvt_p(ffi::EVP_PKEY_new())?; + let pkey = PKey::from_ptr(evp); + cvt(ffi::EVP_PKEY_fromdata( + ctx.as_ptr(), + &mut pkey.as_ptr(), + selection, + params.as_ptr(), + ))?; + Ok(pkey) + } + } +} + +impl PKeyMlKemBuilder { + /// Returns the Private ML-KEM PKey from the provided parameters. + #[corresponds(EVP_PKEY_fromdata)] + pub fn build(self) -> Result, ErrorStack> { + self.build_internal(ffi::EVP_PKEY_KEYPAIR) + } + + /// Creates a new `PKeyRsaBuilder` to generate a new ML-KEM key + /// pair. + pub fn new_generate(variant: Variant) + -> Result, ErrorStack> + { + let bld = OsslParamBuilder::new()?; + Ok(PKeyMlKemBuilder:: { + bld, + variant, + _m: ::std::marker::PhantomData, + }) + } + + /// Generate an ML-KEM PKey. + pub fn generate(self) -> Result, ErrorStack> { + let mut ctx = PkeyCtx::new_from_name( + None, self.variant.as_str(), None)?; + ctx.keygen_init()?; + let params = self.bld.to_param()?; + unsafe { + cvt(ffi::EVP_PKEY_CTX_set_params(ctx.as_ptr(), params.as_ptr()))?; + } + ctx.generate() + } +} + +impl PKeyMlKemBuilder { + /// Returns the Public ML-KEM PKey from the provided parameters. + #[corresponds(EVP_PKEY_fromdata)] + pub fn build(self) -> Result, ErrorStack> { + self.build_internal(ffi::EVP_PKEY_PUBLIC_KEY) + } +} + +pub struct PKeyMlKemParams { + params: OsslParam, + _m: ::std::marker::PhantomData, +} + +impl PKeyMlKemParams { + /// Creates a new `PKeyMlKemParams` from existing ML-KEM PKey. Internal. + #[corresponds(EVP_PKEY_todata)] + fn _new_from_pkey(pkey: &PKey, selection: c_int) + -> Result, ErrorStack> + { + unsafe { + let mut params: *mut ffi::OSSL_PARAM = ptr::null_mut(); + cvt(ffi::EVP_PKEY_todata(pkey.as_ptr(), selection, &mut params))?; + Ok(PKeyMlKemParams:: { + params: OsslParam::from_ptr(params), + _m: ::std::marker::PhantomData, + }) + } + } + + /// Returns a reference to the public key. + pub fn public_key(&self) -> Result<&[u8], ErrorStack> { + self.params + .locate(OSSL_PKEY_PARAM_PUB_KEY)? + .get_octet_string() + } +} + +impl PKeyMlKemParams { + /// Creates a new `PKeyMlKemParams` from existing Public ML-KEM PKey. + #[corresponds(EVP_PKEY_todata)] + pub fn from_pkey(pkey: &PKey) -> Result, ErrorStack> { + Self::_new_from_pkey(pkey, ffi::EVP_PKEY_PUBLIC_KEY) + } +} + +impl PKeyMlKemParams { + /// Creates a new `PKeyMlKemParams` from existing Private ML-KEM PKey. + #[corresponds(EVP_PKEY_todata)] + pub fn from_pkey(pkey: &PKey) -> Result, ErrorStack> { + Self::_new_from_pkey(pkey, ffi::EVP_PKEY_KEYPAIR) + } + + /// Returns the private key seed. + pub fn private_key_seed(&self) -> Result<&[u8], ErrorStack> { + self.params.locate(OSSL_PKEY_PARAM_SEED)?.get_octet_string() + } + + /// Returns the private key. + pub fn private_key(&self) -> Result<&[u8], ErrorStack> { + self.params.locate(OSSL_PKEY_PARAM_PRIV_KEY)?.get_octet_string() + } +} + +#[cfg(test)] +mod tests { + + use super::*; + + #[test] + fn test_generate_ml_kem_512() { + test_generate(Variant::MlKem512); + } + + #[test] + fn test_generate_ml_kem_768() { + test_generate(Variant::MlKem768); + } + + #[test] + fn test_generate_ml_kem_1024() { + test_generate(Variant::MlKem1024); + } + + fn test_generate(variant: Variant) { + let bld = PKeyMlKemBuilder::::new_generate(variant).unwrap(); + let key = bld.generate().unwrap(); + + // Encapsulate with the original PKEY. + let (mut wrappedkey, mut genkey0) = (vec![], vec![]); + let mut ctx = PkeyCtx::new(&key).unwrap(); + ctx.encapsulate_init().unwrap(); + ctx.encapsulate_to_vec(&mut wrappedkey, &mut genkey0).unwrap(); + + let mut genkey1 = vec![]; + let mut ctx = PkeyCtx::new(&key).unwrap(); + ctx.decapsulate_init().unwrap(); + ctx.decapsulate_to_vec(&wrappedkey, &mut genkey1).unwrap(); + + assert_eq!(genkey0, genkey1); + + // Encapsulate with a PKEY derived from the public parameters. + let public_params = + PKeyMlKemParams::::from_pkey(&key).unwrap(); + let key_pub = PKeyMlKemBuilder::::new( + variant, public_params.public_key().unwrap(), None).unwrap() + .build().unwrap(); + + let (mut wrappedkey, mut genkey0) = (vec![], vec![]); + let mut ctx = PkeyCtx::new(&key_pub).unwrap(); + ctx.encapsulate_init().unwrap(); + ctx.encapsulate_to_vec(&mut wrappedkey, &mut genkey0).unwrap(); + + let mut genkey1 = vec![]; + let mut ctx = PkeyCtx::new(&key).unwrap(); + ctx.decapsulate_init().unwrap(); + ctx.decapsulate_to_vec(&wrappedkey, &mut genkey1).unwrap(); + + assert_eq!(genkey0, genkey1); + + // Note that we can get the public parameter from the + // PKeyMlKemParams:: as well. The same is not true + // for ML-DSA, for example. + let private_params = + PKeyMlKemParams::::from_pkey(&key).unwrap(); + assert_eq!(public_params.public_key().unwrap(), + private_params.public_key().unwrap()); + } +} From f60f7b7b503725975b76e8968a5b1d041880b2f4 Mon Sep 17 00:00:00 2001 From: Justus Winter Date: Wed, 23 Apr 2025 13:44:10 +0200 Subject: [PATCH 06/27] Expose message signing and verification. --- openssl-sys/src/handwritten/evp.rs | 27 +++++++++++++ openssl-sys/src/types.rs | 6 +++ openssl/src/lib.rs | 2 + openssl/src/pkey_ctx.rs | 30 +++++++++++++++ openssl/src/signature.rs | 62 ++++++++++++++++++++++++++++++ 5 files changed, 127 insertions(+) create mode 100644 openssl/src/signature.rs diff --git a/openssl-sys/src/handwritten/evp.rs b/openssl-sys/src/handwritten/evp.rs index 66f556cc2..9e7987350 100644 --- a/openssl-sys/src/handwritten/evp.rs +++ b/openssl-sys/src/handwritten/evp.rs @@ -610,6 +610,12 @@ extern "C" { pub fn EVP_PKEY_keygen(ctx: *mut EVP_PKEY_CTX, key: *mut *mut EVP_PKEY) -> c_int; pub fn EVP_PKEY_sign_init(ctx: *mut EVP_PKEY_CTX) -> c_int; + + #[cfg(ossl340)] + pub fn EVP_PKEY_sign_message_init(ctx: *mut EVP_PKEY_CTX, + algo: *mut EVP_SIGNATURE, + params: *const OSSL_PARAM) -> c_int; + pub fn EVP_PKEY_sign( ctx: *mut EVP_PKEY_CTX, sig: *mut c_uchar, @@ -618,6 +624,12 @@ extern "C" { tbslen: size_t, ) -> c_int; pub fn EVP_PKEY_verify_init(ctx: *mut EVP_PKEY_CTX) -> c_int; + + #[cfg(ossl340)] + pub fn EVP_PKEY_verify_message_init(ctx: *mut EVP_PKEY_CTX, + algo: *mut EVP_SIGNATURE, + params: *const OSSL_PARAM) -> c_int; + pub fn EVP_PKEY_verify( ctx: *mut EVP_PKEY_CTX, sig: *const c_uchar, @@ -718,3 +730,18 @@ extern "C" { pub fn EVP_EncodeBlock(dst: *mut c_uchar, src: *const c_uchar, src_len: c_int) -> c_int; pub fn EVP_DecodeBlock(dst: *mut c_uchar, src: *const c_uchar, src_len: c_int) -> c_int; } + +cfg_if! { + if #[cfg(ossl300)] { + extern "C" { + pub fn EVP_SIGNATURE_free(s: *mut EVP_SIGNATURE); + pub fn EVP_SIGNATURE_up_ref(s: *mut EVP_SIGNATURE) -> c_int; + pub fn EVP_SIGNATURE_fetch(ctx: *mut OSSL_LIB_CTX, + algorithm: *const c_char, + properties: *const c_char) + -> *mut EVP_SIGNATURE; + pub fn EVP_SIGNATURE_get0_name(s: *const EVP_SIGNATURE) -> *const c_char; + pub fn EVP_SIGNATURE_get0_description(s: *const EVP_SIGNATURE) -> *const c_char; + } + } +} diff --git a/openssl-sys/src/types.rs b/openssl-sys/src/types.rs index 10c8f6771..76f94809f 100644 --- a/openssl-sys/src/types.rs +++ b/openssl-sys/src/types.rs @@ -19,3 +19,9 @@ cfg_if! { } } } + +cfg_if! { + if #[cfg(ossl300)] { + pub enum EVP_SIGNATURE {} + } +} diff --git a/openssl/src/lib.rs b/openssl/src/lib.rs index c12f05a0e..7d71275dc 100644 --- a/openssl/src/lib.rs +++ b/openssl/src/lib.rs @@ -193,6 +193,8 @@ pub mod rand; pub mod rsa; pub mod sha; pub mod sign; +#[cfg(ossl300)] +pub mod signature; pub mod srtp; pub mod ssl; pub mod stack; diff --git a/openssl/src/pkey_ctx.rs b/openssl/src/pkey_ctx.rs index 1bdd6d16e..ebd31e249 100644 --- a/openssl/src/pkey_ctx.rs +++ b/openssl/src/pkey_ctx.rs @@ -220,6 +220,22 @@ where Ok(()) } + /// Prepares the context for signature verification over a message + /// using the public key. + #[corresponds(EVP_PKEY_verify_message_init)] + #[inline] + pub fn verify_message_init(&mut self, + algo: &mut crate::signature::Signature) + -> Result<(), ErrorStack> + { + unsafe { + cvt(ffi::EVP_PKEY_verify_message_init(self.as_ptr(), algo.as_ptr(), + ptr::null()))?; + } + + Ok(()) + } + /// Prepares the context for signature recovery using the public key. #[corresponds(EVP_PKEY_verify_recover_init)] #[inline] @@ -400,6 +416,20 @@ where Ok(()) } + /// Prepares the context for signing a message using the private key. + #[corresponds(EVP_PKEY_sign_message_init)] + #[inline] + pub fn sign_message_init(&mut self, algo: &mut crate::signature::Signature) + -> Result<(), ErrorStack> + { + unsafe { + cvt(ffi::EVP_PKEY_sign_message_init(self.as_ptr(), algo.as_ptr(), + ptr::null()))?; + } + + Ok(()) + } + /// Sets the peer key used for secret derivation. #[corresponds(EVP_PKEY_derive_set_peer)] pub fn derive_set_peer(&mut self, key: &PKeyRef) -> Result<(), ErrorStack> diff --git a/openssl/src/signature.rs b/openssl/src/signature.rs new file mode 100644 index 000000000..4ed4069df --- /dev/null +++ b/openssl/src/signature.rs @@ -0,0 +1,62 @@ +//! Wraps `EVP_SIGNATURE` objects. + +use crate::error::ErrorStack; +use crate::cvt_p; +use foreign_types::{ForeignType, ForeignTypeRef}; +use openssl_macros::corresponds; +use std::ffi::CStr; +use std::fmt; +use std::ptr; + +foreign_type_and_impl_send_sync! { + type CType = ffi::EVP_SIGNATURE; + fn drop = ffi::EVP_SIGNATURE_free; + + /// A signature algorithm. + pub struct Signature; + + /// Reference to `Signature`. + pub struct SignatureRef; +} + +impl ToOwned for SignatureRef { + type Owned = Signature; + + fn to_owned(&self) -> Signature { + unsafe { + ffi::EVP_SIGNATURE_up_ref(self.as_ptr()); + Signature::from_ptr(self.as_ptr()) + } + } +} + +impl SignatureRef { + /// Returns the name of the signature algorithm. + #[corresponds(EVP_SIGNATURE_get0_name)] + pub fn name(&self) -> &str { + unsafe { + CStr::from_ptr(ffi::EVP_SIGNATURE_get0_name(self.as_ptr())) + }.to_str().expect("identifier to be in UTF8") + } + + /// Returns a human-readable description of the signature + /// algorithm. + #[corresponds(EVP_SIGNATURE_get0_description)] + pub fn description(&self) -> &str { + unsafe { + CStr::from_ptr(ffi::EVP_SIGNATURE_get0_description(self.as_ptr())) + }.to_str().expect("description to be in UTF8") + } +} + +impl fmt::Debug for Signature { + fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result { + fmt.write_str(self.description()) + } +} + +impl Clone for Signature { + fn clone(&self) -> Signature { + SignatureRef::to_owned(self) + } +} From 28647589376b90e5931a2bd3e5a1af236e63704d Mon Sep 17 00:00:00 2001 From: Justus Winter Date: Wed, 23 Apr 2025 13:45:27 +0200 Subject: [PATCH 07/27] Add support for ML-DSA. See #2393. --- openssl/src/lib.rs | 2 + openssl/src/pkey_ml_dsa.rs | 279 +++++++++++++++++++++++++++++++++++++ openssl/src/signature.rs | 25 ++++ 3 files changed, 306 insertions(+) create mode 100644 openssl/src/pkey_ml_dsa.rs diff --git a/openssl/src/lib.rs b/openssl/src/lib.rs index 7d71275dc..e6b0e85a0 100644 --- a/openssl/src/lib.rs +++ b/openssl/src/lib.rs @@ -186,6 +186,8 @@ pub mod pkcs7; pub mod pkey; pub mod pkey_ctx; #[cfg(ossl350)] +pub mod pkey_ml_dsa; +#[cfg(ossl350)] pub mod pkey_ml_kem; #[cfg(ossl300)] pub mod provider; diff --git a/openssl/src/pkey_ml_dsa.rs b/openssl/src/pkey_ml_dsa.rs new file mode 100644 index 000000000..04071c380 --- /dev/null +++ b/openssl/src/pkey_ml_dsa.rs @@ -0,0 +1,279 @@ +//! Module-Lattice-Based Digital Signatures. +//! +//! ML-DSA is a signature algorithm that is believed to be secure +//! against adversaries with quantum computers. It has been +//! standardized by NIST as [FIPS 204]. +//! +//! [FIPS 204]: https://csrc.nist.gov/pubs/fips/204/final + +use std::ffi::CStr; + +use foreign_types::ForeignType; +use libc::c_int; +use std::ptr; + +use crate::error::ErrorStack; +use crate::ossl_param::{OsslParam, OsslParamBuilder}; +use crate::pkey::{PKey, Private, Public}; +use crate::pkey_ctx::PkeyCtx; +use crate::{cvt, cvt_p}; +use openssl_macros::corresponds; + +const OSSL_PKEY_PARAM_SEED: &[u8; 5] = b"seed\0"; +const OSSL_PKEY_PARAM_PUB_KEY: &[u8; 4] = b"pub\0"; +const OSSL_PKEY_PARAM_PRIV_KEY: &[u8; 5] = b"priv\0"; + +#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, PartialOrd, Ord)] +pub enum Variant { + MlDsa44, + MlDsa65, + MlDsa87, +} + +impl Variant { + pub(crate) fn as_str(&self) -> &'static str { + match self { + Variant::MlDsa44 => "ML-DSA-44", + Variant::MlDsa65 => "ML-DSA-65", + Variant::MlDsa87 => "ML-DSA-87", + } + } + + pub(crate) fn as_cstr(&self) -> &'static CStr { + match self { + Variant::MlDsa44 => CStr::from_bytes_with_nul(b"ML-DSA-44\0"), + Variant::MlDsa65 => CStr::from_bytes_with_nul(b"ML-DSA-65\0"), + Variant::MlDsa87 => CStr::from_bytes_with_nul(b"ML-DSA-87\0"), + }.unwrap() + } +} + +pub struct PKeyMlDsaBuilder { + bld: OsslParamBuilder, + variant: Variant, + _m: ::std::marker::PhantomData, +} + +impl PKeyMlDsaBuilder { + /// Creates a new `PKeyMlDsaBuilder` to build ML-DSA private or + /// public keys. + pub fn new( + variant: Variant, + public: &[u8], + private: Option<&[u8]>, + ) -> Result, ErrorStack> { + let bld = OsslParamBuilder::new()?; + bld.add_octet_string(OSSL_PKEY_PARAM_PUB_KEY, public)?; + if let Some(private) = private { + bld.add_octet_string(OSSL_PKEY_PARAM_PRIV_KEY, private)? + }; + Ok(PKeyMlDsaBuilder:: { + bld, + variant, + _m: ::std::marker::PhantomData, + }) + } + + /// Creates a new `PKeyMlDsaBuilder` to build ML-DSA private keys + /// from a seed. + pub fn from_seed( + variant: Variant, + seed: &[u8], + ) -> Result, ErrorStack> { + let bld = OsslParamBuilder::new()?; + bld.add_octet_string(OSSL_PKEY_PARAM_SEED, seed)?; + Ok(PKeyMlDsaBuilder:: { + bld, + variant, + _m: ::std::marker::PhantomData, + }) + } + + /// Build PKey. Internal. + #[corresponds(EVP_PKEY_fromdata)] + fn build_internal(self, selection: c_int) + -> Result, ErrorStack> + { + let mut ctx = PkeyCtx::new_from_name( + None, self.variant.as_str(), None)?; + ctx.fromdata_init()?; + let params = self.bld.to_param()?; + unsafe { + let evp = cvt_p(ffi::EVP_PKEY_new())?; + let pkey = PKey::from_ptr(evp); + cvt(ffi::EVP_PKEY_fromdata( + ctx.as_ptr(), + &mut pkey.as_ptr(), + selection, + params.as_ptr(), + ))?; + Ok(pkey) + } + } +} + +impl PKeyMlDsaBuilder { + /// Returns the Private ML-DSA PKey from the provided parameters. + #[corresponds(EVP_PKEY_fromdata)] + pub fn build(self) -> Result, ErrorStack> { + self.build_internal(ffi::EVP_PKEY_KEYPAIR) + } + + /// Creates a new `PKeyRsaBuilder` to generate a new ML-DSA key + /// pair. + pub fn new_generate(variant: Variant) + -> Result, ErrorStack> + { + let bld = OsslParamBuilder::new()?; + Ok(PKeyMlDsaBuilder:: { + bld, + variant, + _m: ::std::marker::PhantomData, + }) + } + + /// Generate an ML-DSA PKey. + pub fn generate(self) -> Result, ErrorStack> { + let mut ctx = PkeyCtx::new_from_name( + None, self.variant.as_str(), None)?; + ctx.keygen_init()?; + let params = self.bld.to_param()?; + unsafe { + cvt(ffi::EVP_PKEY_CTX_set_params(ctx.as_ptr(), params.as_ptr()))?; + } + ctx.generate() + } +} + +impl PKeyMlDsaBuilder { + /// Returns the Public ML-DSA PKey from the provided parameters. + #[corresponds(EVP_PKEY_fromdata)] + pub fn build(self) -> Result, ErrorStack> { + self.build_internal(ffi::EVP_PKEY_PUBLIC_KEY) + } +} + +pub struct PKeyMlDsaParams { + params: OsslParam, + _m: ::std::marker::PhantomData, +} + +impl PKeyMlDsaParams { + /// Creates a new `PKeyMlDsaParams` from existing ECDSA PKey. Internal. + #[corresponds(EVP_PKEY_todata)] + fn _new_from_pkey(pkey: &PKey, selection: c_int) + -> Result, ErrorStack> + { + unsafe { + let mut params: *mut ffi::OSSL_PARAM = ptr::null_mut(); + cvt(ffi::EVP_PKEY_todata(pkey.as_ptr(), selection, &mut params))?; + Ok(PKeyMlDsaParams:: { + params: OsslParam::from_ptr(params), + _m: ::std::marker::PhantomData, + }) + } + } +} + +impl PKeyMlDsaParams { + /// Creates a new `PKeyMlDsaParams` from existing Public ECDSA PKey. + #[corresponds(EVP_PKEY_todata)] + pub fn from_pkey(pkey: &PKey) -> Result, ErrorStack> { + Self::_new_from_pkey(pkey, ffi::EVP_PKEY_PUBLIC_KEY) + } + + /// Returns a reference to the public key. + pub fn public_key(&self) -> Result<&[u8], ErrorStack> { + self.params + .locate(OSSL_PKEY_PARAM_PUB_KEY).unwrap() + .get_octet_string() + } +} + +impl PKeyMlDsaParams { + /// Creates a new `PKeyMlDsaParams` from existing Private ECDSA PKey. + #[corresponds(EVP_PKEY_todata)] + pub fn from_pkey(pkey: &PKey) -> Result, ErrorStack> { + Self::_new_from_pkey(pkey, ffi::EVP_PKEY_KEYPAIR) + } + + /// Returns the private key seed. + pub fn private_key_seed(&self) -> Result<&[u8], ErrorStack> { + self.params.locate(OSSL_PKEY_PARAM_SEED)?.get_octet_string() + } + + /// Returns the private key. + pub fn private_key(&self) -> Result<&[u8], ErrorStack> { + self.params.locate(OSSL_PKEY_PARAM_PRIV_KEY)?.get_octet_string() + } +} + +#[cfg(test)] +mod tests { + + use super::*; + use crate::signature::Signature; + + #[test] + fn test_generate_ml_dsa_44() { + test_generate(Variant::MlDsa44); + } + + #[test] + fn test_generate_ml_dsa_65() { + test_generate(Variant::MlDsa65); + } + + #[test] + fn test_generate_ml_dsa_87() { + test_generate(Variant::MlDsa87); + } + + fn test_generate(variant: Variant) { + let bld = PKeyMlDsaBuilder::::new_generate(variant).unwrap(); + let key = bld.generate().unwrap(); + + let mut algo = Signature::for_ml_dsa(variant).unwrap(); + + let data = b"Some Crypto Text"; + let bad_data = b"Some Crypto text"; + + let mut signature = vec![]; + let mut ctx = PkeyCtx::new(&key).unwrap(); + ctx.sign_message_init(&mut algo).unwrap(); + ctx.sign_to_vec(&data[..], &mut signature).unwrap(); + + // Verify good version with the original PKEY. + ctx.verify_message_init(&mut algo).unwrap(); + let valid = ctx.verify(&data[..], &signature); + assert!(matches!(valid, Ok(true))); + assert!(ErrorStack::get().errors().is_empty()); + + // Verify bad version with the original PKEY. + ctx.verify_message_init(&mut algo).unwrap(); + let valid = ctx.verify(&bad_data[..], &signature); + assert!(matches!(valid, Ok(false) | Err(_))); + assert!(ErrorStack::get().errors().is_empty()); + + // Derive a new PKEY with only the public bits. + let public_params = + PKeyMlDsaParams::::from_pkey(&key).unwrap(); + let key_pub = PKeyMlDsaBuilder::::new( + variant, public_params.public_key().unwrap(), None).unwrap() + .build().unwrap(); + let mut ctx = PkeyCtx::new(&key_pub).unwrap(); + let mut algo = Signature::for_ml_dsa(variant).unwrap(); + + // Verify good version with the public PKEY. + ctx.verify_message_init(&mut algo).unwrap(); + let valid = ctx.verify(&data[..], &signature); + assert!(matches!(valid, Ok(true))); + assert!(ErrorStack::get().errors().is_empty()); + + // Verify bad version with the public PKEY. + ctx.verify_message_init(&mut algo).unwrap(); + let valid = ctx.verify(&bad_data[..], &signature); + assert!(matches!(valid, Ok(false) | Err(_))); + assert!(ErrorStack::get().errors().is_empty()); + } +} diff --git a/openssl/src/signature.rs b/openssl/src/signature.rs index 4ed4069df..7afa4d9a9 100644 --- a/openssl/src/signature.rs +++ b/openssl/src/signature.rs @@ -60,3 +60,28 @@ impl Clone for Signature { SignatureRef::to_owned(self) } } + +impl Signature { + /// Creates a new `Signature` for use with ML-DSA. + pub fn for_ml_dsa(variant: crate::pkey_ml_dsa::Variant) + -> Result + { + unsafe { + Ok(Signature(cvt_p(ffi::EVP_SIGNATURE_fetch( + ptr::null_mut(), variant.as_cstr().as_ptr(), ptr::null()))?)) + } + } +} + +#[cfg(test)] +mod tests { + + use super::*; + + #[test] + fn test_alloc_free() { + let sig = + Signature::for_ml_dsa(crate::pkey_ml_dsa::Variant::MlDsa44).unwrap(); + drop(sig); + } +} From f87f6ff7722cd4430256fbb48e0263062c66e225 Mon Sep 17 00:00:00 2001 From: Justus Winter Date: Mon, 12 May 2025 17:10:33 +0200 Subject: [PATCH 08/27] fixup! Add support for ML-DSA. --- openssl/src/signature.rs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/openssl/src/signature.rs b/openssl/src/signature.rs index 7afa4d9a9..efd353a3b 100644 --- a/openssl/src/signature.rs +++ b/openssl/src/signature.rs @@ -63,6 +63,7 @@ impl Clone for Signature { impl Signature { /// Creates a new `Signature` for use with ML-DSA. + #[cfg(ossl350)] pub fn for_ml_dsa(variant: crate::pkey_ml_dsa::Variant) -> Result { @@ -78,6 +79,7 @@ mod tests { use super::*; + #[cfg(ossl350)] #[test] fn test_alloc_free() { let sig = From 176a99d6fe6c4b5e7109808c919613a399640357 Mon Sep 17 00:00:00 2001 From: Justus Winter Date: Mon, 12 May 2025 17:11:01 +0200 Subject: [PATCH 09/27] fixup! Expose encapsulation and decapsulation. --- openssl/src/pkey_ctx.rs | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/openssl/src/pkey_ctx.rs b/openssl/src/pkey_ctx.rs index ebd31e249..f0bfbdcc5 100644 --- a/openssl/src/pkey_ctx.rs +++ b/openssl/src/pkey_ctx.rs @@ -198,6 +198,7 @@ where } /// Prepares the context for encapsulateion using the public key. + #[cfg(ossl300)] #[corresponds(EVP_PKEY_encapsulate_init)] #[inline] pub fn encapsulate_init(&mut self) -> Result<(), ErrorStack> { @@ -279,6 +280,7 @@ where } /// Performs a public key encapsulation operation. + #[cfg(ossl300)] #[corresponds(EVP_PKEY_encapsulate)] pub fn encapsulate(&mut self, wrappedkey: Option<&mut [u8]>, genkey: Option<&mut [u8]>) -> Result<(usize, usize), ErrorStack> @@ -299,6 +301,7 @@ where } /// Like [`Self::encapsulate`] but appends ciphertext and key to a [`Vec`]. + #[cfg(ossl300)] pub fn encapsulate_to_vec(&mut self, wrappedkey: &mut Vec, genkey: &mut Vec) -> Result<(usize, usize), ErrorStack> { @@ -395,6 +398,7 @@ where } /// Prepares the context for decapsulation using the private key. + #[cfg(ossl300)] #[corresponds(EVP_PKEY_decapsulate_init)] #[inline] pub fn decapsulate_init(&mut self) -> Result<(), ErrorStack> { @@ -475,6 +479,7 @@ where } /// Performs a decapsulation operation using the private key. + #[cfg(ossl300)] #[corresponds(EVP_PKEY_decapsulate)] pub fn decapsulate(&mut self, from: &[u8], to: Option<&mut [u8]>) -> Result @@ -494,6 +499,7 @@ where } /// Like [`Self::decapsulate`] but appends plaintext to a [`Vec`]. + #[cfg(ossl300)] pub fn decapsulate_to_vec(&mut self, from: &[u8], out: &mut Vec) -> Result { From c0c1dc236dbec339a467a0ce8519443dc64c38e6 Mon Sep 17 00:00:00 2001 From: Justus Winter Date: Mon, 12 May 2025 17:11:13 +0200 Subject: [PATCH 10/27] fixup! Expose message signing and verification. --- openssl/src/pkey_ctx.rs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/openssl/src/pkey_ctx.rs b/openssl/src/pkey_ctx.rs index f0bfbdcc5..08110ce01 100644 --- a/openssl/src/pkey_ctx.rs +++ b/openssl/src/pkey_ctx.rs @@ -223,6 +223,7 @@ where /// Prepares the context for signature verification over a message /// using the public key. + #[cfg(ossl340)] #[corresponds(EVP_PKEY_verify_message_init)] #[inline] pub fn verify_message_init(&mut self, @@ -421,6 +422,7 @@ where } /// Prepares the context for signing a message using the private key. + #[cfg(ossl340)] #[corresponds(EVP_PKEY_sign_message_init)] #[inline] pub fn sign_message_init(&mut self, algo: &mut crate::signature::Signature) From d617f8b5659ebd9334066f50018dc9c6d4d38cf4 Mon Sep 17 00:00:00 2001 From: Justus Winter Date: Mon, 12 May 2025 17:18:00 +0200 Subject: [PATCH 11/27] fixup! Expose message signing and verification. --- openssl/build.rs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/openssl/build.rs b/openssl/build.rs index 8c6a3a6f8..edec8978e 100644 --- a/openssl/build.rs +++ b/openssl/build.rs @@ -46,6 +46,7 @@ fn main() { println!("cargo:rustc-check-cfg=cfg(ossl310)"); println!("cargo:rustc-check-cfg=cfg(ossl320)"); println!("cargo:rustc-check-cfg=cfg(ossl330)"); + println!("cargo:rustc-check-cfg=cfg(ossl340)"); println!("cargo:rustc-check-cfg=cfg(ossl350)"); if env::var("DEP_OPENSSL_LIBRESSL").is_ok() { @@ -170,6 +171,9 @@ fn main() { if version >= 0x3_03_00_00_0 { println!("cargo:rustc-cfg=ossl330"); } + if version >= 0x3_04_00_00_0 { + println!("cargo:rustc-cfg=ossl340"); + } if version >= 0x3_05_00_00_0 { println!("cargo:rustc-cfg=ossl350"); } From 804620c3862ddf1fdea3821d66980cea58d14e14 Mon Sep 17 00:00:00 2001 From: Justus Winter Date: Tue, 13 May 2025 11:07:51 +0200 Subject: [PATCH 12/27] fixup! Expose encapsulation and decapsulation. --- openssl-sys/src/handwritten/evp.rs | 10 ++------ openssl/src/pkey_ctx.rs | 38 +++++++++++++++++------------- 2 files changed, 23 insertions(+), 25 deletions(-) diff --git a/openssl-sys/src/handwritten/evp.rs b/openssl-sys/src/handwritten/evp.rs index 9e7987350..0327fb8c6 100644 --- a/openssl-sys/src/handwritten/evp.rs +++ b/openssl-sys/src/handwritten/evp.rs @@ -663,10 +663,7 @@ extern "C" { ) -> c_int; #[cfg(ossl300)] - pub fn EVP_PKEY_encapsulate_init( - ctx: *mut EVP_PKEY_CTX, - params: *const OSSL_PARAM, - ) -> c_int; + pub fn EVP_PKEY_encapsulate_init(ctx: *mut EVP_PKEY_CTX, params: *const OSSL_PARAM) -> c_int; #[cfg(ossl300)] pub fn EVP_PKEY_encapsulate( ctx: *mut EVP_PKEY_CTX, @@ -677,10 +674,7 @@ extern "C" { ) -> c_int; #[cfg(ossl300)] - pub fn EVP_PKEY_decapsulate_init( - ctx: *mut EVP_PKEY_CTX, - params: *const OSSL_PARAM, - ) -> c_int; + pub fn EVP_PKEY_decapsulate_init(ctx: *mut EVP_PKEY_CTX, params: *const OSSL_PARAM) -> c_int; #[cfg(ossl300)] pub fn EVP_PKEY_decapsulate( ctx: *mut EVP_PKEY_CTX, diff --git a/openssl/src/pkey_ctx.rs b/openssl/src/pkey_ctx.rs index 08110ce01..dd2387a5f 100644 --- a/openssl/src/pkey_ctx.rs +++ b/openssl/src/pkey_ctx.rs @@ -203,8 +203,7 @@ where #[inline] pub fn encapsulate_init(&mut self) -> Result<(), ErrorStack> { unsafe { - cvt(ffi::EVP_PKEY_encapsulate_init(self.as_ptr(), - ptr::null()))?; + cvt(ffi::EVP_PKEY_encapsulate_init(self.as_ptr(), ptr::null()))?; } Ok(()) @@ -283,9 +282,11 @@ where /// Performs a public key encapsulation operation. #[cfg(ossl300)] #[corresponds(EVP_PKEY_encapsulate)] - pub fn encapsulate(&mut self, wrappedkey: Option<&mut [u8]>, genkey: Option<&mut [u8]>) - -> Result<(usize, usize), ErrorStack> - { + pub fn encapsulate( + &mut self, + wrappedkey: Option<&mut [u8]>, + genkey: Option<&mut [u8]>, + ) -> Result<(usize, usize), ErrorStack> { let mut wrappedkey_len = wrappedkey.as_ref().map_or(0, |b| b.len()); let mut genkey_len = genkey.as_ref().map_or(0, |b| b.len()); unsafe { @@ -303,17 +304,20 @@ where /// Like [`Self::encapsulate`] but appends ciphertext and key to a [`Vec`]. #[cfg(ossl300)] - pub fn encapsulate_to_vec(&mut self, wrappedkey: &mut Vec, genkey: &mut Vec) - -> Result<(usize, usize), ErrorStack> - { + pub fn encapsulate_to_vec( + &mut self, + wrappedkey: &mut Vec, + genkey: &mut Vec, + ) -> Result<(usize, usize), ErrorStack> { let wrappedkey_base = wrappedkey.len(); let genkey_base = genkey.len(); let (wrappedkey_len, genkey_len) = self.encapsulate(None, None)?; wrappedkey.resize(wrappedkey_base + wrappedkey_len, 0); genkey.resize(genkey_base + genkey_len, 0); - let (wrappedkey_len, genkey_len) = - self.encapsulate(Some(&mut wrappedkey[wrappedkey_base..]), - Some(&mut genkey[genkey_base..]))?; + let (wrappedkey_len, genkey_len) = self.encapsulate( + Some(&mut wrappedkey[wrappedkey_base..]), + Some(&mut genkey[genkey_base..]), + )?; wrappedkey.truncate(wrappedkey_base + wrappedkey_len); genkey.truncate(genkey_base + genkey_len); Ok((wrappedkey_len, genkey_len)) @@ -483,9 +487,7 @@ where /// Performs a decapsulation operation using the private key. #[cfg(ossl300)] #[corresponds(EVP_PKEY_decapsulate)] - pub fn decapsulate(&mut self, from: &[u8], to: Option<&mut [u8]>) - -> Result - { + pub fn decapsulate(&mut self, from: &[u8], to: Option<&mut [u8]>) -> Result { let mut written = to.as_ref().map_or(0, |b| b.len()); unsafe { cvt(ffi::EVP_PKEY_decapsulate( @@ -502,9 +504,11 @@ where /// Like [`Self::decapsulate`] but appends plaintext to a [`Vec`]. #[cfg(ossl300)] - pub fn decapsulate_to_vec(&mut self, from: &[u8], out: &mut Vec) - -> Result - { + pub fn decapsulate_to_vec( + &mut self, + from: &[u8], + out: &mut Vec, + ) -> Result { let base = out.len(); let len = self.decapsulate(from, None)?; out.resize(base + len, 0); From fd6311338b40b0b57c92ad8ccbcc7ac4ecf80239 Mon Sep 17 00:00:00 2001 From: Justus Winter Date: Tue, 13 May 2025 11:08:21 +0200 Subject: [PATCH 13/27] fixup! Add support for ML-KEM. --- openssl/src/pkey_ml_kem.rs | 52 +++++++++++++++++++------------------- 1 file changed, 26 insertions(+), 26 deletions(-) diff --git a/openssl/src/pkey_ml_kem.rs b/openssl/src/pkey_ml_kem.rs index 59d25339b..bf79aea65 100644 --- a/openssl/src/pkey_ml_kem.rs +++ b/openssl/src/pkey_ml_kem.rs @@ -66,10 +66,7 @@ impl PKeyMlKemBuilder { /// Creates a new `PKeyMlKemBuilder` to build ML-KEM private keys /// from a seed. - pub fn from_seed( - variant: Variant, - seed: &[u8], - ) -> Result, ErrorStack> { + pub fn from_seed(variant: Variant, seed: &[u8]) -> Result, ErrorStack> { let bld = OsslParamBuilder::new()?; bld.add_octet_string(OSSL_PKEY_PARAM_SEED, seed)?; Ok(PKeyMlKemBuilder:: { @@ -82,8 +79,7 @@ impl PKeyMlKemBuilder { /// Build PKey. Internal. #[corresponds(EVP_PKEY_fromdata)] fn build_internal(self, selection: c_int) -> Result, ErrorStack> { - let mut ctx = PkeyCtx::new_from_name( - None, self.variant.as_str(), None)?; + let mut ctx = PkeyCtx::new_from_name(None, self.variant.as_str(), None)?; ctx.fromdata_init()?; let params = self.bld.to_param()?; unsafe { @@ -109,9 +105,7 @@ impl PKeyMlKemBuilder { /// Creates a new `PKeyRsaBuilder` to generate a new ML-KEM key /// pair. - pub fn new_generate(variant: Variant) - -> Result, ErrorStack> - { + pub fn new_generate(variant: Variant) -> Result, ErrorStack> { let bld = OsslParamBuilder::new()?; Ok(PKeyMlKemBuilder:: { bld, @@ -122,8 +116,7 @@ impl PKeyMlKemBuilder { /// Generate an ML-KEM PKey. pub fn generate(self) -> Result, ErrorStack> { - let mut ctx = PkeyCtx::new_from_name( - None, self.variant.as_str(), None)?; + let mut ctx = PkeyCtx::new_from_name(None, self.variant.as_str(), None)?; ctx.keygen_init()?; let params = self.bld.to_param()?; unsafe { @@ -149,9 +142,10 @@ pub struct PKeyMlKemParams { impl PKeyMlKemParams { /// Creates a new `PKeyMlKemParams` from existing ML-KEM PKey. Internal. #[corresponds(EVP_PKEY_todata)] - fn _new_from_pkey(pkey: &PKey, selection: c_int) - -> Result, ErrorStack> - { + fn _new_from_pkey( + pkey: &PKey, + selection: c_int, + ) -> Result, ErrorStack> { unsafe { let mut params: *mut ffi::OSSL_PARAM = ptr::null_mut(); cvt(ffi::EVP_PKEY_todata(pkey.as_ptr(), selection, &mut params))?; @@ -192,7 +186,9 @@ impl PKeyMlKemParams { /// Returns the private key. pub fn private_key(&self) -> Result<&[u8], ErrorStack> { - self.params.locate(OSSL_PKEY_PARAM_PRIV_KEY)?.get_octet_string() + self.params + .locate(OSSL_PKEY_PARAM_PRIV_KEY)? + .get_octet_string() } } @@ -224,7 +220,8 @@ mod tests { let (mut wrappedkey, mut genkey0) = (vec![], vec![]); let mut ctx = PkeyCtx::new(&key).unwrap(); ctx.encapsulate_init().unwrap(); - ctx.encapsulate_to_vec(&mut wrappedkey, &mut genkey0).unwrap(); + ctx.encapsulate_to_vec(&mut wrappedkey, &mut genkey0) + .unwrap(); let mut genkey1 = vec![]; let mut ctx = PkeyCtx::new(&key).unwrap(); @@ -234,16 +231,18 @@ mod tests { assert_eq!(genkey0, genkey1); // Encapsulate with a PKEY derived from the public parameters. - let public_params = - PKeyMlKemParams::::from_pkey(&key).unwrap(); - let key_pub = PKeyMlKemBuilder::::new( - variant, public_params.public_key().unwrap(), None).unwrap() - .build().unwrap(); + let public_params = PKeyMlKemParams::::from_pkey(&key).unwrap(); + let key_pub = + PKeyMlKemBuilder::::new(variant, public_params.public_key().unwrap(), None) + .unwrap() + .build() + .unwrap(); let (mut wrappedkey, mut genkey0) = (vec![], vec![]); let mut ctx = PkeyCtx::new(&key_pub).unwrap(); ctx.encapsulate_init().unwrap(); - ctx.encapsulate_to_vec(&mut wrappedkey, &mut genkey0).unwrap(); + ctx.encapsulate_to_vec(&mut wrappedkey, &mut genkey0) + .unwrap(); let mut genkey1 = vec![]; let mut ctx = PkeyCtx::new(&key).unwrap(); @@ -255,9 +254,10 @@ mod tests { // Note that we can get the public parameter from the // PKeyMlKemParams:: as well. The same is not true // for ML-DSA, for example. - let private_params = - PKeyMlKemParams::::from_pkey(&key).unwrap(); - assert_eq!(public_params.public_key().unwrap(), - private_params.public_key().unwrap()); + let private_params = PKeyMlKemParams::::from_pkey(&key).unwrap(); + assert_eq!( + public_params.public_key().unwrap(), + private_params.public_key().unwrap() + ); } } From 95255298ede21a43fe2c6c4038b40b70fa8ff5e2 Mon Sep 17 00:00:00 2001 From: Justus Winter Date: Tue, 13 May 2025 11:09:21 +0200 Subject: [PATCH 14/27] fixup! Expose message signing and verification. --- openssl-sys/src/handwritten/evp.rs | 16 ++++++++++------ openssl/src/pkey_ctx.rs | 29 ++++++++++++++++++----------- openssl/src/signature.rs | 14 +++++++------- 3 files changed, 35 insertions(+), 24 deletions(-) diff --git a/openssl-sys/src/handwritten/evp.rs b/openssl-sys/src/handwritten/evp.rs index 0327fb8c6..77ae232d0 100644 --- a/openssl-sys/src/handwritten/evp.rs +++ b/openssl-sys/src/handwritten/evp.rs @@ -612,9 +612,11 @@ extern "C" { pub fn EVP_PKEY_sign_init(ctx: *mut EVP_PKEY_CTX) -> c_int; #[cfg(ossl340)] - pub fn EVP_PKEY_sign_message_init(ctx: *mut EVP_PKEY_CTX, - algo: *mut EVP_SIGNATURE, - params: *const OSSL_PARAM) -> c_int; + pub fn EVP_PKEY_sign_message_init( + ctx: *mut EVP_PKEY_CTX, + algo: *mut EVP_SIGNATURE, + params: *const OSSL_PARAM, + ) -> c_int; pub fn EVP_PKEY_sign( ctx: *mut EVP_PKEY_CTX, @@ -626,9 +628,11 @@ extern "C" { pub fn EVP_PKEY_verify_init(ctx: *mut EVP_PKEY_CTX) -> c_int; #[cfg(ossl340)] - pub fn EVP_PKEY_verify_message_init(ctx: *mut EVP_PKEY_CTX, - algo: *mut EVP_SIGNATURE, - params: *const OSSL_PARAM) -> c_int; + pub fn EVP_PKEY_verify_message_init( + ctx: *mut EVP_PKEY_CTX, + algo: *mut EVP_SIGNATURE, + params: *const OSSL_PARAM, + ) -> c_int; pub fn EVP_PKEY_verify( ctx: *mut EVP_PKEY_CTX, diff --git a/openssl/src/pkey_ctx.rs b/openssl/src/pkey_ctx.rs index dd2387a5f..5638b7ecd 100644 --- a/openssl/src/pkey_ctx.rs +++ b/openssl/src/pkey_ctx.rs @@ -225,13 +225,16 @@ where #[cfg(ossl340)] #[corresponds(EVP_PKEY_verify_message_init)] #[inline] - pub fn verify_message_init(&mut self, - algo: &mut crate::signature::Signature) - -> Result<(), ErrorStack> - { + pub fn verify_message_init( + &mut self, + algo: &mut crate::signature::Signature, + ) -> Result<(), ErrorStack> { unsafe { - cvt(ffi::EVP_PKEY_verify_message_init(self.as_ptr(), algo.as_ptr(), - ptr::null()))?; + cvt(ffi::EVP_PKEY_verify_message_init( + self.as_ptr(), + algo.as_ptr(), + ptr::null(), + ))?; } Ok(()) @@ -429,12 +432,16 @@ where #[cfg(ossl340)] #[corresponds(EVP_PKEY_sign_message_init)] #[inline] - pub fn sign_message_init(&mut self, algo: &mut crate::signature::Signature) - -> Result<(), ErrorStack> - { + pub fn sign_message_init( + &mut self, + algo: &mut crate::signature::Signature, + ) -> Result<(), ErrorStack> { unsafe { - cvt(ffi::EVP_PKEY_sign_message_init(self.as_ptr(), algo.as_ptr(), - ptr::null()))?; + cvt(ffi::EVP_PKEY_sign_message_init( + self.as_ptr(), + algo.as_ptr(), + ptr::null(), + ))?; } Ok(()) diff --git a/openssl/src/signature.rs b/openssl/src/signature.rs index efd353a3b..8b9e919e9 100644 --- a/openssl/src/signature.rs +++ b/openssl/src/signature.rs @@ -1,7 +1,7 @@ //! Wraps `EVP_SIGNATURE` objects. -use crate::error::ErrorStack; use crate::cvt_p; +use crate::error::ErrorStack; use foreign_types::{ForeignType, ForeignTypeRef}; use openssl_macros::corresponds; use std::ffi::CStr; @@ -34,18 +34,18 @@ impl SignatureRef { /// Returns the name of the signature algorithm. #[corresponds(EVP_SIGNATURE_get0_name)] pub fn name(&self) -> &str { - unsafe { - CStr::from_ptr(ffi::EVP_SIGNATURE_get0_name(self.as_ptr())) - }.to_str().expect("identifier to be in UTF8") + unsafe { CStr::from_ptr(ffi::EVP_SIGNATURE_get0_name(self.as_ptr())) } + .to_str() + .expect("identifier to be in UTF8") } /// Returns a human-readable description of the signature /// algorithm. #[corresponds(EVP_SIGNATURE_get0_description)] pub fn description(&self) -> &str { - unsafe { - CStr::from_ptr(ffi::EVP_SIGNATURE_get0_description(self.as_ptr())) - }.to_str().expect("description to be in UTF8") + unsafe { CStr::from_ptr(ffi::EVP_SIGNATURE_get0_description(self.as_ptr())) } + .to_str() + .expect("description to be in UTF8") } } From c72455b15ab489f855bda67651648a90a320f747 Mon Sep 17 00:00:00 2001 From: Justus Winter Date: Tue, 13 May 2025 11:09:36 +0200 Subject: [PATCH 15/27] fixup! Add support for ML-DSA. --- openssl/src/pkey_ml_dsa.rs | 47 ++++++++++++++++++-------------------- openssl/src/signature.rs | 12 +++++----- 2 files changed, 28 insertions(+), 31 deletions(-) diff --git a/openssl/src/pkey_ml_dsa.rs b/openssl/src/pkey_ml_dsa.rs index 04071c380..e7053a98c 100644 --- a/openssl/src/pkey_ml_dsa.rs +++ b/openssl/src/pkey_ml_dsa.rs @@ -44,7 +44,8 @@ impl Variant { Variant::MlDsa44 => CStr::from_bytes_with_nul(b"ML-DSA-44\0"), Variant::MlDsa65 => CStr::from_bytes_with_nul(b"ML-DSA-65\0"), Variant::MlDsa87 => CStr::from_bytes_with_nul(b"ML-DSA-87\0"), - }.unwrap() + } + .unwrap() } } @@ -76,10 +77,7 @@ impl PKeyMlDsaBuilder { /// Creates a new `PKeyMlDsaBuilder` to build ML-DSA private keys /// from a seed. - pub fn from_seed( - variant: Variant, - seed: &[u8], - ) -> Result, ErrorStack> { + pub fn from_seed(variant: Variant, seed: &[u8]) -> Result, ErrorStack> { let bld = OsslParamBuilder::new()?; bld.add_octet_string(OSSL_PKEY_PARAM_SEED, seed)?; Ok(PKeyMlDsaBuilder:: { @@ -91,11 +89,8 @@ impl PKeyMlDsaBuilder { /// Build PKey. Internal. #[corresponds(EVP_PKEY_fromdata)] - fn build_internal(self, selection: c_int) - -> Result, ErrorStack> - { - let mut ctx = PkeyCtx::new_from_name( - None, self.variant.as_str(), None)?; + fn build_internal(self, selection: c_int) -> Result, ErrorStack> { + let mut ctx = PkeyCtx::new_from_name(None, self.variant.as_str(), None)?; ctx.fromdata_init()?; let params = self.bld.to_param()?; unsafe { @@ -121,9 +116,7 @@ impl PKeyMlDsaBuilder { /// Creates a new `PKeyRsaBuilder` to generate a new ML-DSA key /// pair. - pub fn new_generate(variant: Variant) - -> Result, ErrorStack> - { + pub fn new_generate(variant: Variant) -> Result, ErrorStack> { let bld = OsslParamBuilder::new()?; Ok(PKeyMlDsaBuilder:: { bld, @@ -134,8 +127,7 @@ impl PKeyMlDsaBuilder { /// Generate an ML-DSA PKey. pub fn generate(self) -> Result, ErrorStack> { - let mut ctx = PkeyCtx::new_from_name( - None, self.variant.as_str(), None)?; + let mut ctx = PkeyCtx::new_from_name(None, self.variant.as_str(), None)?; ctx.keygen_init()?; let params = self.bld.to_param()?; unsafe { @@ -161,9 +153,10 @@ pub struct PKeyMlDsaParams { impl PKeyMlDsaParams { /// Creates a new `PKeyMlDsaParams` from existing ECDSA PKey. Internal. #[corresponds(EVP_PKEY_todata)] - fn _new_from_pkey(pkey: &PKey, selection: c_int) - -> Result, ErrorStack> - { + fn _new_from_pkey( + pkey: &PKey, + selection: c_int, + ) -> Result, ErrorStack> { unsafe { let mut params: *mut ffi::OSSL_PARAM = ptr::null_mut(); cvt(ffi::EVP_PKEY_todata(pkey.as_ptr(), selection, &mut params))?; @@ -185,7 +178,8 @@ impl PKeyMlDsaParams { /// Returns a reference to the public key. pub fn public_key(&self) -> Result<&[u8], ErrorStack> { self.params - .locate(OSSL_PKEY_PARAM_PUB_KEY).unwrap() + .locate(OSSL_PKEY_PARAM_PUB_KEY) + .unwrap() .get_octet_string() } } @@ -204,7 +198,9 @@ impl PKeyMlDsaParams { /// Returns the private key. pub fn private_key(&self) -> Result<&[u8], ErrorStack> { - self.params.locate(OSSL_PKEY_PARAM_PRIV_KEY)?.get_octet_string() + self.params + .locate(OSSL_PKEY_PARAM_PRIV_KEY)? + .get_octet_string() } } @@ -256,11 +252,12 @@ mod tests { assert!(ErrorStack::get().errors().is_empty()); // Derive a new PKEY with only the public bits. - let public_params = - PKeyMlDsaParams::::from_pkey(&key).unwrap(); - let key_pub = PKeyMlDsaBuilder::::new( - variant, public_params.public_key().unwrap(), None).unwrap() - .build().unwrap(); + let public_params = PKeyMlDsaParams::::from_pkey(&key).unwrap(); + let key_pub = + PKeyMlDsaBuilder::::new(variant, public_params.public_key().unwrap(), None) + .unwrap() + .build() + .unwrap(); let mut ctx = PkeyCtx::new(&key_pub).unwrap(); let mut algo = Signature::for_ml_dsa(variant).unwrap(); diff --git a/openssl/src/signature.rs b/openssl/src/signature.rs index 8b9e919e9..e2bd5fbfd 100644 --- a/openssl/src/signature.rs +++ b/openssl/src/signature.rs @@ -64,12 +64,13 @@ impl Clone for Signature { impl Signature { /// Creates a new `Signature` for use with ML-DSA. #[cfg(ossl350)] - pub fn for_ml_dsa(variant: crate::pkey_ml_dsa::Variant) - -> Result - { + pub fn for_ml_dsa(variant: crate::pkey_ml_dsa::Variant) -> Result { unsafe { Ok(Signature(cvt_p(ffi::EVP_SIGNATURE_fetch( - ptr::null_mut(), variant.as_cstr().as_ptr(), ptr::null()))?)) + ptr::null_mut(), + variant.as_cstr().as_ptr(), + ptr::null(), + ))?)) } } } @@ -82,8 +83,7 @@ mod tests { #[cfg(ossl350)] #[test] fn test_alloc_free() { - let sig = - Signature::for_ml_dsa(crate::pkey_ml_dsa::Variant::MlDsa44).unwrap(); + let sig = Signature::for_ml_dsa(crate::pkey_ml_dsa::Variant::MlDsa44).unwrap(); drop(sig); } } From 7fb4d0b42a346a858fb8e9fac54a2bdccc321d9a Mon Sep 17 00:00:00 2001 From: Justus Winter Date: Tue, 13 May 2025 11:20:37 +0200 Subject: [PATCH 16/27] Appease warnings about unused imports and functions. Some imports and private functions are currently only used on newer OpenSSL versions. This may change once more classical algorithms are using the new interfaces. For now, simply silence the warnings. Let me know if you prefer a different solution. --- openssl/src/ossl_param.rs | 2 ++ openssl/src/signature.rs | 12 ++++++++++++ 2 files changed, 14 insertions(+) diff --git a/openssl/src/ossl_param.rs b/openssl/src/ossl_param.rs index daaeadc5b..b6e775f6f 100644 --- a/openssl/src/ossl_param.rs +++ b/openssl/src/ossl_param.rs @@ -33,6 +33,7 @@ impl OsslParam {} impl OsslParamRef { /// Locates the `OsslParam` in the `OsslParam` array + #[allow(dead_code)] #[corresponds(OSSL_PARAM_locate)] pub fn locate(&self, key: &[u8]) -> Result<&OsslParamRef, ErrorStack> { unsafe { @@ -67,6 +68,7 @@ impl OsslParamRef { } /// Get octet string (as `&[u8]) from the current `OsslParam` + #[allow(dead_code)] #[corresponds(OSSL_PARAM_get_octet_string)] pub fn get_octet_string(&self) -> Result<&[u8], ErrorStack> { unsafe { diff --git a/openssl/src/signature.rs b/openssl/src/signature.rs index e2bd5fbfd..9492e6ccf 100644 --- a/openssl/src/signature.rs +++ b/openssl/src/signature.rs @@ -1,5 +1,10 @@ //! Wraps `EVP_SIGNATURE` objects. +// XXX: A number of imports is only used when ML-DSA is available. +// Once support for universally available signature algorithms is +// added, remove the following line. +#[allow(unused_imports)] + use crate::cvt_p; use crate::error::ErrorStack; use foreign_types::{ForeignType, ForeignTypeRef}; @@ -78,6 +83,13 @@ impl Signature { #[cfg(test)] mod tests { +use std::fs::File; + + // XXX: A number of imports is only used when ML-DSA is available. + // Once support for universally available signature algorithms is + // added, remove the following line. + #[allow(unused_imports)] + use super::*; #[cfg(ossl350)] From efcdbf378c6090d148678585fe66b8170e46c59b Mon Sep 17 00:00:00 2001 From: Justus Winter Date: Tue, 13 May 2025 11:24:38 +0200 Subject: [PATCH 17/27] fixup! Appease warnings about unused imports and functions. --- openssl/src/signature.rs | 2 -- 1 file changed, 2 deletions(-) diff --git a/openssl/src/signature.rs b/openssl/src/signature.rs index 9492e6ccf..79406d175 100644 --- a/openssl/src/signature.rs +++ b/openssl/src/signature.rs @@ -83,8 +83,6 @@ impl Signature { #[cfg(test)] mod tests { -use std::fs::File; - // XXX: A number of imports is only used when ML-DSA is available. // Once support for universally available signature algorithms is // added, remove the following line. From 5fa6d4e6130633f7822a51bcc8ea8e0fb5676e68 Mon Sep 17 00:00:00 2001 From: Justus Winter Date: Tue, 13 May 2025 11:56:06 +0200 Subject: [PATCH 18/27] fixup! Appease warnings about unused imports and functions. --- openssl/src/ossl_param.rs | 12 +++++------- openssl/src/signature.rs | 3 +-- 2 files changed, 6 insertions(+), 9 deletions(-) diff --git a/openssl/src/ossl_param.rs b/openssl/src/ossl_param.rs index b6e775f6f..ec00b7ab6 100644 --- a/openssl/src/ossl_param.rs +++ b/openssl/src/ossl_param.rs @@ -8,7 +8,11 @@ //! //! Note, that this module is available only in OpenSSL 3.* and //! only internally for this crate! -//! + +// Depending on which version of OpenSSL is used, and which algorithms +// are exposed in the bindings, not all of these functions are used. +#![allow(dead_code)] + use crate::bn::{BigNum, BigNumRef}; use crate::error::ErrorStack; use crate::util; @@ -33,7 +37,6 @@ impl OsslParam {} impl OsslParamRef { /// Locates the `OsslParam` in the `OsslParam` array - #[allow(dead_code)] #[corresponds(OSSL_PARAM_locate)] pub fn locate(&self, key: &[u8]) -> Result<&OsslParamRef, ErrorStack> { unsafe { @@ -46,7 +49,6 @@ impl OsslParamRef { } /// Get `BigNum` from the current `OsslParam` - #[allow(dead_code)] #[corresponds(OSSL_PARAM_get_BN)] pub fn get_bn(&self) -> Result { unsafe { @@ -57,7 +59,6 @@ impl OsslParamRef { } /// Get `&str` from the current `OsslParam` - #[allow(dead_code)] #[corresponds(OSSL_PARAM_get_utf8_string)] pub fn get_utf8_string(&self) -> Result<&str, ErrorStack> { unsafe { @@ -68,7 +69,6 @@ impl OsslParamRef { } /// Get octet string (as `&[u8]) from the current `OsslParam` - #[allow(dead_code)] #[corresponds(OSSL_PARAM_get_octet_string)] pub fn get_octet_string(&self) -> Result<&[u8], ErrorStack> { unsafe { @@ -121,7 +121,6 @@ impl OsslParamBuilderRef { /// Adds a `BigNum` to `OsslParamBuilder`. /// /// Note, that both key and bn need to exist until the `to_param` is called! - #[allow(dead_code)] #[corresponds(OSSL_PARAM_BLD_push_BN)] pub fn add_bn(&self, key: &[u8], bn: &BigNumRef) -> Result<(), ErrorStack> { unsafe { @@ -137,7 +136,6 @@ impl OsslParamBuilderRef { /// Adds a utf8 string to `OsslParamBuilder`. /// /// Note, that both `key` and `buf` need to exist until the `to_param` is called! - #[allow(dead_code)] #[corresponds(OSSL_PARAM_BLD_push_utf8_string)] pub fn add_utf8_string(&self, key: &[u8], buf: &str) -> Result<(), ErrorStack> { unsafe { diff --git a/openssl/src/signature.rs b/openssl/src/signature.rs index 79406d175..68096f441 100644 --- a/openssl/src/signature.rs +++ b/openssl/src/signature.rs @@ -3,7 +3,7 @@ // XXX: A number of imports is only used when ML-DSA is available. // Once support for universally available signature algorithms is // added, remove the following line. -#[allow(unused_imports)] +#![allow(unused_imports)] use crate::cvt_p; use crate::error::ErrorStack; @@ -87,7 +87,6 @@ mod tests { // Once support for universally available signature algorithms is // added, remove the following line. #[allow(unused_imports)] - use super::*; #[cfg(ossl350)] From 2c3e2d499bfffc618aa06098efd93759de9f0f42 Mon Sep 17 00:00:00 2001 From: Justus Winter Date: Tue, 13 May 2025 18:39:05 +0200 Subject: [PATCH 19/27] fixup! Add support for ML-DSA. --- openssl/src/pkey_ml_dsa.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/openssl/src/pkey_ml_dsa.rs b/openssl/src/pkey_ml_dsa.rs index e7053a98c..a57fa9d23 100644 --- a/openssl/src/pkey_ml_dsa.rs +++ b/openssl/src/pkey_ml_dsa.rs @@ -151,7 +151,7 @@ pub struct PKeyMlDsaParams { } impl PKeyMlDsaParams { - /// Creates a new `PKeyMlDsaParams` from existing ECDSA PKey. Internal. + /// Creates a new `PKeyMlDsaParams` from existing ML-DSA PKey. Internal. #[corresponds(EVP_PKEY_todata)] fn _new_from_pkey( pkey: &PKey, @@ -169,7 +169,7 @@ impl PKeyMlDsaParams { } impl PKeyMlDsaParams { - /// Creates a new `PKeyMlDsaParams` from existing Public ECDSA PKey. + /// Creates a new `PKeyMlDsaParams` from existing Public ML-DSA PKey. #[corresponds(EVP_PKEY_todata)] pub fn from_pkey(pkey: &PKey) -> Result, ErrorStack> { Self::_new_from_pkey(pkey, ffi::EVP_PKEY_PUBLIC_KEY) @@ -185,7 +185,7 @@ impl PKeyMlDsaParams { } impl PKeyMlDsaParams { - /// Creates a new `PKeyMlDsaParams` from existing Private ECDSA PKey. + /// Creates a new `PKeyMlDsaParams` from existing Private ML-DSA PKey. #[corresponds(EVP_PKEY_todata)] pub fn from_pkey(pkey: &PKey) -> Result, ErrorStack> { Self::_new_from_pkey(pkey, ffi::EVP_PKEY_KEYPAIR) From 6b3b15c0eb210d72ec1f895b88d575f42e62d740 Mon Sep 17 00:00:00 2001 From: Justus Winter Date: Thu, 15 May 2025 12:34:01 +0200 Subject: [PATCH 20/27] Add support for SLH-DSA. Fixes #2393. --- openssl/src/lib.rs | 2 + openssl/src/pkey_slh_dsa.rs | 348 ++++++++++++++++++++++++++++++++++++ openssl/src/signature.rs | 12 ++ 3 files changed, 362 insertions(+) create mode 100644 openssl/src/pkey_slh_dsa.rs diff --git a/openssl/src/lib.rs b/openssl/src/lib.rs index e6b0e85a0..23f79e5bc 100644 --- a/openssl/src/lib.rs +++ b/openssl/src/lib.rs @@ -189,6 +189,8 @@ pub mod pkey_ctx; pub mod pkey_ml_dsa; #[cfg(ossl350)] pub mod pkey_ml_kem; +#[cfg(ossl350)] +pub mod pkey_slh_dsa; #[cfg(ossl300)] pub mod provider; pub mod rand; diff --git a/openssl/src/pkey_slh_dsa.rs b/openssl/src/pkey_slh_dsa.rs new file mode 100644 index 000000000..7fe3ff340 --- /dev/null +++ b/openssl/src/pkey_slh_dsa.rs @@ -0,0 +1,348 @@ +//! Stateless hash-based digital signatures. +//! +//! SLH-DSA is a signature algorithm that is believed to be secure +//! against adversaries with quantum computers. It has been +//! standardized by NIST as [FIPS 205]. +//! +//! [FIPS 205]: https://csrc.nist.gov/pubs/fips/205/final + +use std::ffi::CStr; + +use foreign_types::ForeignType; +use libc::c_int; +use std::ptr; + +use crate::error::ErrorStack; +use crate::ossl_param::{OsslParam, OsslParamBuilder}; +use crate::pkey::{PKey, Private, Public}; +use crate::pkey_ctx::PkeyCtx; +use crate::{cvt, cvt_p}; +use openssl_macros::corresponds; + +const OSSL_PKEY_PARAM_SEED: &[u8; 5] = b"seed\0"; +const OSSL_PKEY_PARAM_PUB_KEY: &[u8; 4] = b"pub\0"; +const OSSL_PKEY_PARAM_PRIV_KEY: &[u8; 5] = b"priv\0"; + +#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, PartialOrd, Ord)] +pub enum Variant { + SlhDsaSha2_128s, + SlhDsaSha2_128f, + SlhDsaSha2_192s, + SlhDsaSha2_192f, + SlhDsaSha2_256s, + SlhDsaSha2_256f, + SlhDsaShake128s, + SlhDsaShake128f, + SlhDsaShake192s, + SlhDsaShake192f, + SlhDsaShake256s, + SlhDsaShake256f, +} + +impl Variant { + pub(crate) fn as_str(&self) -> &'static str { + match self { + Variant::SlhDsaSha2_128s => "SLH-DSA-SHA2-128s", + Variant::SlhDsaSha2_128f => "SLH-DSA-SHA2-128f", + Variant::SlhDsaSha2_192s => "SLH-DSA-SHA2-192s", + Variant::SlhDsaSha2_192f => "SLH-DSA-SHA2-192f", + Variant::SlhDsaSha2_256s => "SLH-DSA-SHA2-256s", + Variant::SlhDsaSha2_256f => "SLH-DSA-SHA2-256f", + Variant::SlhDsaShake128s => "SLH-DSA-SHAKE-128s", + Variant::SlhDsaShake128f => "SLH-DSA-SHAKE-128f", + Variant::SlhDsaShake192s => "SLH-DSA-SHAKE-192s", + Variant::SlhDsaShake192f => "SLH-DSA-SHAKE-192f", + Variant::SlhDsaShake256s => "SLH-DSA-SHAKE-256s", + Variant::SlhDsaShake256f => "SLH-DSA-SHAKE-256f", + } + } + + pub(crate) fn as_cstr(&self) -> &'static CStr { + match self { + Variant::SlhDsaSha2_128s => CStr::from_bytes_with_nul(b"SLH-DSA-SHA2-128s\0"), + Variant::SlhDsaSha2_128f => CStr::from_bytes_with_nul(b"SLH-DSA-SHA2-128f\0"), + Variant::SlhDsaSha2_192s => CStr::from_bytes_with_nul(b"SLH-DSA-SHA2-192s\0"), + Variant::SlhDsaSha2_192f => CStr::from_bytes_with_nul(b"SLH-DSA-SHA2-192f\0"), + Variant::SlhDsaSha2_256s => CStr::from_bytes_with_nul(b"SLH-DSA-SHA2-256s\0"), + Variant::SlhDsaSha2_256f => CStr::from_bytes_with_nul(b"SLH-DSA-SHA2-256f\0"), + Variant::SlhDsaShake128s => CStr::from_bytes_with_nul(b"SLH-DSA-SHAKE-128s\0"), + Variant::SlhDsaShake128f => CStr::from_bytes_with_nul(b"SLH-DSA-SHAKE-128f\0"), + Variant::SlhDsaShake192s => CStr::from_bytes_with_nul(b"SLH-DSA-SHAKE-192s\0"), + Variant::SlhDsaShake192f => CStr::from_bytes_with_nul(b"SLH-DSA-SHAKE-192f\0"), + Variant::SlhDsaShake256s => CStr::from_bytes_with_nul(b"SLH-DSA-SHAKE-256s\0"), + Variant::SlhDsaShake256f => CStr::from_bytes_with_nul(b"SLH-DSA-SHAKE-256f\0"), + } + .unwrap() + } +} + +pub struct PKeySlhDsaBuilder { + bld: OsslParamBuilder, + variant: Variant, + _m: ::std::marker::PhantomData, +} + +impl PKeySlhDsaBuilder { + /// Creates a new `PKeySlhDsaBuilder` to build ML-DSA private or + /// public keys. + pub fn new( + variant: Variant, + public: &[u8], + private: Option<&[u8]>, + ) -> Result, ErrorStack> { + let bld = OsslParamBuilder::new()?; + bld.add_octet_string(OSSL_PKEY_PARAM_PUB_KEY, public)?; + if let Some(private) = private { + bld.add_octet_string(OSSL_PKEY_PARAM_PRIV_KEY, private)? + }; + Ok(PKeySlhDsaBuilder:: { + bld, + variant, + _m: ::std::marker::PhantomData, + }) + } + + /// Creates a new `PKeySlhDsaBuilder` to build ML-DSA private keys + /// from a seed. + pub fn from_seed(variant: Variant, seed: &[u8]) -> Result, ErrorStack> { + let bld = OsslParamBuilder::new()?; + bld.add_octet_string(OSSL_PKEY_PARAM_SEED, seed)?; + Ok(PKeySlhDsaBuilder:: { + bld, + variant, + _m: ::std::marker::PhantomData, + }) + } + + /// Build PKey. Internal. + #[corresponds(EVP_PKEY_fromdata)] + fn build_internal(self, selection: c_int) -> Result, ErrorStack> { + let mut ctx = PkeyCtx::new_from_name(None, self.variant.as_str(), None)?; + ctx.fromdata_init()?; + let params = self.bld.to_param()?; + unsafe { + let evp = cvt_p(ffi::EVP_PKEY_new())?; + let pkey = PKey::from_ptr(evp); + cvt(ffi::EVP_PKEY_fromdata( + ctx.as_ptr(), + &mut pkey.as_ptr(), + selection, + params.as_ptr(), + ))?; + Ok(pkey) + } + } +} + +impl PKeySlhDsaBuilder { + /// Returns the Private ML-DSA PKey from the provided parameters. + #[corresponds(EVP_PKEY_fromdata)] + pub fn build(self) -> Result, ErrorStack> { + self.build_internal(ffi::EVP_PKEY_KEYPAIR) + } + + /// Creates a new `PKeyRsaBuilder` to generate a new ML-DSA key + /// pair. + pub fn new_generate(variant: Variant) -> Result, ErrorStack> { + let bld = OsslParamBuilder::new()?; + Ok(PKeySlhDsaBuilder:: { + bld, + variant, + _m: ::std::marker::PhantomData, + }) + } + + /// Generate an ML-DSA PKey. + pub fn generate(self) -> Result, ErrorStack> { + let mut ctx = PkeyCtx::new_from_name(None, self.variant.as_str(), None)?; + ctx.keygen_init()?; + let params = self.bld.to_param()?; + unsafe { + cvt(ffi::EVP_PKEY_CTX_set_params(ctx.as_ptr(), params.as_ptr()))?; + } + ctx.generate() + } +} + +impl PKeySlhDsaBuilder { + /// Returns the Public ML-DSA PKey from the provided parameters. + #[corresponds(EVP_PKEY_fromdata)] + pub fn build(self) -> Result, ErrorStack> { + self.build_internal(ffi::EVP_PKEY_PUBLIC_KEY) + } +} + +pub struct PKeySlhDsaParams { + params: OsslParam, + _m: ::std::marker::PhantomData, +} + +impl PKeySlhDsaParams { + /// Creates a new `PKeySlhDsaParams` from existing ML-DSA PKey. Internal. + #[corresponds(EVP_PKEY_todata)] + fn _new_from_pkey( + pkey: &PKey, + selection: c_int, + ) -> Result, ErrorStack> { + unsafe { + let mut params: *mut ffi::OSSL_PARAM = ptr::null_mut(); + cvt(ffi::EVP_PKEY_todata(pkey.as_ptr(), selection, &mut params))?; + Ok(PKeySlhDsaParams:: { + params: OsslParam::from_ptr(params), + _m: ::std::marker::PhantomData, + }) + } + } +} + +impl PKeySlhDsaParams { + /// Creates a new `PKeySlhDsaParams` from existing Public ML-DSA PKey. + #[corresponds(EVP_PKEY_todata)] + pub fn from_pkey(pkey: &PKey) -> Result, ErrorStack> { + Self::_new_from_pkey(pkey, ffi::EVP_PKEY_PUBLIC_KEY) + } + + /// Returns a reference to the public key. + pub fn public_key(&self) -> Result<&[u8], ErrorStack> { + self.params + .locate(OSSL_PKEY_PARAM_PUB_KEY) + .unwrap() + .get_octet_string() + } +} + +impl PKeySlhDsaParams { + /// Creates a new `PKeySlhDsaParams` from existing Private ML-DSA PKey. + #[corresponds(EVP_PKEY_todata)] + pub fn from_pkey(pkey: &PKey) -> Result, ErrorStack> { + Self::_new_from_pkey(pkey, ffi::EVP_PKEY_KEYPAIR) + } + + /// Returns the private key seed. + pub fn private_key_seed(&self) -> Result<&[u8], ErrorStack> { + self.params.locate(OSSL_PKEY_PARAM_SEED)?.get_octet_string() + } + + /// Returns the private key. + pub fn private_key(&self) -> Result<&[u8], ErrorStack> { + self.params + .locate(OSSL_PKEY_PARAM_PRIV_KEY)? + .get_octet_string() + } +} + +#[cfg(test)] +mod tests { + + use super::*; + use crate::signature::Signature; + + #[test] + fn test_generate_slh_dsa_sha2_128s() { + test_generate(Variant::SlhDsaSha2_128s); + } + + #[test] + fn test_generate_slh_dsa_sha2_128f() { + test_generate(Variant::SlhDsaSha2_128f); + } + + #[test] + fn test_generate_slh_dsa_sha2_192s() { + test_generate(Variant::SlhDsaSha2_192s); + } + + #[test] + fn test_generate_slh_dsa_sha2_192f() { + test_generate(Variant::SlhDsaSha2_192f); + } + + #[test] + fn test_generate_slh_dsa_sha2_256s() { + test_generate(Variant::SlhDsaSha2_256s); + } + + #[test] + fn test_generate_slh_dsa_sha2_256f() { + test_generate(Variant::SlhDsaSha2_256f); + } + + #[test] + fn test_generate_slh_dsa_shake_128s() { + test_generate(Variant::SlhDsaShake128s); + } + + #[test] + fn test_generate_slh_dsa_shake_128f() { + test_generate(Variant::SlhDsaShake128f); + } + + #[test] + fn test_generate_slh_dsa_shake_192s() { + test_generate(Variant::SlhDsaShake192s); + } + + #[test] + fn test_generate_slh_dsa_shake_192f() { + test_generate(Variant::SlhDsaShake192f); + } + + #[test] + fn test_generate_slh_dsa_shake_256s() { + test_generate(Variant::SlhDsaShake256s); + } + + #[test] + fn test_generate_slh_dsa_shake_256f() { + test_generate(Variant::SlhDsaShake256f); + } + + fn test_generate(variant: Variant) { + let bld = PKeySlhDsaBuilder::::new_generate(variant).unwrap(); + let key = bld.generate().unwrap(); + + let mut algo = Signature::for_slh_dsa(variant).unwrap(); + + let data = b"Some Crypto Text"; + let bad_data = b"Some Crypto text"; + + let mut signature = vec![]; + let mut ctx = PkeyCtx::new(&key).unwrap(); + ctx.sign_message_init(&mut algo).unwrap(); + ctx.sign_to_vec(&data[..], &mut signature).unwrap(); + + // Verify good version with the original PKEY. + ctx.verify_message_init(&mut algo).unwrap(); + let valid = ctx.verify(&data[..], &signature); + assert!(matches!(valid, Ok(true))); + assert!(ErrorStack::get().errors().is_empty()); + + // Verify bad version with the original PKEY. + ctx.verify_message_init(&mut algo).unwrap(); + let valid = ctx.verify(&bad_data[..], &signature); + assert!(matches!(valid, Ok(false) | Err(_))); + assert!(ErrorStack::get().errors().is_empty()); + + // Derive a new PKEY with only the public bits. + let public_params = PKeySlhDsaParams::::from_pkey(&key).unwrap(); + let key_pub = + PKeySlhDsaBuilder::::new(variant, public_params.public_key().unwrap(), None) + .unwrap() + .build() + .unwrap(); + let mut ctx = PkeyCtx::new(&key_pub).unwrap(); + let mut algo = Signature::for_slh_dsa(variant).unwrap(); + + // Verify good version with the public PKEY. + ctx.verify_message_init(&mut algo).unwrap(); + let valid = ctx.verify(&data[..], &signature); + assert!(matches!(valid, Ok(true))); + assert!(ErrorStack::get().errors().is_empty()); + + // Verify bad version with the public PKEY. + ctx.verify_message_init(&mut algo).unwrap(); + let valid = ctx.verify(&bad_data[..], &signature); + assert!(matches!(valid, Ok(false) | Err(_))); + assert!(ErrorStack::get().errors().is_empty()); + } +} diff --git a/openssl/src/signature.rs b/openssl/src/signature.rs index 68096f441..eb687b772 100644 --- a/openssl/src/signature.rs +++ b/openssl/src/signature.rs @@ -78,6 +78,18 @@ impl Signature { ))?)) } } + + /// Creates a new `Signature` for use with SLH-DSA. + #[cfg(ossl350)] + pub fn for_slh_dsa(variant: crate::pkey_slh_dsa::Variant) -> Result { + unsafe { + Ok(Signature(cvt_p(ffi::EVP_SIGNATURE_fetch( + ptr::null_mut(), + variant.as_cstr().as_ptr(), + ptr::null(), + ))?)) + } + } } #[cfg(test)] From 25b5f99a8c3ee82dbe3b68014b02d50864f4aab0 Mon Sep 17 00:00:00 2001 From: Justus Winter Date: Thu, 15 May 2025 12:35:13 +0200 Subject: [PATCH 21/27] fixup! Add support for SLH-DSA. --- openssl/src/pkey_slh_dsa.rs | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/openssl/src/pkey_slh_dsa.rs b/openssl/src/pkey_slh_dsa.rs index 7fe3ff340..abe0424fd 100644 --- a/openssl/src/pkey_slh_dsa.rs +++ b/openssl/src/pkey_slh_dsa.rs @@ -83,7 +83,7 @@ pub struct PKeySlhDsaBuilder { } impl PKeySlhDsaBuilder { - /// Creates a new `PKeySlhDsaBuilder` to build ML-DSA private or + /// Creates a new `PKeySlhDsaBuilder` to build SLH-DSA private or /// public keys. pub fn new( variant: Variant, @@ -102,7 +102,7 @@ impl PKeySlhDsaBuilder { }) } - /// Creates a new `PKeySlhDsaBuilder` to build ML-DSA private keys + /// Creates a new `PKeySlhDsaBuilder` to build SLH-DSA private keys /// from a seed. pub fn from_seed(variant: Variant, seed: &[u8]) -> Result, ErrorStack> { let bld = OsslParamBuilder::new()?; @@ -135,13 +135,13 @@ impl PKeySlhDsaBuilder { } impl PKeySlhDsaBuilder { - /// Returns the Private ML-DSA PKey from the provided parameters. + /// Returns the Private SLH-DSA PKey from the provided parameters. #[corresponds(EVP_PKEY_fromdata)] pub fn build(self) -> Result, ErrorStack> { self.build_internal(ffi::EVP_PKEY_KEYPAIR) } - /// Creates a new `PKeyRsaBuilder` to generate a new ML-DSA key + /// Creates a new `PKeyRsaBuilder` to generate a new SLH-DSA key /// pair. pub fn new_generate(variant: Variant) -> Result, ErrorStack> { let bld = OsslParamBuilder::new()?; @@ -152,7 +152,7 @@ impl PKeySlhDsaBuilder { }) } - /// Generate an ML-DSA PKey. + /// Generate an SLH-DSA PKey. pub fn generate(self) -> Result, ErrorStack> { let mut ctx = PkeyCtx::new_from_name(None, self.variant.as_str(), None)?; ctx.keygen_init()?; @@ -165,7 +165,7 @@ impl PKeySlhDsaBuilder { } impl PKeySlhDsaBuilder { - /// Returns the Public ML-DSA PKey from the provided parameters. + /// Returns the Public SLH-DSA PKey from the provided parameters. #[corresponds(EVP_PKEY_fromdata)] pub fn build(self) -> Result, ErrorStack> { self.build_internal(ffi::EVP_PKEY_PUBLIC_KEY) @@ -178,7 +178,7 @@ pub struct PKeySlhDsaParams { } impl PKeySlhDsaParams { - /// Creates a new `PKeySlhDsaParams` from existing ML-DSA PKey. Internal. + /// Creates a new `PKeySlhDsaParams` from existing SLH-DSA PKey. Internal. #[corresponds(EVP_PKEY_todata)] fn _new_from_pkey( pkey: &PKey, @@ -196,7 +196,7 @@ impl PKeySlhDsaParams { } impl PKeySlhDsaParams { - /// Creates a new `PKeySlhDsaParams` from existing Public ML-DSA PKey. + /// Creates a new `PKeySlhDsaParams` from existing Public SLH-DSA PKey. #[corresponds(EVP_PKEY_todata)] pub fn from_pkey(pkey: &PKey) -> Result, ErrorStack> { Self::_new_from_pkey(pkey, ffi::EVP_PKEY_PUBLIC_KEY) @@ -212,7 +212,7 @@ impl PKeySlhDsaParams { } impl PKeySlhDsaParams { - /// Creates a new `PKeySlhDsaParams` from existing Private ML-DSA PKey. + /// Creates a new `PKeySlhDsaParams` from existing Private SLH-DSA PKey. #[corresponds(EVP_PKEY_todata)] pub fn from_pkey(pkey: &PKey) -> Result, ErrorStack> { Self::_new_from_pkey(pkey, ffi::EVP_PKEY_KEYPAIR) From 816b788ff341bae9ac8cdcc4fdfbab28f7a4cc52 Mon Sep 17 00:00:00 2001 From: Justus Winter Date: Mon, 19 May 2025 11:40:11 +0200 Subject: [PATCH 22/27] fixup! Add support for SLH-DSA. --- openssl/src/pkey_slh_dsa.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openssl/src/pkey_slh_dsa.rs b/openssl/src/pkey_slh_dsa.rs index abe0424fd..32d787cb2 100644 --- a/openssl/src/pkey_slh_dsa.rs +++ b/openssl/src/pkey_slh_dsa.rs @@ -141,7 +141,7 @@ impl PKeySlhDsaBuilder { self.build_internal(ffi::EVP_PKEY_KEYPAIR) } - /// Creates a new `PKeyRsaBuilder` to generate a new SLH-DSA key + /// Creates a new `PKeySlhDsaBuilder` to generate a new SLH-DSA key /// pair. pub fn new_generate(variant: Variant) -> Result, ErrorStack> { let bld = OsslParamBuilder::new()?; From 28a442d474272106975dbd1f82dc316ec68eecbd Mon Sep 17 00:00:00 2001 From: Justus Winter Date: Mon, 19 May 2025 11:40:20 +0200 Subject: [PATCH 23/27] fixup! Add support for ML-KEM. --- openssl/src/pkey_ml_kem.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openssl/src/pkey_ml_kem.rs b/openssl/src/pkey_ml_kem.rs index bf79aea65..37db7da0d 100644 --- a/openssl/src/pkey_ml_kem.rs +++ b/openssl/src/pkey_ml_kem.rs @@ -103,7 +103,7 @@ impl PKeyMlKemBuilder { self.build_internal(ffi::EVP_PKEY_KEYPAIR) } - /// Creates a new `PKeyRsaBuilder` to generate a new ML-KEM key + /// Creates a new `PKeyMlKemBuilder` to generate a new ML-KEM key /// pair. pub fn new_generate(variant: Variant) -> Result, ErrorStack> { let bld = OsslParamBuilder::new()?; From 37768f2f4631239a9d807949bc343be4af2f07fd Mon Sep 17 00:00:00 2001 From: Justus Winter Date: Mon, 19 May 2025 11:40:29 +0200 Subject: [PATCH 24/27] fixup! Add support for ML-DSA. --- openssl/src/pkey_ml_dsa.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openssl/src/pkey_ml_dsa.rs b/openssl/src/pkey_ml_dsa.rs index a57fa9d23..99bada434 100644 --- a/openssl/src/pkey_ml_dsa.rs +++ b/openssl/src/pkey_ml_dsa.rs @@ -114,7 +114,7 @@ impl PKeyMlDsaBuilder { self.build_internal(ffi::EVP_PKEY_KEYPAIR) } - /// Creates a new `PKeyRsaBuilder` to generate a new ML-DSA key + /// Creates a new `PKeyMlDsaBuilder` to generate a new ML-DSA key /// pair. pub fn new_generate(variant: Variant) -> Result, ErrorStack> { let bld = OsslParamBuilder::new()?; From d85a9990a543663fa743c72dc1b0b8a12f60ddfc Mon Sep 17 00:00:00 2001 From: Justus Winter Date: Mon, 19 May 2025 12:14:43 +0200 Subject: [PATCH 25/27] fixup! Add support for ML-DSA. --- openssl/src/pkey_ml_dsa.rs | 44 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 44 insertions(+) diff --git a/openssl/src/pkey_ml_dsa.rs b/openssl/src/pkey_ml_dsa.rs index 99bada434..558527590 100644 --- a/openssl/src/pkey_ml_dsa.rs +++ b/openssl/src/pkey_ml_dsa.rs @@ -272,5 +272,49 @@ mod tests { let valid = ctx.verify(&bad_data[..], &signature); assert!(matches!(valid, Ok(false) | Err(_))); assert!(ErrorStack::get().errors().is_empty()); + + // Derive a new PKEY with the public and private bits. + let private_params = PKeyMlDsaParams::::from_pkey(&key).unwrap(); + let key_priv = PKeyMlDsaBuilder::::new( + variant, + public_params.public_key().unwrap(), + Some(private_params.private_key().unwrap()), + ) + .unwrap() + .build() + .unwrap(); + + // And redo the signature and verification. + let mut signature = vec![]; + let mut ctx = PkeyCtx::new(&key_priv).unwrap(); + ctx.sign_message_init(&mut algo).unwrap(); + ctx.sign_to_vec(&data[..], &mut signature).unwrap(); + + // Verify good version with the public PKEY. + ctx.verify_message_init(&mut algo).unwrap(); + let valid = ctx.verify(&data[..], &signature); + assert!(matches!(valid, Ok(true))); + assert!(ErrorStack::get().errors().is_empty()); + + // Derive a new PKEY from the private seed. + let key_priv = PKeyMlDsaBuilder::::from_seed( + variant, + private_params.private_key_seed().unwrap(), + ) + .unwrap() + .build() + .unwrap(); + + // And redo the signature and verification. + let mut signature = vec![]; + let mut ctx = PkeyCtx::new(&key_priv).unwrap(); + ctx.sign_message_init(&mut algo).unwrap(); + ctx.sign_to_vec(&data[..], &mut signature).unwrap(); + + // Verify good version with the public PKEY. + ctx.verify_message_init(&mut algo).unwrap(); + let valid = ctx.verify(&data[..], &signature); + assert!(matches!(valid, Ok(true))); + assert!(ErrorStack::get().errors().is_empty()); } } From 13f396e2fa48ee8e9eebc6988d29684a5d24e35f Mon Sep 17 00:00:00 2001 From: Justus Winter Date: Mon, 19 May 2025 12:14:53 +0200 Subject: [PATCH 26/27] fixup! Add support for ML-KEM. --- openssl/src/pkey_ml_kem.rs | 31 ++++++++++++++++++++++++++++++- 1 file changed, 30 insertions(+), 1 deletion(-) diff --git a/openssl/src/pkey_ml_kem.rs b/openssl/src/pkey_ml_kem.rs index 37db7da0d..4aaeb8f5e 100644 --- a/openssl/src/pkey_ml_kem.rs +++ b/openssl/src/pkey_ml_kem.rs @@ -251,10 +251,39 @@ mod tests { assert_eq!(genkey0, genkey1); + // Decapsulate with a PKEY derived from the private parameters. + let private_params = PKeyMlKemParams::::from_pkey(&key).unwrap(); + let key_priv = PKeyMlKemBuilder::::new( + variant, + public_params.public_key().unwrap(), + Some(private_params.private_key().unwrap()), + ) + .unwrap() + .build() + .unwrap(); + let mut genkey1 = vec![]; + let mut ctx = PkeyCtx::new(&key_priv).unwrap(); + ctx.decapsulate_init().unwrap(); + ctx.decapsulate_to_vec(&wrappedkey, &mut genkey1).unwrap(); + assert_eq!(genkey0, genkey1); + + // Decapsulate with a PKEY derived from the private key seed. + let key_priv = PKeyMlKemBuilder::::from_seed( + variant, + private_params.private_key_seed().unwrap(), + ) + .unwrap() + .build() + .unwrap(); + let mut genkey1 = vec![]; + let mut ctx = PkeyCtx::new(&key_priv).unwrap(); + ctx.decapsulate_init().unwrap(); + ctx.decapsulate_to_vec(&wrappedkey, &mut genkey1).unwrap(); + assert_eq!(genkey0, genkey1); + // Note that we can get the public parameter from the // PKeyMlKemParams:: as well. The same is not true // for ML-DSA, for example. - let private_params = PKeyMlKemParams::::from_pkey(&key).unwrap(); assert_eq!( public_params.public_key().unwrap(), private_params.public_key().unwrap() From 4ada1c1d7c264e052e7bb71ecddbd196a5c2a0c7 Mon Sep 17 00:00:00 2001 From: Justus Winter Date: Mon, 19 May 2025 12:15:04 +0200 Subject: [PATCH 27/27] fixup! Add support for SLH-DSA. --- openssl/src/pkey_slh_dsa.rs | 56 +++++++++++++++++++++++++++---------- 1 file changed, 41 insertions(+), 15 deletions(-) diff --git a/openssl/src/pkey_slh_dsa.rs b/openssl/src/pkey_slh_dsa.rs index 32d787cb2..a51330743 100644 --- a/openssl/src/pkey_slh_dsa.rs +++ b/openssl/src/pkey_slh_dsa.rs @@ -83,18 +83,11 @@ pub struct PKeySlhDsaBuilder { } impl PKeySlhDsaBuilder { - /// Creates a new `PKeySlhDsaBuilder` to build SLH-DSA private or - /// public keys. - pub fn new( - variant: Variant, - public: &[u8], - private: Option<&[u8]>, - ) -> Result, ErrorStack> { + /// Creates a new `PKeySlhDsaBuilder` to build SLH-DSA public + /// keys. + pub fn new_public(variant: Variant, public: &[u8]) -> Result, ErrorStack> { let bld = OsslParamBuilder::new()?; bld.add_octet_string(OSSL_PKEY_PARAM_PUB_KEY, public)?; - if let Some(private) = private { - bld.add_octet_string(OSSL_PKEY_PARAM_PRIV_KEY, private)? - }; Ok(PKeySlhDsaBuilder:: { bld, variant, @@ -102,11 +95,14 @@ impl PKeySlhDsaBuilder { }) } - /// Creates a new `PKeySlhDsaBuilder` to build SLH-DSA private keys - /// from a seed. - pub fn from_seed(variant: Variant, seed: &[u8]) -> Result, ErrorStack> { + /// Creates a new `PKeySlhDsaBuilder` to build SLH-DSA private + /// keys. + pub fn new_private( + variant: Variant, + private: &[u8], + ) -> Result, ErrorStack> { let bld = OsslParamBuilder::new()?; - bld.add_octet_string(OSSL_PKEY_PARAM_SEED, seed)?; + bld.add_octet_string(OSSL_PKEY_PARAM_PRIV_KEY, private)?; Ok(PKeySlhDsaBuilder:: { bld, variant, @@ -326,7 +322,7 @@ mod tests { // Derive a new PKEY with only the public bits. let public_params = PKeySlhDsaParams::::from_pkey(&key).unwrap(); let key_pub = - PKeySlhDsaBuilder::::new(variant, public_params.public_key().unwrap(), None) + PKeySlhDsaBuilder::::new_public(variant, public_params.public_key().unwrap()) .unwrap() .build() .unwrap(); @@ -344,5 +340,35 @@ mod tests { let valid = ctx.verify(&bad_data[..], &signature); assert!(matches!(valid, Ok(false) | Err(_))); assert!(ErrorStack::get().errors().is_empty()); + + // Derive a new PKEY with the public and private bits. + let private_params = PKeySlhDsaParams::::from_pkey(&key).unwrap(); + let key_priv = PKeySlhDsaBuilder::::new_private( + variant, + private_params.private_key().unwrap(), + ) + .unwrap() + .build() + .unwrap(); + + // And redo the signature and verification. + let mut signature = vec![]; + let mut ctx = PkeyCtx::new(&key_priv).unwrap(); + ctx.sign_message_init(&mut algo).unwrap(); + ctx.sign_to_vec(&data[..], &mut signature).unwrap(); + + // Verify good version with the public PKEY. + let mut ctx = PkeyCtx::new(&key_pub).unwrap(); + ctx.verify_message_init(&mut algo).unwrap(); + let valid = ctx.verify(&data[..], &signature); + assert!(matches!(valid, Ok(true))); + assert!(ErrorStack::get().errors().is_empty()); + + // Verify good version with the private PKEY. + let mut ctx = PkeyCtx::new(&key_priv).unwrap(); + ctx.verify_message_init(&mut algo).unwrap(); + let valid = ctx.verify(&data[..], &signature); + assert!(matches!(valid, Ok(true))); + assert!(ErrorStack::get().errors().is_empty()); } }