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/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..77ae232d0 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( @@ -583,6 +610,14 @@ 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, @@ -591,6 +626,14 @@ 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, @@ -622,6 +665,28 @@ 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! { @@ -663,3 +728,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/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-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-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/build.rs b/openssl/build.rs index d6d65798f..edec8978e 100644 --- a/openssl/build.rs +++ b/openssl/build.rs @@ -46,6 +46,8 @@ 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() { println!("cargo:rustc-cfg=libressl"); @@ -169,5 +171,11 @@ 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"); + } } } 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, diff --git a/openssl/src/lib.rs b/openssl/src/lib.rs index 1afe5de38..23f79e5bc 100644 --- a/openssl/src/lib.rs +++ b/openssl/src/lib.rs @@ -177,18 +177,28 @@ 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)))] 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(ossl350)] +pub mod pkey_slh_dsa; #[cfg(ossl300)] pub mod provider; 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/ossl_param.rs b/openssl/src/ossl_param.rs new file mode 100644 index 000000000..ec00b7ab6 --- /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! + +// 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; +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` + #[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` + #[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! + #[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! + #[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/openssl/src/pkey_ctx.rs b/openssl/src/pkey_ctx.rs index aa39a0f9c..5638b7ecd 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 @@ -173,6 +197,18 @@ where Ok(()) } + /// 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> { + 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] @@ -184,6 +220,26 @@ where Ok(()) } + /// 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, + 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] @@ -226,6 +282,50 @@ where Ok(len) } + /// 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> { + 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`]. + #[cfg(ossl300)] + 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 @@ -305,6 +405,18 @@ where Ok(()) } + /// 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> { + 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] @@ -316,6 +428,25 @@ where Ok(()) } + /// 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, + ) -> 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> @@ -360,6 +491,39 @@ where Ok(len) } + /// 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 { + 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`]. + #[cfg(ossl300)] + 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 @@ -756,6 +920,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 +958,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 +1293,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(); + } } diff --git a/openssl/src/pkey_ml_dsa.rs b/openssl/src/pkey_ml_dsa.rs new file mode 100644 index 000000000..558527590 --- /dev/null +++ b/openssl/src/pkey_ml_dsa.rs @@ -0,0 +1,320 @@ +//! 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 `PKeyMlDsaBuilder` 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 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(PKeyMlDsaParams:: { + params: OsslParam::from_ptr(params), + _m: ::std::marker::PhantomData, + }) + } + } +} + +impl PKeyMlDsaParams { + /// 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) + } + + /// 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 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_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()); + + // 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()); + } +} diff --git a/openssl/src/pkey_ml_kem.rs b/openssl/src/pkey_ml_kem.rs new file mode 100644 index 000000000..4aaeb8f5e --- /dev/null +++ b/openssl/src/pkey_ml_kem.rs @@ -0,0 +1,292 @@ +//! 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 `PKeyMlKemBuilder` 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); + + // 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. + assert_eq!( + public_params.public_key().unwrap(), + private_params.public_key().unwrap() + ); + } +} diff --git a/openssl/src/pkey_slh_dsa.rs b/openssl/src/pkey_slh_dsa.rs new file mode 100644 index 000000000..a51330743 --- /dev/null +++ b/openssl/src/pkey_slh_dsa.rs @@ -0,0 +1,374 @@ +//! 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 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)?; + Ok(PKeySlhDsaBuilder:: { + bld, + variant, + _m: ::std::marker::PhantomData, + }) + } + + /// 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_PRIV_KEY, private)?; + 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 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 `PKeySlhDsaBuilder` to generate a new SLH-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 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()?; + 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 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) + } +} + +pub struct PKeySlhDsaParams { + params: OsslParam, + _m: ::std::marker::PhantomData, +} + +impl PKeySlhDsaParams { + /// Creates a new `PKeySlhDsaParams` from existing SLH-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 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) + } + + /// 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 SLH-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_public(variant, public_params.public_key().unwrap()) + .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()); + + // 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()); + } +} diff --git a/openssl/src/signature.rs b/openssl/src/signature.rs new file mode 100644 index 000000000..eb687b772 --- /dev/null +++ b/openssl/src/signature.rs @@ -0,0 +1,110 @@ +//! 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}; +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) + } +} + +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 { + unsafe { + Ok(Signature(cvt_p(ffi::EVP_SIGNATURE_fetch( + ptr::null_mut(), + variant.as_cstr().as_ptr(), + ptr::null(), + ))?)) + } + } + + /// 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)] +mod tests { + + // 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)] + #[test] + fn test_alloc_free() { + let sig = Signature::for_ml_dsa(crate::pkey_ml_dsa::Variant::MlDsa44).unwrap(); + drop(sig); + } +} 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");