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
198 changes: 198 additions & 0 deletions openssl/src/ossl_param.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,198 @@
//! 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 individual `OSSL_PARAM` element identified by the key
/// in the `OsslParamArray` array and returns a reference
/// to it.
#[corresponds(OSSL_PARAM_locate)]
#[allow(dead_code)] // TODO: remove when when used by ML-DSA / ML-KEM
pub(crate) 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 OSSL_PARAM at the top of this
/// `OsslParamArray` reference.
Copy link
Collaborator

Choose a reason for hiding this comment

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

I'm not sure this is a useful behavior -- I think we really need separate types for these.

(I think it logically follows that locate has the wrong return type ATM.)

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Gotcha. I’ll think on that.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I separated out the overall OSSL_PARAM array and added a separate struct for an interior individual OSSL_PARAM struct (wrapping an internal reference tied to the lifetime over the entire array). I think this should be cleaner.

#[corresponds(OSSL_PARAM_get_BN)]
#[allow(dead_code)] // TODO: remove when when used by ML-DSA / ML-KEM
pub(crate) 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 OSSL_PARAM at the top of this
/// `OsslParamArray` reference.
#[corresponds(OSSL_PARAM_get_utf8_string)]
#[allow(dead_code)] // TODO: remove when when used by ML-DSA / ML-KEM
pub(crate) 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 OSSL_PARAM at the
/// top of this `OsslParamArray` reference.
#[corresponds(OSSL_PARAM_get_octet_string)]
#[allow(dead_code)] // TODO: remove when when used by ML-DSA / ML-KEM
pub(crate) 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(crate) 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(crate) 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(crate) 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(crate) fn add_utf8_string(&mut self, key: &CStr, buf: &str) -> Result<(), ErrorStack> {
Copy link
Collaborator

Choose a reason for hiding this comment

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

We need to tie the key and buf lifetimes to the output I believe? (Sorry, I meant to check on this earlier).

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, it's even there in the comment. I'll tie them all together.

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(&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(crate) 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