Skip to content
111 changes: 46 additions & 65 deletions openssl/src/kdf.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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(
Expand Down Expand Up @@ -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::<ffi::OSSL_PARAM>::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,
Expand Down
2 changes: 2 additions & 0 deletions openssl/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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)))]
Expand Down
193 changes: 193 additions & 0 deletions openssl/src/ossl_param.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,193 @@
//! 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::{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! {
// 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;
}

impl OsslParamArrayRef {
/// Locates the `OsslParamArray` in the `OsslParamArray` array
#[corresponds(OSSL_PARAM_locate)]
#[allow(dead_code)] // TODO: remove when when used by ML-DSA / ML-KEM
pub fn locate(&self, key: &CStr) -> Result<&OsslParamArrayRef, ErrorStack> {
unsafe {
let param = cvt_p(ffi::OSSL_PARAM_locate(self.as_ptr(), key.as_ptr()))?;
Ok(OsslParamArrayRef::from_ptr(param))
}
}

/// Get `BigNum` from the current `OsslParamArray`
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What does this mean? I think this operation only makes sense in the context of a single OSSL_PARAM?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You're right. I'll clarify these comments.

#[corresponds(OSSL_PARAM_get_BN)]
#[allow(dead_code)] // TODO: remove when when used by ML-DSA / ML-KEM
pub fn get_bn(&self) -> Result<BigNum, ErrorStack> {
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 `OsslParamArray`
#[corresponds(OSSL_PARAM_get_utf8_string)]
#[allow(dead_code)] // TODO: remove when when used by ML-DSA / ML-KEM
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 `OsslParamArray`
#[corresponds(OSSL_PARAM_get_octet_string)]
#[allow(dead_code)] // TODO: remove when when used by ML-DSA / ML-KEM
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 `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 fn new() -> Result<OsslParamBuilder, ErrorStack> {
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 fn to_param(&mut self) -> Result<OsslParamArray, ErrorStack> {
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 fn add_bn(&mut self, key: &CStr, bn: &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 fn add_utf8_string(&mut self, key: &CStr, buf: &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 fn add_octet_string(&mut self, key: &CStr, buf: &[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 fn add_uint(&mut self, key: &CStr, val: u32) -> Result<(), ErrorStack> {
unsafe {
cvt(ffi::OSSL_PARAM_BLD_push_uint(
self.as_ptr(),
key.as_ptr(),
val as c_uint,
))
.map(|_| ())
}
}
}
Loading