diff --git a/openssl/src/kdf.rs b/openssl/src/kdf.rs index 79e0eff49..885ca1a66 100644 --- a/openssl/src/kdf.rs +++ b/openssl/src/kdf.rs @@ -25,15 +25,28 @@ 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::ffi::CStr; - 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; + + // Safety: these all have null terminators. + // We cen remove these CStr::from_bytes_with_nul_unchecked calls + // when we upgrade to Rust 1.77+ with literal c"" syntax. + + const OSSL_KDF_PARAM_PASSWORD: &CStr = unsafe { CStr::from_bytes_with_nul_unchecked(b"pass\0") }; + const OSSL_KDF_PARAM_SALT: &CStr = unsafe { CStr::from_bytes_with_nul_unchecked(b"salt\0") }; + const OSSL_KDF_PARAM_SECRET: &CStr = unsafe { CStr::from_bytes_with_nul_unchecked(b"secret\0") }; + const OSSL_KDF_PARAM_ITER: &CStr = unsafe { CStr::from_bytes_with_nul_unchecked(b"iter\0") }; + const OSSL_KDF_PARAM_SIZE: &CStr = unsafe { CStr::from_bytes_with_nul_unchecked(b"size\0") }; + const OSSL_KDF_PARAM_THREADS: &CStr = unsafe { CStr::from_bytes_with_nul_unchecked(b"threads\0") }; + const OSSL_KDF_PARAM_ARGON2_AD: &CStr = unsafe { CStr::from_bytes_with_nul_unchecked(b"ad\0") }; + const OSSL_KDF_PARAM_ARGON2_LANES: &CStr = unsafe { CStr::from_bytes_with_nul_unchecked(b"lanes\0") }; + const OSSL_KDF_PARAM_ARGON2_MEMCOST: &CStr = unsafe { CStr::from_bytes_with_nul_unchecked(b"memcost\0") }; #[allow(clippy::too_many_arguments)] pub fn argon2d( @@ -94,72 +107,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 - // argon2'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 mut 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, kdf_identifier.as_ptr() as *const c_char, 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..1c6ed50e7 --- /dev/null +++ b/openssl/src/ossl_param.rs @@ -0,0 +1,153 @@ +//! OSSL_PARAM management for OpenSSL 3.* +//! +//! The OSSL_PARAM structure represents an array of generic +//! attributes that can represent various +//! properties in OpenSSL, including keys and operations. +//! +//! This is always represented as an array of OSSL_PARAM +//! structures, terminated by an entry with a NULL key. +//! +//! For convinience, the OSSL_PARAM_BLD builder can be used to +//! dynamically construct these structures. +//! +//! Note, that this module is available only in OpenSSL 3.* and +//! only internally for this crate. + +use crate::bn::BigNumRef; +use crate::error::ErrorStack; +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; + +foreign_type_and_impl_send_sync! { + // This is the singular type, but it is always allocated + // and used as an array of such types. + type CType = ffi::OSSL_PARAM; + // OSSL_PARMA_free correctly frees the entire array. + fn drop = ffi::OSSL_PARAM_free; + + /// `OsslParamArray` constructed using `OsslParamBuilder`. + /// Internally this is a pointer to an array of the OSSL_PARAM + /// structures. + pub struct OsslParamArray; + /// Reference to `OsslParamArray`. + pub struct OsslParamArrayRef; +} + +foreign_type_and_impl_send_sync! { + type CType = ffi::OSSL_PARAM_BLD; + fn drop = ffi::OSSL_PARAM_BLD_free; + + /// Builder used to construct `OsslParamArray`. + pub struct OsslParamBuilder; + /// Reference to `OsslParamBuilder`. + pub struct OsslParamBuilderRef; +} + +impl OsslParamBuilder { + /// Returns a builder for an OsslParamArray. + /// + /// The array is initially empty. + #[corresponds(OSSL_PARAM_BLD_new)] + #[cfg_attr(any(not(ossl320), osslconf = "OPENSSL_NO_ARGON2"), allow(dead_code))] + pub(crate) fn new() -> Result { + unsafe { + ffi::init(); + + cvt_p(ffi::OSSL_PARAM_BLD_new()).map(OsslParamBuilder) + } + } + + /// Constructs the `OsslParamArray` and clears this builder. + #[corresponds(OSSL_PARAM_BLD_to_param)] + #[cfg_attr(any(not(ossl320), osslconf = "OPENSSL_NO_ARGON2"), allow(dead_code))] + #[allow(clippy::wrong_self_convention)] + pub(crate) fn to_param(&mut self) -> Result { + unsafe { + let params = cvt_p(ffi::OSSL_PARAM_BLD_to_param(self.0))?; + Ok(OsslParamArray::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)] + #[allow(dead_code)] // TODO: remove when when used by ML-DSA / ML-KEM + pub(crate) fn add_bn<'a>( + &'a mut self, + key: &'a CStr, + bn: &'a BigNumRef, + ) -> Result<(), ErrorStack> { + unsafe { + cvt(ffi::OSSL_PARAM_BLD_push_BN( + self.as_ptr(), + key.as_ptr(), + 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)] + #[allow(dead_code)] // TODO: remove when when used by ML-DSA / ML-KEM + pub(crate) fn add_utf8_string<'a>( + &'a mut self, + key: &'a CStr, + buf: &'a str, + ) -> Result<(), ErrorStack> { + unsafe { + cvt(ffi::OSSL_PARAM_BLD_push_utf8_string( + self.as_ptr(), + key.as_ptr(), + 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)] + #[cfg_attr(any(not(ossl320), osslconf = "OPENSSL_NO_ARGON2"), allow(dead_code))] + pub(crate) fn add_octet_string<'a>( + &'a mut self, + key: &'a CStr, + buf: &'a [u8], + ) -> Result<(), ErrorStack> { + unsafe { + cvt(ffi::OSSL_PARAM_BLD_push_octet_string( + self.as_ptr(), + key.as_ptr(), + 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)] + #[cfg_attr(any(not(ossl320), osslconf = "OPENSSL_NO_ARGON2"), allow(dead_code))] + pub(crate) fn add_uint<'a>(&'a mut self, key: &'a CStr, val: u32) -> Result<(), ErrorStack> { + unsafe { + cvt(ffi::OSSL_PARAM_BLD_push_uint( + self.as_ptr(), + key.as_ptr(), + val as c_uint, + )) + .map(|_| ()) + } + } +}