From f76224dfdd4e7a10c30bb0dcfdb4b98620dc2556 Mon Sep 17 00:00:00 2001 From: Huw Jones Date: Wed, 6 Aug 2025 14:46:12 +0100 Subject: [PATCH 01/21] sys/params: add OSSL_PARAM_BLD symbols --- openssl-sys/build/run_bindgen.rs | 1 + openssl-sys/src/handwritten/params.rs | 52 +++++++++++++++++++++++++-- openssl-sys/src/handwritten/types.rs | 5 ++- systest/build.rs | 3 +- 4 files changed, 56 insertions(+), 5 deletions(-) diff --git a/openssl-sys/build/run_bindgen.rs b/openssl-sys/build/run_bindgen.rs index cba6b5556..754caf7f1 100644 --- a/openssl-sys/build/run_bindgen.rs +++ b/openssl-sys/build/run_bindgen.rs @@ -59,6 +59,7 @@ const INCLUDES: &str = " #if OPENSSL_VERSION_NUMBER >= 0x30000000 #include +#include #endif #if OPENSSL_VERSION_NUMBER >= 0x30200000 diff --git a/openssl-sys/src/handwritten/params.rs b/openssl-sys/src/handwritten/params.rs index 542cef337..bc2489078 100644 --- a/openssl-sys/src/handwritten/params.rs +++ b/openssl-sys/src/handwritten/params.rs @@ -1,16 +1,62 @@ use super::super::*; use libc::*; +#[cfg(ossl300)] extern "C" { - #[cfg(ossl300)] 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_BLD_new() -> *mut OSSL_PARAM_BLD; + pub fn OSSL_PARAM_BLD_free(bld: *mut OSSL_PARAM_BLD); + pub fn OSSL_PARAM_BLD_to_param(bld: *mut OSSL_PARAM_BLD) -> *mut OSSL_PARAM; + pub fn OSSL_PARAM_BLD_push_uint( + bld: *mut OSSL_PARAM_BLD, + key: *const c_char, + val: c_uint, + ) -> c_int; + pub fn OSSL_PARAM_BLD_push_size_t( + bld: *mut OSSL_PARAM_BLD, + key: *const c_char, + val: size_t, + ) -> c_int; + 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_BN_pad( + bld: *mut OSSL_PARAM_BLD, + key: *const c_char, + bn: *const BIGNUM, + sz: size_t, + ) -> c_int; + pub fn OSSL_PARAM_BLD_push_utf8_string( + bld: *mut OSSL_PARAM_BLD, + key: *const c_char, + buf: *const c_char, + bsize: size_t, + ) -> c_int; + pub fn OSSL_PARAM_BLD_push_utf8_ptr( + bld: *mut OSSL_PARAM_BLD, + key: *const c_char, + buf: *mut c_char, + bsize: size_t, + ) -> c_int; + pub fn OSSL_PARAM_BLD_push_octet_string( + bld: *mut OSSL_PARAM_BLD, + key: *const c_char, + buf: *const c_void, + bsize: size_t, + ) -> c_int; + pub fn OSSL_PARAM_BLD_push_octet_ptr( + bld: *mut OSSL_PARAM_BLD, + key: *const c_char, + buf: *mut c_void, + bsize: size_t, + ) -> c_int; } diff --git a/openssl-sys/src/handwritten/types.rs b/openssl-sys/src/handwritten/types.rs index a4b90d789..576811c02 100644 --- a/openssl-sys/src/handwritten/types.rs +++ b/openssl-sys/src/handwritten/types.rs @@ -1134,12 +1134,15 @@ pub enum OSSL_LIB_CTX {} #[repr(C)] pub struct OSSL_PARAM { key: *const c_char, - data_type: c_uchar, + data_type: c_uint, data: *mut c_void, data_size: size_t, return_size: size_t, } +#[cfg(ossl300)] +pub enum OSSL_PARAM_BLD {} + #[cfg(ossl300)] pub enum EVP_KDF {} #[cfg(ossl300)] diff --git a/systest/build.rs b/systest/build.rs index 22fc6b836..1b653cd16 100644 --- a/systest/build.rs +++ b/systest/build.rs @@ -83,7 +83,8 @@ fn main() { } if version >= 0x30000000 { - cfg.header("openssl/provider.h"); + cfg.header("openssl/provider.h") + .header("openssl/param_build.h"); } if version >= 0x30200000 { cfg.header("openssl/thread.h"); From c396f7879931469f151c1d7ce870abe59f855f04 Mon Sep 17 00:00:00 2001 From: Huw Jones Date: Wed, 6 Aug 2025 14:51:24 +0100 Subject: [PATCH 02/21] sys/params: add OSSL_PARAM dup, free, merge, locate, locate_const --- openssl-sys/build/run_bindgen.rs | 1 + openssl-sys/src/handwritten/params.rs | 11 +++++++++++ systest/build.rs | 1 + 3 files changed, 13 insertions(+) diff --git a/openssl-sys/build/run_bindgen.rs b/openssl-sys/build/run_bindgen.rs index 754caf7f1..e016a092b 100644 --- a/openssl-sys/build/run_bindgen.rs +++ b/openssl-sys/build/run_bindgen.rs @@ -59,6 +59,7 @@ const INCLUDES: &str = " #if OPENSSL_VERSION_NUMBER >= 0x30000000 #include +#include #include #endif diff --git a/openssl-sys/src/handwritten/params.rs b/openssl-sys/src/handwritten/params.rs index bc2489078..8c5c45b77 100644 --- a/openssl-sys/src/handwritten/params.rs +++ b/openssl-sys/src/handwritten/params.rs @@ -3,6 +3,17 @@ use libc::*; #[cfg(ossl300)] extern "C" { + pub fn OSSL_PARAM_dup(params: *const OSSL_PARAM) -> *mut OSSL_PARAM; + pub fn OSSL_PARAM_free(params: *mut OSSL_PARAM); + pub fn OSSL_PARAM_merge( + params: *const OSSL_PARAM, + params1: *const OSSL_PARAM, + ) -> *mut OSSL_PARAM; + pub fn OSSL_PARAM_locate(params: *mut OSSL_PARAM, key: *const c_char) -> *mut OSSL_PARAM; + pub fn OSSL_PARAM_locate_const( + params: *const OSSL_PARAM, + key: *const c_char, + ) -> *const OSSL_PARAM; pub fn OSSL_PARAM_construct_uint(key: *const c_char, buf: *mut c_uint) -> OSSL_PARAM; pub fn OSSL_PARAM_construct_end() -> OSSL_PARAM; pub fn OSSL_PARAM_construct_octet_string( diff --git a/systest/build.rs b/systest/build.rs index 1b653cd16..eddcf6076 100644 --- a/systest/build.rs +++ b/systest/build.rs @@ -84,6 +84,7 @@ fn main() { if version >= 0x30000000 { cfg.header("openssl/provider.h") + .header("openssl/params.h") .header("openssl/param_build.h"); } if version >= 0x30200000 { From 737f315a5ac20f948e3de3cd8328f02d04f4a7f0 Mon Sep 17 00:00:00 2001 From: Huw Jones Date: Wed, 6 Aug 2025 17:16:30 +0100 Subject: [PATCH 03/21] utils: add c_str helper --- openssl/src/pkey_ctx.rs | 8 ++++---- openssl/src/util.rs | 9 +++++++++ 2 files changed, 13 insertions(+), 4 deletions(-) diff --git a/openssl/src/pkey_ctx.rs b/openssl/src/pkey_ctx.rs index 1b58108ae..eb4be4df7 100644 --- a/openssl/src/pkey_ctx.rs +++ b/openssl/src/pkey_ctx.rs @@ -73,6 +73,8 @@ use crate::nid::Nid; use crate::pkey::{HasPrivate, HasPublic, Id, PKey, PKeyRef, Params, Private}; use crate::rsa::Padding; use crate::sign::RsaPssSaltlen; +#[cfg(ossl320)] +use crate::util::c_str; use crate::{cvt, cvt_p}; use cfg_if::cfg_if; use foreign_types::{ForeignType, ForeignTypeRef}; @@ -82,8 +84,6 @@ use libc::c_int; use libc::c_uint; use openssl_macros::corresponds; use std::convert::TryFrom; -#[cfg(ossl320)] -use std::ffi::CStr; use std::ptr; /// HKDF modes of operation. @@ -876,7 +876,7 @@ impl PkeyCtxRef { #[cfg(ossl320)] #[corresponds(EVP_PKEY_CTX_set_params)] pub fn set_nonce_type(&mut self, nonce_type: NonceType) -> Result<(), ErrorStack> { - let nonce_field_name = CStr::from_bytes_with_nul(b"nonce-type\0").unwrap(); + let nonce_field_name = c_str(b"nonce-type\0"); let mut nonce_type = nonce_type.0; unsafe { let param_nonce = @@ -898,7 +898,7 @@ impl PkeyCtxRef { #[cfg(ossl320)] #[corresponds(EVP_PKEY_CTX_get_params)] pub fn nonce_type(&mut self) -> Result { - let nonce_field_name = CStr::from_bytes_with_nul(b"nonce-type\0").unwrap(); + let nonce_field_name = c_str(b"nonce-type\0"); let mut nonce_type: c_uint = 0; unsafe { let param_nonce = diff --git a/openssl/src/util.rs b/openssl/src/util.rs index c903a3209..5cefdefd2 100644 --- a/openssl/src/util.rs +++ b/openssl/src/util.rs @@ -3,6 +3,7 @@ use crate::util; use foreign_types::{ForeignType, ForeignTypeRef}; use libc::{c_char, c_int, c_void}; use std::any::Any; +use std::ffi::CStr; use std::panic::{self, AssertUnwindSafe}; use std::slice; @@ -116,3 +117,11 @@ pub unsafe fn from_raw_parts_mut<'a, T>(data: *mut T, len: usize) -> &'a mut [T] slice::from_raw_parts_mut(data, len) } } + +/// Converts a byte slice to a C string. +/// +/// String should be null-terminated. +#[allow(dead_code)] +pub fn c_str(s: &[u8]) -> &CStr { + CStr::from_bytes_with_nul(s).unwrap() +} From 1d81632ebea67bd3d47b2d42073f5e7e28e7134f Mon Sep 17 00:00:00 2001 From: Huw Jones Date: Wed, 6 Aug 2025 14:50:36 +0100 Subject: [PATCH 04/21] add internal wrapper around OSSL_PARAM_BLD --- openssl/src/lib.rs | 2 + openssl/src/params.rs | 168 ++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 170 insertions(+) create mode 100644 openssl/src/params.rs diff --git a/openssl/src/lib.rs b/openssl/src/lib.rs index 1afe5de38..21407e5e2 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 params; pub mod pkcs12; pub mod pkcs5; #[cfg(not(any(boringssl, awslc)))] diff --git a/openssl/src/params.rs b/openssl/src/params.rs new file mode 100644 index 000000000..cf80c44a2 --- /dev/null +++ b/openssl/src/params.rs @@ -0,0 +1,168 @@ +use crate::bn::BigNumRef; +use crate::error::ErrorStack; +use crate::{cvt, cvt_p}; +use foreign_types::ForeignTypeRef; +use libc::{c_char, c_void}; +use openssl_macros::corresponds; +use std::ffi::CStr; +use std::marker::PhantomData; + +pub struct ParamBuilder<'a>(*mut ffi::OSSL_PARAM_BLD, PhantomData<&'a ()>); + +impl Drop for ParamBuilder<'_> { + #[inline] + fn drop(&mut self) { + unsafe { ffi::OSSL_PARAM_BLD_free(self.0) } + } +} + +unsafe impl Send for ParamBuilder<'_> {} +unsafe impl Sync for ParamBuilder<'_> {} + +impl<'a, 'b> ParamBuilder<'a> { + /// Creates a new `ParamBuilder`. + #[corresponds[OSSL_PARAM_BLD_new]] + pub fn new() -> Self { + unsafe { ParamBuilder(ffi::OSSL_PARAM_BLD_new(), PhantomData) } + } + + /// Push a BigNum parameter into the builder. + #[corresponds[OSSL_PARAM_BLD_push_BN]] + pub fn push_bignum(&mut self, key: &'b CStr, bn: &'a BigNumRef) -> Result<(), ErrorStack> { + cvt(unsafe { ffi::OSSL_PARAM_BLD_push_BN(self.0, key.as_ptr(), bn.as_ptr()) }).map(|_| ()) + } + + /// Push a UTF-8 String parameter into the builder. + #[corresponds[OSSL_PARAM_BLD_push_utf8_string]] + pub fn push_utf8_string(&mut self, key: &'b CStr, string: &'a str) -> Result<(), ErrorStack> { + let value = string.as_bytes(); + cvt(unsafe { + ffi::OSSL_PARAM_BLD_push_utf8_string( + self.0, + key.as_ptr(), + value.as_ptr().cast::(), + value.len(), + ) + }) + .map(|_| ()) + } + + /// Push a byte string parameter into the builder. + #[corresponds[OSSL_PARAM_BLD_push_octet_string]] + pub fn push_byte_string(&mut self, key: &'b CStr, value: &'a [u8]) -> Result<(), ErrorStack> { + cvt(unsafe { + ffi::OSSL_PARAM_BLD_push_octet_string( + self.0, + key.as_ptr(), + value.as_ptr().cast::(), + value.len(), + ) + }) + .map(|_| ()) + } + + /// Push a uint parameter into the builder. + #[corresponds[OSSL_PARAM_BLD_push_uint]] + #[allow(dead_code)] + pub fn push_uint(self, key: &'b CStr, val: u32) -> Result { + cvt(unsafe { ffi::OSSL_PARAM_BLD_push_uint(self.0, key.as_ptr(), val) })?; + Ok(self) + } + + /// Push a size_t parameter into the builder. + #[corresponds[OSSL_PARAM_BLD_push_size_t]] + #[allow(dead_code)] + pub fn push_size_t(self, key: &'b CStr, val: usize) -> Result { + cvt(unsafe { ffi::OSSL_PARAM_BLD_push_size_t(self.0, key.as_ptr(), val) })?; + Ok(self) + } + + /// Build a `Params` array from the builder consuming the builder. + #[corresponds(OSSL_PARAM_BLD_to_param)] + pub fn build(self) -> Result<*mut ffi::OSSL_PARAM, ErrorStack> { + cvt_p(unsafe { ffi::OSSL_PARAM_BLD_to_param(self.0) }) + } +} + +#[cfg(test)] +mod test { + use super::*; + use crate::bn::BigNum; + use crate::util::c_str; + + fn assert_param(params: *mut ffi::OSSL_PARAM, key: &CStr, is_null: bool) { + let param = unsafe { ffi::OSSL_PARAM_locate_const(params, key.as_ptr()) }; + if is_null { + assert!(param.is_null(), "Unexpectedly found param: {key:?}"); + } else { + assert!(!param.is_null(), "Failed to find param: {key:?}"); + } + } + + #[test] + fn test_param_builder_uint() { + let params = ParamBuilder::new() + .push_uint(c_str(b"nonce-type\0"), 42) + .unwrap() + .build() + .unwrap(); + + assert_param(params, c_str(b"nonce-type\0"), false); + assert_param(params, c_str(b"group\0"), true); + } + + #[test] + fn test_param_builder_size_t() { + let params = ParamBuilder::new() + .push_size_t(c_str(b"size\0"), 42) + .unwrap() + .build() + .unwrap(); + + assert_param(params, c_str(b"size\0"), false); + assert_param(params, c_str(b"out\0"), true); + } + + #[test] + fn test_param_builder_bignum() { + let n = BigNum::from_u32(0xbc747fc5).unwrap(); + let e = BigNum::from_u32(0x10001).unwrap(); + let d = BigNum::from_u32(0x7b133399).unwrap(); + + let mut builder = ParamBuilder::new(); + builder.push_bignum(c_str(b"n\0"), &n).unwrap(); + builder.push_bignum(c_str(b"e\0"), &e).unwrap(); + builder.push_bignum(c_str(b"d\0"), &d).unwrap(); + let params = builder.build().unwrap(); + + for param in [b"n\0", b"e\0", b"d\0"] { + assert_param(params, c_str(param), false); + } + + assert_param(params, c_str(b"group\0"), true); + } + + #[test] + fn test_param_builder_string() { + let mut builder = ParamBuilder::new(); + builder + .push_utf8_string(c_str(b"group\0"), "primve256v1") + .unwrap(); + let params = builder.build().unwrap(); + + assert_param(params, c_str(b"group\0"), false); + assert_param(params, c_str(b"n\0"), true); + } + + #[test] + fn test_param_builder_byte_string() { + let mut builder = ParamBuilder::new(); + builder + .push_byte_string(c_str(b"pass\0"), b"primve256v1") + .unwrap(); + let params = builder.build().unwrap(); + + assert_param(params, c_str(b"pass\0"), false); + assert_param(params, c_str(b"group\0"), true); + } +} From 9a6e3131a4ed0c8a34604505d512d6d9eaac2dbd Mon Sep 17 00:00:00 2001 From: Huw Jones Date: Wed, 6 Aug 2025 14:52:29 +0100 Subject: [PATCH 05/21] add internal wrapper around OSSL_PARAM array --- openssl/src/params.rs | 226 ++++++++++++++++++++++++++++++++++-------- 1 file changed, 184 insertions(+), 42 deletions(-) diff --git a/openssl/src/params.rs b/openssl/src/params.rs index cf80c44a2..bd77e10db 100644 --- a/openssl/src/params.rs +++ b/openssl/src/params.rs @@ -1,12 +1,115 @@ use crate::bn::BigNumRef; use crate::error::ErrorStack; use crate::{cvt, cvt_p}; -use foreign_types::ForeignTypeRef; +use foreign_types::{ForeignType, ForeignTypeRef, Opaque}; use libc::{c_char, c_void}; use openssl_macros::corresponds; use std::ffi::CStr; use std::marker::PhantomData; +pub struct Params<'a>(*mut ffi::OSSL_PARAM, PhantomData<&'a ()>); + +impl<'a> ForeignType for Params<'a> { + type CType = ffi::OSSL_PARAM; + type Ref = ParamsRef<'a>; + + #[inline] + unsafe fn from_ptr(ptr: *mut ffi::OSSL_PARAM) -> Params<'a> { + Self(ptr, PhantomData) + } + + #[inline] + fn as_ptr(&self) -> *mut ffi::OSSL_PARAM { + self.0 + } +} + +impl Drop for Params<'_> { + fn drop(&mut self) { + unsafe { ffi::OSSL_PARAM_free(self.0) }; + } +} + +impl Clone for Params<'_> { + #[inline] + fn clone(&self) -> Self { + Self(unsafe { ffi::OSSL_PARAM_dup(self.0) }, PhantomData) + } +} + +impl<'a> ToOwned for ParamsRef<'a> { + type Owned = Params<'a>; + + #[inline] + fn to_owned(&self) -> Params<'a> { + unsafe { + let handle: *mut ffi::OSSL_PARAM = ffi::OSSL_PARAM_dup(self.as_ptr()); + ForeignType::from_ptr(handle) + } + } +} + +impl<'a> std::ops::Deref for Params<'a> { + type Target = ParamsRef<'a>; + + #[inline] + fn deref(&self) -> &ParamsRef<'a> { + unsafe { ParamsRef::from_ptr(self.as_ptr()) } + } +} + +impl<'a> std::ops::DerefMut for Params<'a> { + #[inline] + fn deref_mut(&mut self) -> &mut ParamsRef<'a> { + unsafe { ParamsRef::from_ptr_mut(self.as_ptr()) } + } +} + +impl<'a> std::borrow::Borrow> for Params<'a> { + #[inline] + fn borrow(&self) -> &ParamsRef<'a> { + self + } +} + +impl<'a> AsRef> for Params<'a> { + #[inline] + fn as_ref(&self) -> &ParamsRef<'a> { + self + } +} + +pub struct ParamsRef<'a>(Opaque, PhantomData<&'a ()>); + +impl ForeignTypeRef for ParamsRef<'_> { + type CType = ffi::OSSL_PARAM; +} + +unsafe impl Send for Params<'_> {} +unsafe impl Send for ParamsRef<'_> {} +unsafe impl Sync for Params<'_> {} +unsafe impl Sync for ParamsRef<'_> {} + +impl<'a> ParamsRef<'a> { + /// Merges two `ParamsRef` objects into a new `Params` object. + #[corresponds(OSSL_PARAM_merge)] + pub fn merge(&self, other: &ParamsRef<'a>) -> Result, ErrorStack> { + cvt_p(unsafe { ffi::OSSL_PARAM_merge(self.as_ptr(), other.as_ptr()) }) + .map(|p| unsafe { Params::from_ptr(p) }) + } + + /// Locate a parameter by the given key. + #[corresponds(OSSL_PARAM_locate_const)] + fn locate(&self, key: &CStr) -> Option<*const ffi::OSSL_PARAM> { + let param = unsafe { ffi::OSSL_PARAM_locate_const(self.as_ptr(), key.as_ptr()) }; + if param.is_null() { + None + } else { + Some(param) + } + } +} + pub struct ParamBuilder<'a>(*mut ffi::OSSL_PARAM_BLD, PhantomData<&'a ()>); impl Drop for ParamBuilder<'_> { @@ -28,13 +131,14 @@ impl<'a, 'b> ParamBuilder<'a> { /// Push a BigNum parameter into the builder. #[corresponds[OSSL_PARAM_BLD_push_BN]] - pub fn push_bignum(&mut self, key: &'b CStr, bn: &'a BigNumRef) -> Result<(), ErrorStack> { - cvt(unsafe { ffi::OSSL_PARAM_BLD_push_BN(self.0, key.as_ptr(), bn.as_ptr()) }).map(|_| ()) + pub fn push_bignum(self, key: &'b CStr, bn: &'a BigNumRef) -> Result { + cvt(unsafe { ffi::OSSL_PARAM_BLD_push_BN(self.0, key.as_ptr(), bn.as_ptr()) })?; + Ok(self) } /// Push a UTF-8 String parameter into the builder. #[corresponds[OSSL_PARAM_BLD_push_utf8_string]] - pub fn push_utf8_string(&mut self, key: &'b CStr, string: &'a str) -> Result<(), ErrorStack> { + pub fn push_utf8_string(self, key: &'b CStr, string: &'a str) -> Result { let value = string.as_bytes(); cvt(unsafe { ffi::OSSL_PARAM_BLD_push_utf8_string( @@ -43,13 +147,13 @@ impl<'a, 'b> ParamBuilder<'a> { value.as_ptr().cast::(), value.len(), ) - }) - .map(|_| ()) + })?; + Ok(self) } /// Push a byte string parameter into the builder. #[corresponds[OSSL_PARAM_BLD_push_octet_string]] - pub fn push_byte_string(&mut self, key: &'b CStr, value: &'a [u8]) -> Result<(), ErrorStack> { + pub fn push_byte_string(self, key: &'b CStr, value: &'a [u8]) -> Result { cvt(unsafe { ffi::OSSL_PARAM_BLD_push_octet_string( self.0, @@ -57,8 +161,8 @@ impl<'a, 'b> ParamBuilder<'a> { value.as_ptr().cast::(), value.len(), ) - }) - .map(|_| ()) + })?; + Ok(self) } /// Push a uint parameter into the builder. @@ -79,8 +183,9 @@ impl<'a, 'b> ParamBuilder<'a> { /// Build a `Params` array from the builder consuming the builder. #[corresponds(OSSL_PARAM_BLD_to_param)] - pub fn build(self) -> Result<*mut ffi::OSSL_PARAM, ErrorStack> { - cvt_p(unsafe { ffi::OSSL_PARAM_BLD_to_param(self.0) }) + pub fn build(self) -> Result, ErrorStack> { + let ptr = cvt_p(unsafe { ffi::OSSL_PARAM_BLD_to_param(self.0) })?; + Ok(unsafe { Params::from_ptr(ptr) }) } } @@ -90,12 +195,10 @@ mod test { use crate::bn::BigNum; use crate::util::c_str; - fn assert_param(params: *mut ffi::OSSL_PARAM, key: &CStr, is_null: bool) { - let param = unsafe { ffi::OSSL_PARAM_locate_const(params, key.as_ptr()) }; - if is_null { - assert!(param.is_null(), "Unexpectedly found param: {key:?}"); - } else { - assert!(!param.is_null(), "Failed to find param: {key:?}"); + fn assert_param(params: &ParamsRef<'_>, key: &CStr, is_null: bool) { + match params.locate(key) { + Some(_) => assert!(!is_null, "Unexpectedly found param: {key:?}"), + None => assert!(is_null, "Failed to find param: {key:?}"), } } @@ -107,8 +210,8 @@ mod test { .build() .unwrap(); - assert_param(params, c_str(b"nonce-type\0"), false); - assert_param(params, c_str(b"group\0"), true); + assert_param(¶ms, c_str(b"nonce-type\0"), false); + assert_param(¶ms, c_str(b"group\0"), true); } #[test] @@ -119,50 +222,89 @@ mod test { .build() .unwrap(); - assert_param(params, c_str(b"size\0"), false); - assert_param(params, c_str(b"out\0"), true); + assert_param(¶ms, c_str(b"size\0"), false); + assert_param(¶ms, c_str(b"out\0"), true); } #[test] fn test_param_builder_bignum() { - let n = BigNum::from_u32(0xbc747fc5).unwrap(); - let e = BigNum::from_u32(0x10001).unwrap(); - let d = BigNum::from_u32(0x7b133399).unwrap(); + let a = BigNum::from_u32(0x01).unwrap(); + let b = BigNum::from_u32(0x02).unwrap(); + let c = BigNum::from_u32(0x03).unwrap(); - let mut builder = ParamBuilder::new(); - builder.push_bignum(c_str(b"n\0"), &n).unwrap(); - builder.push_bignum(c_str(b"e\0"), &e).unwrap(); - builder.push_bignum(c_str(b"d\0"), &d).unwrap(); - let params = builder.build().unwrap(); + let params = ParamBuilder::new() + .push_bignum(c_str(b"a\0"), &a) + .unwrap() + .push_bignum(c_str(b"b\0"), &b) + .unwrap() + .push_bignum(c_str(b"c\0"), &c) + .unwrap() + .build() + .unwrap(); - for param in [b"n\0", b"e\0", b"d\0"] { - assert_param(params, c_str(param), false); + for param in [b"a\0", b"b\0", b"c\0"] { + assert_param(¶ms, c_str(param), false); } - assert_param(params, c_str(b"group\0"), true); + assert_param(¶ms, c_str(b"group\0"), true); } #[test] fn test_param_builder_string() { - let mut builder = ParamBuilder::new(); - builder + let params = ParamBuilder::new() .push_utf8_string(c_str(b"group\0"), "primve256v1") + .unwrap() + .build() .unwrap(); - let params = builder.build().unwrap(); - assert_param(params, c_str(b"group\0"), false); - assert_param(params, c_str(b"n\0"), true); + assert_param(¶ms, c_str(b"group\0"), false); + assert_param(¶ms, c_str(b"n\0"), true); } #[test] fn test_param_builder_byte_string() { - let mut builder = ParamBuilder::new(); - builder + let params = ParamBuilder::new() .push_byte_string(c_str(b"pass\0"), b"primve256v1") + .unwrap() + .build() + .unwrap(); + + assert_param(¶ms, c_str(b"pass\0"), false); + assert_param(¶ms, c_str(b"group\0"), true); + } + + #[test] + fn test_merge() { + let n = BigNum::from_u32(0xbc747fc5).unwrap(); + let e = BigNum::from_u32(0x10001).unwrap(); + let d = BigNum::from_u32(0x7b133399).unwrap(); + + let params1 = ParamBuilder::new() + .push_bignum(c_str(b"n\0"), &n) + .unwrap() + .build() .unwrap(); - let params = builder.build().unwrap(); + let params2 = ParamBuilder::new() + .push_bignum(c_str(b"e\0"), &e) + .unwrap() + .build() + .unwrap(); + let params3 = ParamBuilder::new() + .push_bignum(c_str(b"d\0"), &d) + .unwrap() + .build() + .unwrap(); + + // Merge 1 & 2, d (added in 3) should not be present + let merged_params = params1.merge(¶ms2).unwrap(); + assert_param(&merged_params, c_str(b"n\0"), false); + assert_param(&merged_params, c_str(b"e\0"), false); + assert_param(&merged_params, c_str(b"d\0"), true); - assert_param(params, c_str(b"pass\0"), false); - assert_param(params, c_str(b"group\0"), true); + // Merge 3 into 1+2, we should now have all params + let merged_params = merged_params.merge(¶ms3).unwrap(); + assert_param(&merged_params, c_str(b"n\0"), false); + assert_param(&merged_params, c_str(b"e\0"), false); + assert_param(&merged_params, c_str(b"e\0"), false); } } From a3eecd6f919c6675f5c5d2d3f6851db341fa1f53 Mon Sep 17 00:00:00 2001 From: Huw Jones Date: Wed, 6 Aug 2025 15:13:42 +0100 Subject: [PATCH 06/21] params: temporarily disable dead_code lint --- openssl/src/params.rs | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/openssl/src/params.rs b/openssl/src/params.rs index bd77e10db..3d11a8c31 100644 --- a/openssl/src/params.rs +++ b/openssl/src/params.rs @@ -93,6 +93,7 @@ unsafe impl Sync for ParamsRef<'_> {} impl<'a> ParamsRef<'a> { /// Merges two `ParamsRef` objects into a new `Params` object. #[corresponds(OSSL_PARAM_merge)] + #[allow(dead_code)] pub fn merge(&self, other: &ParamsRef<'a>) -> Result, ErrorStack> { cvt_p(unsafe { ffi::OSSL_PARAM_merge(self.as_ptr(), other.as_ptr()) }) .map(|p| unsafe { Params::from_ptr(p) }) @@ -100,6 +101,7 @@ impl<'a> ParamsRef<'a> { /// Locate a parameter by the given key. #[corresponds(OSSL_PARAM_locate_const)] + #[allow(dead_code)] fn locate(&self, key: &CStr) -> Option<*const ffi::OSSL_PARAM> { let param = unsafe { ffi::OSSL_PARAM_locate_const(self.as_ptr(), key.as_ptr()) }; if param.is_null() { @@ -125,12 +127,14 @@ unsafe impl Sync for ParamBuilder<'_> {} impl<'a, 'b> ParamBuilder<'a> { /// Creates a new `ParamBuilder`. #[corresponds[OSSL_PARAM_BLD_new]] + #[allow(dead_code)] pub fn new() -> Self { unsafe { ParamBuilder(ffi::OSSL_PARAM_BLD_new(), PhantomData) } } /// Push a BigNum parameter into the builder. #[corresponds[OSSL_PARAM_BLD_push_BN]] + #[allow(dead_code)] pub fn push_bignum(self, key: &'b CStr, bn: &'a BigNumRef) -> Result { cvt(unsafe { ffi::OSSL_PARAM_BLD_push_BN(self.0, key.as_ptr(), bn.as_ptr()) })?; Ok(self) @@ -138,6 +142,7 @@ impl<'a, 'b> ParamBuilder<'a> { /// Push a UTF-8 String parameter into the builder. #[corresponds[OSSL_PARAM_BLD_push_utf8_string]] + #[allow(dead_code)] pub fn push_utf8_string(self, key: &'b CStr, string: &'a str) -> Result { let value = string.as_bytes(); cvt(unsafe { @@ -153,6 +158,7 @@ impl<'a, 'b> ParamBuilder<'a> { /// Push a byte string parameter into the builder. #[corresponds[OSSL_PARAM_BLD_push_octet_string]] + #[allow(dead_code)] pub fn push_byte_string(self, key: &'b CStr, value: &'a [u8]) -> Result { cvt(unsafe { ffi::OSSL_PARAM_BLD_push_octet_string( @@ -183,6 +189,7 @@ impl<'a, 'b> ParamBuilder<'a> { /// Build a `Params` array from the builder consuming the builder. #[corresponds(OSSL_PARAM_BLD_to_param)] + #[allow(dead_code)] pub fn build(self) -> Result, ErrorStack> { let ptr = cvt_p(unsafe { ffi::OSSL_PARAM_BLD_to_param(self.0) })?; Ok(unsafe { Params::from_ptr(ptr) }) From f63fca69eb67955608ad0fc2ad1cedf0d93b8a32 Mon Sep 17 00:00:00 2001 From: Huw Jones Date: Wed, 6 Aug 2025 15:25:38 +0100 Subject: [PATCH 07/21] sys/evp: add selection consts --- openssl-sys/src/evp.rs | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/openssl-sys/src/evp.rs b/openssl-sys/src/evp.rs index 6374b2e60..bd1802eaa 100644 --- a/openssl-sys/src/evp.rs +++ b/openssl-sys/src/evp.rs @@ -341,3 +341,27 @@ pub unsafe fn EVP_PKEY_assign_DH(pkey: *mut EVP_PKEY, dh: *mut DH) -> c_int { pub unsafe fn EVP_PKEY_assign_EC_KEY(pkey: *mut EVP_PKEY, ec_key: *mut EC_KEY) -> c_int { EVP_PKEY_assign(pkey, EVP_PKEY_EC, ec_key as *mut c_void) } + +cfg_if! { + if #[cfg(ossl300)] { + // consts required for EVP_PKEY_fromdata selection value + + // From + const OSSL_KEYMGMT_SELECT_PRIVATE_KEY: c_int = 0x01; + const OSSL_KEYMGMT_SELECT_PUBLIC_KEY: c_int = 0x02; + const OSSL_KEYMGMT_SELECT_DOMAIN_PARAMETERS: c_int = 0x04; + const OSSL_KEYMGMT_SELECT_OTHER_PARAMETERS: c_int = 0x80; + const OSSL_KEYMGMT_SELECT_ALL_PARAMETERS: c_int = + OSSL_KEYMGMT_SELECT_DOMAIN_PARAMETERS | OSSL_KEYMGMT_SELECT_OTHER_PARAMETERS; + const OSSL_KEYMGMT_SELECT_KEYPAIR: c_int = + OSSL_KEYMGMT_SELECT_PRIVATE_KEY | OSSL_KEYMGMT_SELECT_PUBLIC_KEY; + const OSSL_KEYMGMT_SELECT_ALL: c_int = + OSSL_KEYMGMT_SELECT_KEYPAIR | OSSL_KEYMGMT_SELECT_ALL_PARAMETERS; + + // From + pub const EVP_PKEY_KEY_PARAMETERS: c_int = OSSL_KEYMGMT_SELECT_ALL_PARAMETERS; + pub const EVP_PKEY_PRIVATE_KEY: c_int = EVP_PKEY_KEY_PARAMETERS | OSSL_KEYMGMT_SELECT_PRIVATE_KEY; + pub const EVP_PKEY_PUBLIC_KEY: c_int = EVP_PKEY_KEY_PARAMETERS | OSSL_KEYMGMT_SELECT_PUBLIC_KEY; + pub const EVP_PKEY_KEYPAIR: c_int = EVP_PKEY_PUBLIC_KEY | OSSL_KEYMGMT_SELECT_PRIVATE_KEY; + } +} From 9def0e55414b69db03cf63f3d8ca47b1717bfa38 Mon Sep 17 00:00:00 2001 From: Huw Jones Date: Wed, 6 Aug 2025 15:27:01 +0100 Subject: [PATCH 08/21] pkey_ctx: add Selection trait for Param/Public/Private markers --- openssl/src/pkey_ctx.rs | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/openssl/src/pkey_ctx.rs b/openssl/src/pkey_ctx.rs index eb4be4df7..24a3f8137 100644 --- a/openssl/src/pkey_ctx.rs +++ b/openssl/src/pkey_ctx.rs @@ -70,6 +70,8 @@ use crate::cipher::CipherRef; use crate::error::ErrorStack; use crate::md::MdRef; use crate::nid::Nid; +#[cfg(ossl300)] +use crate::pkey::Public; use crate::pkey::{HasPrivate, HasPublic, Id, PKey, PKeyRef, Params, Private}; use crate::rsa::Padding; use crate::sign::RsaPssSaltlen; @@ -127,6 +129,27 @@ impl NonceType { pub const DETERMINISTIC_K: Self = NonceType(1); } +cfg_if! { + if #[cfg(ossl300)] { + /// Selection for fromdata operation. + pub(crate) trait Selection { + const SELECTION: c_int; + } + + impl Selection for Params { + const SELECTION: c_int = ffi::EVP_PKEY_KEY_PARAMETERS; + } + + impl Selection for Public { + const SELECTION: c_int = ffi::EVP_PKEY_PUBLIC_KEY; + } + + impl Selection for Private { + const SELECTION: c_int = ffi::EVP_PKEY_KEYPAIR; + } + } +} + generic_foreign_type_and_impl_send_sync! { type CType = ffi::EVP_PKEY_CTX; fn drop = ffi::EVP_PKEY_CTX_free; From 3d13bd866d290b138b2e4edbefb5ce3326ccd02d Mon Sep 17 00:00:00 2001 From: Huw Jones Date: Wed, 6 Aug 2025 15:27:23 +0100 Subject: [PATCH 09/21] sys/evp: add EVP_PKEY_fromdata functions --- openssl-sys/src/handwritten/evp.rs | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/openssl-sys/src/handwritten/evp.rs b/openssl-sys/src/handwritten/evp.rs index 22d614550..6ebefd304 100644 --- a/openssl-sys/src/handwritten/evp.rs +++ b/openssl-sys/src/handwritten/evp.rs @@ -588,6 +588,16 @@ extern "C" { pub fn EVP_PKEY_keygen(ctx: *mut EVP_PKEY_CTX, key: *mut *mut EVP_PKEY) -> c_int; pub fn EVP_PKEY_paramgen(ctx: *mut EVP_PKEY_CTX, key: *mut *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, + params: *mut OSSL_PARAM, + ) -> c_int; + pub fn EVP_PKEY_sign_init(ctx: *mut EVP_PKEY_CTX) -> c_int; pub fn EVP_PKEY_sign( ctx: *mut EVP_PKEY_CTX, From f2cea2bb4a14ff49a4a85805f20e687fc1c820f4 Mon Sep 17 00:00:00 2001 From: Huw Jones Date: Wed, 6 Aug 2025 15:27:59 +0100 Subject: [PATCH 10/21] pkey_ctx: add ability to crate a PKey from params --- openssl/src/pkey_ctx.rs | 71 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 71 insertions(+) diff --git a/openssl/src/pkey_ctx.rs b/openssl/src/pkey_ctx.rs index 24a3f8137..474f8ee23 100644 --- a/openssl/src/pkey_ctx.rs +++ b/openssl/src/pkey_ctx.rs @@ -71,6 +71,8 @@ use crate::error::ErrorStack; use crate::md::MdRef; use crate::nid::Nid; #[cfg(ossl300)] +use crate::params::ParamsRef; +#[cfg(ossl300)] use crate::pkey::Public; use crate::pkey::{HasPrivate, HasPublic, Id, PKey, PKeyRef, Params, Private}; use crate::rsa::Padding; @@ -457,6 +459,31 @@ impl PkeyCtxRef { Ok(()) } + /// Prepares the context for creating a key from user data. + #[corresponds(EVP_PKEY_fromdata_init)] + #[inline] + #[cfg(ossl300)] + #[allow(dead_code)] + pub(crate) fn fromdata_init(&mut self) -> Result<(), ErrorStack> { + cvt(unsafe { ffi::EVP_PKEY_fromdata_init(self.as_ptr()) }).map(|_| ()) + } + + /// Convert a stack of Params into a PKey. + #[corresponds(EVP_PKEY_fromdata)] + #[inline] + #[cfg(ossl300)] + #[allow(dead_code)] + pub(crate) fn fromdata( + &mut self, + params: &ParamsRef<'_>, + ) -> Result, ErrorStack> { + let mut key_ptr = ptr::null_mut(); + cvt(unsafe { + ffi::EVP_PKEY_fromdata(self.as_ptr(), &mut key_ptr, K::SELECTION, params.as_ptr()) + })?; + Ok(unsafe { PKey::from_ptr(key_ptr) }) + } + /// Sets which algorithm was used to compute the digest used in a /// signature. With RSA signatures this causes the signature to be wrapped /// in a `DigestInfo` structure. This is almost always what you want with @@ -938,6 +965,18 @@ impl PkeyCtxRef { } } +/// Creates a new `PKey` from the given ID and parameters. +#[cfg(ossl300)] +#[allow(dead_code)] +pub(crate) fn pkey_from_params( + id: Id, + params: &ParamsRef<'_>, +) -> Result, ErrorStack> { + let mut ctx = PkeyCtx::new_id(id)?; + ctx.fromdata_init()?; + ctx.fromdata(params) +} + #[cfg(test)] mod test { use super::*; @@ -948,11 +987,17 @@ mod test { use crate::hash::{hash, MessageDigest}; use crate::md::Md; use crate::nid::Nid; + #[cfg(ossl300)] + use crate::params::ParamBuilder; use crate::pkey::PKey; use crate::rsa::Rsa; use crate::sign::Verifier; + #[cfg(ossl300)] + use crate::util::c_str; #[cfg(not(boringssl))] use cfg_if::cfg_if; + #[cfg(ossl300)] + use std::cmp::Ordering; #[test] fn rsa() { @@ -1324,4 +1369,30 @@ mxJ7imIrEg9nIQ== assert_eq!(output, expected_output); assert!(ErrorStack::get().errors().is_empty()); } + + #[test] + #[cfg(ossl300)] + fn test_fromdata() { + let n = BigNum::from_u32(0xbc747fc5).unwrap(); + let e = BigNum::from_u32(0x10001).unwrap(); + let d = BigNum::from_u32(0x7b133399).unwrap(); + + let params = ParamBuilder::new() + .push_bignum(c_str(b"n\0"), &n) + .unwrap() + .push_bignum(c_str(b"e\0"), &e) + .unwrap() + .push_bignum(c_str(b"d\0"), &d) + .unwrap() + .build() + .unwrap(); + + let pkey: PKey = pkey_from_params(Id::RSA, ¶ms).unwrap(); + + let rsa = pkey.rsa().unwrap(); + + assert_eq!(rsa.n().ucmp(&n), Ordering::Equal); + assert_eq!(rsa.e().ucmp(&e), Ordering::Equal); + assert_eq!(rsa.d().ucmp(&d), Ordering::Equal); + } } From 16d071ae73418caa0091a8ef16de0197532b5491 Mon Sep 17 00:00:00 2001 From: Huw Jones Date: Wed, 6 Aug 2025 17:37:45 +0100 Subject: [PATCH 11/21] kdf: use ParamBuilder Increases the safety of the code and, imho, increases the readability. Additionally, removes the need for iter, lanes & memcost to be mutable. --- openssl/src/kdf.rs | 117 ++++++++++++++++----------------------------- 1 file changed, 40 insertions(+), 77 deletions(-) diff --git a/openssl/src/kdf.rs b/openssl/src/kdf.rs index 79e0eff49..c42863311 100644 --- a/openssl/src/kdf.rs +++ b/openssl/src/kdf.rs @@ -25,15 +25,14 @@ 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::params::ParamBuilder; + use crate::util::c_str; #[allow(clippy::too_many_arguments)] pub fn argon2d( @@ -94,86 +93,50 @@ 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 { - 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::init(); + let libctx = ctx.map_or(ptr::null_mut(), ForeignTypeRef::as_ptr); + + let max_threads = unsafe { 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 param_builder = ParamBuilder::new() + .push_byte_string(c_str(b"pass\0"), pass)? + .push_byte_string(c_str(b"salt\0"), salt)? + .push_uint(c_str(b"threads\0"), threads)? + .push_uint(c_str(b"lanes\0"), lanes)? + .push_uint(c_str(b"memcost\0"), memcost)? + .push_uint(c_str(b"iter\0"), iter)? + .push_size_t(c_str(b"size\0"), out.len())?; + if let Some(ad) = ad { + param_builder = param_builder.push_byte_string(c_str(b"ad\0"), ad)?; + } + if let Some(secret) = secret { + param_builder = param_builder.push_byte_string(c_str(b"secret\0"), secret)?; + } - let argon2 = EvpKdf(cvt_p(ffi::EVP_KDF_fetch( - libctx, - kdf_identifier.as_ptr() as *const c_char, - ptr::null(), - ))?); - let ctx = EvpKdfCtx(cvt_p(ffi::EVP_KDF_CTX_new(argon2.0))?); - cvt(ffi::EVP_KDF_derive( + let argon2 = EvpKdf(cvt_p(unsafe { + ffi::EVP_KDF_fetch(libctx, kdf_identifier.as_ptr(), ptr::null()) + })?); + let ctx = EvpKdfCtx(cvt_p(unsafe { ffi::EVP_KDF_CTX_new(argon2.0) })?); + cvt(unsafe { + ffi::EVP_KDF_derive( ctx.0, out.as_mut_ptr(), out.len(), - params.as_ptr(), - )) - .map(|_| ()) - } + param_builder.build()?.as_ptr(), + ) + }) + .map(|_| ()) } } } From 9b2e29323e890c2915dbe9c67af22d38aca9ddc8 Mon Sep 17 00:00:00 2001 From: Huw Jones Date: Wed, 6 Aug 2025 17:47:44 +0100 Subject: [PATCH 12/21] pkey_ctx/set_nonce_type: use ParamBuilder Simplifies and safens the code --- openssl/src/pkey_ctx.rs | 25 ++++++++++++++----------- 1 file changed, 14 insertions(+), 11 deletions(-) diff --git a/openssl/src/pkey_ctx.rs b/openssl/src/pkey_ctx.rs index 474f8ee23..e1ef2f66d 100644 --- a/openssl/src/pkey_ctx.rs +++ b/openssl/src/pkey_ctx.rs @@ -70,6 +70,8 @@ use crate::cipher::CipherRef; use crate::error::ErrorStack; use crate::md::MdRef; use crate::nid::Nid; +#[cfg(ossl320)] +use crate::params::ParamBuilder; #[cfg(ossl300)] use crate::params::ParamsRef; #[cfg(ossl300)] @@ -917,6 +919,14 @@ impl PkeyCtxRef { } } + /// Sets parameters on the given context + #[corresponds(EVP_PKEY_CTX_set_params)] + #[cfg(ossl300)] + #[allow(dead_code)] + fn set_params(&mut self, params: &ParamsRef<'_>) -> Result<(), ErrorStack> { + cvt(unsafe { ffi::EVP_PKEY_CTX_set_params(self.as_ptr(), params.as_ptr()) }).map(|_| ()) + } + /// Sets 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). @@ -926,17 +936,10 @@ impl PkeyCtxRef { #[cfg(ossl320)] #[corresponds(EVP_PKEY_CTX_set_params)] pub fn set_nonce_type(&mut self, nonce_type: NonceType) -> Result<(), ErrorStack> { - let nonce_field_name = c_str(b"nonce-type\0"); - let mut nonce_type = nonce_type.0; - unsafe { - let param_nonce = - ffi::OSSL_PARAM_construct_uint(nonce_field_name.as_ptr(), &mut nonce_type); - let param_end = ffi::OSSL_PARAM_construct_end(); - - let params = [param_nonce, param_end]; - cvt(ffi::EVP_PKEY_CTX_set_params(self.as_ptr(), params.as_ptr()))?; - } - Ok(()) + let params = ParamBuilder::new() + .push_uint(c_str(b"nonce-type\0"), nonce_type.0)? + .build()?; + self.set_params(¶ms) } /// Gets the nonce type for a private key context. From 6dd167cbefb8c4b11c6a1837241a6f4724e5b144 Mon Sep 17 00:00:00 2001 From: Huw Jones Date: Fri, 8 Aug 2025 20:02:51 +0100 Subject: [PATCH 13/21] params: add test to prove the lifetimes are screwed --- openssl/src/params.rs | 30 ++++++++++++++++++------------ 1 file changed, 18 insertions(+), 12 deletions(-) diff --git a/openssl/src/params.rs b/openssl/src/params.rs index 3d11a8c31..c5bd514e7 100644 --- a/openssl/src/params.rs +++ b/openssl/src/params.rs @@ -286,30 +286,36 @@ mod test { let e = BigNum::from_u32(0x10001).unwrap(); let d = BigNum::from_u32(0x7b133399).unwrap(); + let mut merged_params: Params<'_>; let params1 = ParamBuilder::new() .push_bignum(c_str(b"n\0"), &n) .unwrap() .build() .unwrap(); - let params2 = ParamBuilder::new() - .push_bignum(c_str(b"e\0"), &e) - .unwrap() - .build() - .unwrap(); - let params3 = ParamBuilder::new() - .push_bignum(c_str(b"d\0"), &d) - .unwrap() - .build() - .unwrap(); + { + let params2 = ParamBuilder::new() + .push_bignum(c_str(b"e\0"), &e) + .unwrap() + .build() + .unwrap(); + merged_params = params1.merge(¶ms2).unwrap(); + } // Merge 1 & 2, d (added in 3) should not be present - let merged_params = params1.merge(¶ms2).unwrap(); assert_param(&merged_params, c_str(b"n\0"), false); assert_param(&merged_params, c_str(b"e\0"), false); assert_param(&merged_params, c_str(b"d\0"), true); + { + let params3 = ParamBuilder::new() + .push_bignum(c_str(b"d\0"), &d) + .unwrap() + .build() + .unwrap(); + merged_params = merged_params.merge(¶ms3).unwrap(); + } + // Merge 3 into 1+2, we should now have all params - let merged_params = merged_params.merge(¶ms3).unwrap(); assert_param(&merged_params, c_str(b"n\0"), false); assert_param(&merged_params, c_str(b"e\0"), false); assert_param(&merged_params, c_str(b"e\0"), false); From 1adb27bfed2c9353c1834779ab8ebdccc9f3ffd9 Mon Sep 17 00:00:00 2001 From: Huw Jones Date: Fri, 8 Aug 2025 20:03:02 +0100 Subject: [PATCH 14/21] params/merge: fix lifetimes --- openssl/src/params.rs | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/openssl/src/params.rs b/openssl/src/params.rs index c5bd514e7..7e7e9566e 100644 --- a/openssl/src/params.rs +++ b/openssl/src/params.rs @@ -94,9 +94,18 @@ impl<'a> ParamsRef<'a> { /// Merges two `ParamsRef` objects into a new `Params` object. #[corresponds(OSSL_PARAM_merge)] #[allow(dead_code)] - pub fn merge(&self, other: &ParamsRef<'a>) -> Result, ErrorStack> { - cvt_p(unsafe { ffi::OSSL_PARAM_merge(self.as_ptr(), other.as_ptr()) }) - .map(|p| unsafe { Params::from_ptr(p) }) + pub fn merge(&self, other: &ParamsRef<'_>) -> Result, ErrorStack> { + // OSSL_PARAM_merge shallow copies the params + // OSSL_PARAM_free deep frees (so the params and values will be freed) + // OSSL_PARAM_dup deep copies + // Dupe both params[] so we don't end up pointing to freed memory. + cvt_p(unsafe { + ffi::OSSL_PARAM_merge( + ffi::OSSL_PARAM_dup(self.as_ptr()), + ffi::OSSL_PARAM_dup(other.as_ptr()), + ) + }) + .map(|p| unsafe { Params::from_ptr(p) }) } /// Locate a parameter by the given key. From 5e26fd8465aa245c90cce7859d65817c34cc2ce4 Mon Sep 17 00:00:00 2001 From: Huw Jones Date: Mon, 11 Aug 2025 17:57:26 +0100 Subject: [PATCH 15/21] pkey_ctx/Selection: make Enum and rename trait --- openssl/src/pkey_ctx.rs | 47 +++++++++++++++++++++++++++++++---------- 1 file changed, 36 insertions(+), 11 deletions(-) diff --git a/openssl/src/pkey_ctx.rs b/openssl/src/pkey_ctx.rs index e1ef2f66d..4da1805b1 100644 --- a/openssl/src/pkey_ctx.rs +++ b/openssl/src/pkey_ctx.rs @@ -135,21 +135,41 @@ impl NonceType { cfg_if! { if #[cfg(ossl300)] { + #[derive(Debug, PartialEq)] + pub(crate) enum Selection { + /// Key parameters + KeyParameters, + /// Public key (including parameters, if applicable). + PublicKey, + /// Keypair, which includes private key, public key, and parameters (if available). + Keypair, + } + + impl From for i32 { + fn from(value: Selection) -> Self { + match value { + Selection::KeyParameters => ffi::EVP_PKEY_KEY_PARAMETERS, + Selection::PublicKey => ffi::EVP_PKEY_PUBLIC_KEY, + Selection::Keypair => ffi::EVP_PKEY_KEYPAIR, + } + } + } + /// Selection for fromdata operation. - pub(crate) trait Selection { - const SELECTION: c_int; + pub(crate) trait SelectionT { + const SELECTION: Selection; } - impl Selection for Params { - const SELECTION: c_int = ffi::EVP_PKEY_KEY_PARAMETERS; + impl SelectionT for Params { + const SELECTION: Selection = Selection::KeyParameters; } - impl Selection for Public { - const SELECTION: c_int = ffi::EVP_PKEY_PUBLIC_KEY; + impl SelectionT for Public { + const SELECTION: Selection = Selection::PublicKey; } - impl Selection for Private { - const SELECTION: c_int = ffi::EVP_PKEY_KEYPAIR; + impl SelectionT for Private { + const SELECTION: Selection = Selection::Keypair; } } } @@ -475,13 +495,18 @@ impl PkeyCtxRef { #[inline] #[cfg(ossl300)] #[allow(dead_code)] - pub(crate) fn fromdata( + pub(crate) fn fromdata( &mut self, params: &ParamsRef<'_>, ) -> Result, ErrorStack> { let mut key_ptr = ptr::null_mut(); cvt(unsafe { - ffi::EVP_PKEY_fromdata(self.as_ptr(), &mut key_ptr, K::SELECTION, params.as_ptr()) + ffi::EVP_PKEY_fromdata( + self.as_ptr(), + &mut key_ptr, + K::SELECTION.into(), + params.as_ptr(), + ) })?; Ok(unsafe { PKey::from_ptr(key_ptr) }) } @@ -971,7 +996,7 @@ impl PkeyCtxRef { /// Creates a new `PKey` from the given ID and parameters. #[cfg(ossl300)] #[allow(dead_code)] -pub(crate) fn pkey_from_params( +pub(crate) fn pkey_from_params( id: Id, params: &ParamsRef<'_>, ) -> Result, ErrorStack> { From d383cd90ae8127b2ccf391167908b8a5c06012c0 Mon Sep 17 00:00:00 2001 From: Huw Jones Date: Mon, 11 Aug 2025 11:45:20 +0100 Subject: [PATCH 16/21] sys: add decoder symbols --- openssl-sys/build/run_bindgen.rs | 1 + openssl-sys/src/handwritten/decoder.rs | 53 ++++++++++++++++++++++++++ openssl-sys/src/handwritten/mod.rs | 2 + openssl-sys/src/handwritten/types.rs | 14 +++++++ systest/build.rs | 4 +- 5 files changed, 73 insertions(+), 1 deletion(-) create mode 100644 openssl-sys/src/handwritten/decoder.rs diff --git a/openssl-sys/build/run_bindgen.rs b/openssl-sys/build/run_bindgen.rs index e016a092b..500cc8403 100644 --- a/openssl-sys/build/run_bindgen.rs +++ b/openssl-sys/build/run_bindgen.rs @@ -61,6 +61,7 @@ const INCLUDES: &str = " #include #include #include +#include #endif #if OPENSSL_VERSION_NUMBER >= 0x30200000 diff --git a/openssl-sys/src/handwritten/decoder.rs b/openssl-sys/src/handwritten/decoder.rs new file mode 100644 index 000000000..eabb5f7da --- /dev/null +++ b/openssl-sys/src/handwritten/decoder.rs @@ -0,0 +1,53 @@ +use super::super::*; +use libc::*; + +#[cfg(ossl300)] +extern "C" { + pub fn OSSL_DECODER_CTX_new() -> *mut OSSL_DECODER_CTX; + pub fn OSSL_DECODER_CTX_free(ctx: *mut OSSL_DECODER_CTX); + + pub fn OSSL_DECODER_CTX_new_for_pkey( + pkey: *mut *mut EVP_PKEY, + input_type: *const c_char, + input_struct: *const c_char, + keytype: *const c_char, + selection: c_int, + libctx: *mut OSSL_LIB_CTX, + propquery: *const c_char, + ) -> *mut OSSL_DECODER_CTX; + + pub fn OSSL_DECODER_CTX_set_selection(ctx: *mut OSSL_DECODER_CTX, selection: c_int) -> c_int; + pub fn OSSL_DECODER_CTX_set_input_type( + ctx: *mut OSSL_DECODER_CTX, + input_type: *const c_char, + ) -> c_int; + pub fn OSSL_DECODER_CTX_set_input_structure( + ctx: *mut OSSL_DECODER_CTX, + input_structure: *const c_char, + ) -> c_int; + + pub fn OSSL_DECODER_CTX_set_passphrase( + ctx: *mut OSSL_DECODER_CTX, + kstr: *const c_uchar, + klen: size_t, + ) -> c_int; + pub fn OSSL_DECODER_CTX_set_pem_password_cb( + ctx: *mut OSSL_DECODER_CTX, + cb: pem_password_cb, + cbarg: *mut c_void, + ) -> c_int; + pub fn OSSL_DECODER_CTX_set_passphrase_cb( + ctx: *mut OSSL_DECODER_CTX, + cb: OSSL_PASSPHRASE_CALLBACK, + cbarg: *mut c_void, + ) -> c_int; + + pub fn OSSL_DECODER_from_bio(ctx: *mut OSSL_DECODER_CTX, b_in: *mut BIO) -> c_int; + #[cfg(not(osslconf = "OPENSSL_NO_STDIO"))] + pub fn OSSL_DECODER_from_fp(ctx: *mut OSSL_DECODER_CTX, fp: *mut FILE) -> c_int; + pub fn OSSL_DECODER_from_data( + ctx: *mut OSSL_DECODER_CTX, + pdata: *mut *const c_uchar, + pdata_len: *mut size_t, + ) -> c_int; +} diff --git a/openssl-sys/src/handwritten/mod.rs b/openssl-sys/src/handwritten/mod.rs index 47b3360fd..a3f890a85 100644 --- a/openssl-sys/src/handwritten/mod.rs +++ b/openssl-sys/src/handwritten/mod.rs @@ -6,6 +6,7 @@ pub use self::cmac::*; pub use self::cms::*; pub use self::conf::*; pub use self::crypto::*; +pub use self::decoder::*; pub use self::dh::*; pub use self::dsa::*; pub use self::ec::*; @@ -45,6 +46,7 @@ mod cmac; mod cms; mod conf; mod crypto; +mod decoder; mod dh; mod dsa; mod ec; diff --git a/openssl-sys/src/handwritten/types.rs b/openssl-sys/src/handwritten/types.rs index 576811c02..59f6e0b03 100644 --- a/openssl-sys/src/handwritten/types.rs +++ b/openssl-sys/src/handwritten/types.rs @@ -1147,3 +1147,17 @@ pub enum OSSL_PARAM_BLD {} pub enum EVP_KDF {} #[cfg(ossl300)] pub enum EVP_KDF_CTX {} + +#[cfg(ossl300)] +pub enum OSSL_DECODER_CTX {} + +#[cfg(ossl300)] +pub type OSSL_PASSPHRASE_CALLBACK = Option< + unsafe extern "C" fn( + pass: *mut c_char, + pass_size: size_t, + pass_len: *mut size_t, + params: *const OSSL_PARAM, + arg: *mut c_void, + ) -> c_int, +>; diff --git a/systest/build.rs b/systest/build.rs index eddcf6076..2f8740d99 100644 --- a/systest/build.rs +++ b/systest/build.rs @@ -85,7 +85,8 @@ fn main() { if version >= 0x30000000 { cfg.header("openssl/provider.h") .header("openssl/params.h") - .header("openssl/param_build.h"); + .header("openssl/param_build.h") + .header("openssl/decoder.h"); } if version >= 0x30200000 { cfg.header("openssl/thread.h"); @@ -120,6 +121,7 @@ fn main() { s == "PasswordCallback" || s == "pem_password_cb" || s == "bio_info_cb" + || s == "OSSL_PASSPHRASE_CALLBACK" || s.starts_with("CRYPTO_EX_") }); cfg.skip_struct(|s| { From f7ff9c55901ec7978e96745f3c527eaadc287ab0 Mon Sep 17 00:00:00 2001 From: Huw Jones Date: Mon, 11 Aug 2025 12:02:47 +0100 Subject: [PATCH 17/21] sys: add encoder symbols --- openssl-sys/build/run_bindgen.rs | 1 + openssl-sys/src/handwritten/encoder.rs | 56 ++++++++++++++++++++++++++ openssl-sys/src/handwritten/mod.rs | 2 + openssl-sys/src/handwritten/types.rs | 2 + systest/build.rs | 3 +- 5 files changed, 63 insertions(+), 1 deletion(-) create mode 100644 openssl-sys/src/handwritten/encoder.rs diff --git a/openssl-sys/build/run_bindgen.rs b/openssl-sys/build/run_bindgen.rs index 500cc8403..efd891c77 100644 --- a/openssl-sys/build/run_bindgen.rs +++ b/openssl-sys/build/run_bindgen.rs @@ -62,6 +62,7 @@ const INCLUDES: &str = " #include #include #include +#include #endif #if OPENSSL_VERSION_NUMBER >= 0x30200000 diff --git a/openssl-sys/src/handwritten/encoder.rs b/openssl-sys/src/handwritten/encoder.rs new file mode 100644 index 000000000..3613fab1c --- /dev/null +++ b/openssl-sys/src/handwritten/encoder.rs @@ -0,0 +1,56 @@ +use super::super::*; +use libc::*; + +#[cfg(ossl300)] +extern "C" { + pub fn OSSL_ENCODER_CTX_new() -> *mut OSSL_ENCODER_CTX; + pub fn OSSL_ENCODER_CTX_free(ctx: *mut OSSL_ENCODER_CTX); + + pub fn OSSL_ENCODER_CTX_new_for_pkey( + pkey: *const EVP_PKEY, + selection: c_int, + output_type: *const c_char, + output_structure: *const c_char, + propquery: *const c_char, + ) -> *mut OSSL_ENCODER_CTX; + + pub fn OSSL_ENCODER_CTX_set_selection(ctx: *mut OSSL_ENCODER_CTX, selection: c_int) -> c_int; + pub fn OSSL_ENCODER_CTX_set_output_type( + ctx: *mut OSSL_ENCODER_CTX, + output_type: *const c_char, + ) -> c_int; + pub fn OSSL_ENCODER_CTX_set_output_structure( + ctx: *mut OSSL_ENCODER_CTX, + output_structure: *const c_char, + ) -> c_int; + + pub fn OSSL_ENCODER_CTX_set_cipher( + ctx: *mut OSSL_ENCODER_CTX, + cipher_name: *const c_char, + propquery: *const c_char, + ) -> c_int; + pub fn OSSL_ENCODER_CTX_set_passphrase( + ctx: *mut OSSL_ENCODER_CTX, + kstr: *const c_uchar, + klen: size_t, + ) -> c_int; + pub fn OSSL_ENCODER_CTX_set_pem_password_cb( + ctx: *mut OSSL_ENCODER_CTX, + cb: pem_password_cb, + cbarg: *mut c_void, + ) -> c_int; + pub fn OSSL_ENCODER_CTX_set_passphrase_cb( + ctx: *mut OSSL_ENCODER_CTX, + cb: OSSL_PASSPHRASE_CALLBACK, + cbarg: *mut c_void, + ) -> c_int; + + pub fn OSSL_ENCODER_to_data( + ctx: *mut OSSL_ENCODER_CTX, + pdata: *mut *mut c_uchar, + pdata_len: *mut size_t, + ) -> c_int; + pub fn OSSL_ENCODER_to_bio(ctx: *mut OSSL_ENCODER_CTX, out: *mut BIO) -> c_int; + #[cfg(not(osslconf = "OPENSSL_NO_STDIO"))] + pub fn OSSL_ENCODER_to_fp(ctx: *mut OSSL_ENCODER_CTX, fp: *mut FILE) -> c_int; +} diff --git a/openssl-sys/src/handwritten/mod.rs b/openssl-sys/src/handwritten/mod.rs index a3f890a85..b0c71b579 100644 --- a/openssl-sys/src/handwritten/mod.rs +++ b/openssl-sys/src/handwritten/mod.rs @@ -10,6 +10,7 @@ pub use self::decoder::*; pub use self::dh::*; pub use self::dsa::*; pub use self::ec::*; +pub use self::encoder::*; pub use self::err::*; pub use self::evp::*; pub use self::hmac::*; @@ -50,6 +51,7 @@ mod decoder; mod dh; mod dsa; mod ec; +mod encoder; mod err; mod evp; mod hmac; diff --git a/openssl-sys/src/handwritten/types.rs b/openssl-sys/src/handwritten/types.rs index 59f6e0b03..c4692b1bd 100644 --- a/openssl-sys/src/handwritten/types.rs +++ b/openssl-sys/src/handwritten/types.rs @@ -1148,6 +1148,8 @@ pub enum EVP_KDF {} #[cfg(ossl300)] pub enum EVP_KDF_CTX {} +#[cfg(ossl300)] +pub enum OSSL_ENCODER_CTX {} #[cfg(ossl300)] pub enum OSSL_DECODER_CTX {} diff --git a/systest/build.rs b/systest/build.rs index 2f8740d99..c22be4b9b 100644 --- a/systest/build.rs +++ b/systest/build.rs @@ -86,7 +86,8 @@ fn main() { cfg.header("openssl/provider.h") .header("openssl/params.h") .header("openssl/param_build.h") - .header("openssl/decoder.h"); + .header("openssl/decoder.h") + .header("openssl/encoder.h"); } if version >= 0x30200000 { cfg.header("openssl/thread.h"); From eebd7ae39258d7bf5a2056e611dc50fdc5727ca9 Mon Sep 17 00:00:00 2001 From: Huw Jones Date: Mon, 11 Aug 2025 17:57:45 +0100 Subject: [PATCH 18/21] Add encoder wrapper --- openssl/src/encdec.rs | 606 +++++++++++++++++++++++++++++++++++++ openssl/src/lib.rs | 2 + openssl/test/dhparams.der | Bin 0 -> 268 bytes openssl/test/pkcs1.der.pub | Bin 0 -> 270 bytes openssl/test/rsa.der | Bin 0 -> 1191 bytes 5 files changed, 608 insertions(+) create mode 100644 openssl/src/encdec.rs create mode 100644 openssl/test/dhparams.der create mode 100644 openssl/test/pkcs1.der.pub create mode 100644 openssl/test/rsa.der diff --git a/openssl/src/encdec.rs b/openssl/src/encdec.rs new file mode 100644 index 000000000..15c9d497d --- /dev/null +++ b/openssl/src/encdec.rs @@ -0,0 +1,606 @@ +use crate::bio::MemBio; +use crate::error::ErrorStack; +use crate::pkey::PKeyRef; +use crate::pkey_ctx::Selection; +use crate::symm::Cipher; +use crate::util::c_str; +use crate::{cvt, cvt_p}; +use foreign_types::{ForeignType, ForeignTypeRef}; +use openssl_macros::corresponds; +use std::ffi::{CStr, CString}; +use std::ptr; + +#[derive(Debug, Copy, Clone, PartialEq, Eq)] +pub enum KeyFormat { + /// Human-readable description of the key. + Text, + /// DER formatted data + Der, + /// PEM formatted data + Pem, + // MSBLOB formatted data + MsBlob, + // PVK formatted data + Pvk, +} + +impl From<&CStr> for KeyFormat { + fn from(s: &CStr) -> Self { + match s.to_bytes() { + b"TEXT" => Self::Text, + b"DER" => Self::Der, + b"PEM" => Self::Pem, + b"MSBLOB" => Self::MsBlob, + b"PVK" => Self::Pvk, + _ => panic!("Unknown output type"), + } + } +} + +impl From for &CStr { + fn from(o: KeyFormat) -> Self { + match o { + KeyFormat::Text => c_str(b"TEXT\0"), + KeyFormat::Der => c_str(b"DER\0"), + KeyFormat::Pem => c_str(b"PEM\0"), + KeyFormat::MsBlob => c_str(b"MSBLOB\0"), + KeyFormat::Pvk => c_str(b"PVK\0"), + } + } +} + +pub enum Structure<'a> { + /// Encoding of public keys according to the Subject Public Key Info of RFC 5280 + SubjectPublicKeyInfo, + /// Structure according to the PKCS#1 specification + PKCS1, + /// Structure according to the PKCS#8 specification + PKCS8, + /// Type-specific structure + TypeSpecific, + Other(&'a CStr), +} + +impl<'a> From<&'a CStr> for Structure<'a> { + fn from(s: &'a CStr) -> Self { + match s.to_bytes() { + b"SubjectPublicKeyInfo" => Self::SubjectPublicKeyInfo, + b"pkcs1" => Self::PKCS1, + b"pkcs8" => Self::PKCS8, + b"type-specific" => Self::TypeSpecific, + _ => Self::Other(s), + } + } +} + +impl<'a> From> for &'a CStr { + fn from(o: Structure<'a>) -> Self { + match o { + Structure::SubjectPublicKeyInfo => c_str(b"SubjectPublicKeyInfo\0"), + Structure::PKCS1 => c_str(b"pkcs1\0"), + Structure::PKCS8 => c_str(b"pkcs8\0"), + Structure::TypeSpecific => c_str(b"type-specific\0"), + Structure::Other(v) => v, + } + } +} + +foreign_type_and_impl_send_sync! { + type CType = ffi::OSSL_DECODER_CTX; + fn drop = ffi::OSSL_DECODER_CTX_free; + + /// A context object which can perform decode operations. + pub struct DecoderCtx; + /// A reference to a [`DecoderCtx`]. + pub struct DecoderCtxRef; +} + +impl DecoderCtx { + /// Creates a new decoder context using the provided key. + #[corresponds(OSSL_DECODER_CTX_new)] + #[inline] + #[allow(dead_code)] + pub fn new() -> Result { + unsafe { + let ptr = cvt_p(ffi::OSSL_DECODER_CTX_new())?; + Ok(DecoderCtx::from_ptr(ptr)) + } + } +} + +foreign_type_and_impl_send_sync! { + type CType = ffi::OSSL_ENCODER_CTX; + fn drop = ffi::OSSL_ENCODER_CTX_free; + + /// A context object which can perform encode operations. + pub struct EncoderCtx; + /// A reference to a [`DecoderCtx`]. + pub struct EncoderCtxRef; +} + +impl EncoderCtx { + /// Creates a new encoder context using the provided key. + #[corresponds(OSSL_ENCODER_CTX_new_for_pkey)] + #[inline] + #[allow(dead_code)] + fn new_for_key( + pkey: &PKeyRef, + selection: Selection, + output: Option, + structure: Option>, + ) -> Result { + let output_ptr = output + .map(|o| { + let output: &CStr = o.into(); + output.as_ptr() + }) + .unwrap_or_else(ptr::null); + let structure_ptr = structure + .map(|s| { + let structure: &CStr = s.into(); + structure.as_ptr() + }) + .unwrap_or_else(ptr::null); + + unsafe { + let ptr = cvt_p(ffi::OSSL_ENCODER_CTX_new_for_pkey( + pkey.as_ptr(), + selection.into(), + output_ptr, + structure_ptr, + ptr::null(), + ))?; + Ok(EncoderCtx::from_ptr(ptr)) + } + } +} + +impl EncoderCtxRef { + // XXX: Because the only way to create an `EncoderCtx` is through `new_for_key`, don't expose + // set_selection, because it doesn't work if OSSL_ENCODER_CTX_new_for_key is called! + // See https://github.com/openssl/openssl/issues/28249 + // /// Select which parts of the key to encode. + // #[corresponds(OSSL_ENCODER_CTX_set_selection)] + // #[allow(dead_code)] + // pub fn set_selection(&mut self, selection: Selection) -> Result<(), ErrorStack> { + // cvt(unsafe { ffi::OSSL_ENCODER_CTX_set_selection(self.as_ptr(), selection.into()) }) + // .map(|_| ()) + // } + + /// Set the output type for the encoded data. + #[corresponds(OSSL_ENCODER_CTX_set_output_type)] + #[allow(dead_code)] + fn set_output_type(&mut self, output: KeyFormat) -> Result<(), ErrorStack> { + let output: &CStr = output.into(); + cvt(unsafe { ffi::OSSL_ENCODER_CTX_set_output_type(self.as_ptr(), output.as_ptr()) }) + .map(|_| ()) + } + + /// Set the output structure for the encoded data. + #[corresponds(OSSL_ENCODER_CTX_set_output_structure)] + #[allow(dead_code)] + fn set_output_structure(&mut self, structure: Structure<'_>) -> Result<(), ErrorStack> { + let structure: &CStr = structure.into(); + cvt(unsafe { + ffi::OSSL_ENCODER_CTX_set_output_structure(self.as_ptr(), structure.as_ptr()) + }) + .map(|_| ()) + } + + /// Set the (optional) output cipher for the encoded data. + /// + /// If `cipher` is `None`, no cipher will be used (i.e., the output will not be encrypted). + #[corresponds(OSSL_ENCODER_CTX_set_cipher)] + #[allow(dead_code)] + fn set_cipher(&mut self, cipher: Option) -> Result<(), ErrorStack> { + let cipher_name = cipher.map(|c| CString::new(c.nid().short_name().unwrap()).unwrap()); + cvt(unsafe { + ffi::OSSL_ENCODER_CTX_set_cipher( + self.as_ptr(), + cipher_name.as_ref().map_or(ptr::null(), |c| c.as_ptr()), + ptr::null(), + ) + }) + .map(|_| ()) + } + + /// Set the passphrase for the encoded data. + #[corresponds(OSSL_ENCODER_CTX_set_passphrase)] + #[allow(dead_code)] + fn set_passphrase(&mut self, passphrase: &[u8]) -> Result<(), ErrorStack> { + cvt(unsafe { + ffi::OSSL_ENCODER_CTX_set_passphrase( + self.as_ptr(), + passphrase.as_ptr().cast(), + passphrase.len(), + ) + }) + .map(|_| ()) + } + + /// Encode the data and return the result + #[corresponds(OSSL_ENCODER_to_bio)] + #[allow(dead_code)] + fn encode(&mut self) -> Result, ErrorStack> { + let bio = MemBio::new()?; + unsafe { + cvt(ffi::OSSL_ENCODER_to_bio(self.as_ptr(), bio.as_ptr()))?; + } + + Ok(bio.get_buf().to_owned()) + } +} + +pub struct Encoder<'a> { + selection: Selection, + format: Option, + structure: Option>, + cipher: Option, + passphrase: Option<&'a [u8]>, +} + +impl<'a> Encoder<'a> { + #[allow(dead_code)] + pub(crate) fn new(selection: Selection) -> Self { + Self { + selection, + format: None, + structure: None, + cipher: None, + passphrase: None, + } + } + + #[allow(dead_code)] + pub fn set_format(mut self, format: KeyFormat) -> Self { + self.format = Some(format); + self + } + + #[allow(dead_code)] + pub fn set_structure(mut self, structure: Structure<'a>) -> Self { + self.structure = Some(structure); + self + } + + #[allow(dead_code)] + pub fn set_cipher(mut self, cipher: Cipher) -> Self { + self.cipher = Some(cipher); + self + } + + #[allow(dead_code)] + pub fn set_passphrase(mut self, passphrase: &'a [u8]) -> Self { + self.passphrase = Some(passphrase); + self + } + + #[allow(dead_code)] + pub fn encode(self, pkey: &PKeyRef) -> Result, ErrorStack> { + let mut ctx = EncoderCtx::new_for_key(pkey, self.selection, self.format, self.structure)?; + + ctx.set_cipher(self.cipher)?; + if let Some(passphrase) = self.passphrase { + ctx.set_passphrase(passphrase)?; + } + + ctx.encode() + } +} + +#[cfg(test)] +mod test { + use super::*; + use crate::pkey::PKey; + use crate::rsa::Rsa; + use std::str::from_utf8; + + mod output { + use super::*; + #[test] + fn test_output_from_cstr() { + let text: KeyFormat = c_str(b"TEXT\0").into(); + let der: KeyFormat = c_str(b"DER\0").into(); + let pem: KeyFormat = c_str(b"PEM\0").into(); + + assert_eq!(text, KeyFormat::Text); + assert_eq!(der, KeyFormat::Der); + assert_eq!(pem, KeyFormat::Pem); + } + + #[test] + fn test_cstr_from_output() { + let text: &CStr = KeyFormat::Text.into(); + let der: &CStr = KeyFormat::Der.into(); + let pem: &CStr = KeyFormat::Pem.into(); + + assert_eq!(text.to_bytes(), b"TEXT"); + assert_eq!(der.to_bytes(), b"DER"); + assert_eq!(pem.to_bytes(), b"PEM"); + } + } + + mod encoder { + use super::*; + + mod params { + use super::*; + use crate::dh::Dh; + use crate::pkey::Id; + use crate::pkey::Params; + use crate::pkey_ctx::PkeyCtx; + + fn generate_dh_params() -> Result, ErrorStack> { + let mut ctx = PkeyCtx::new_id(Id::DH)?; + ctx.paramgen_init()?; + ctx.set_dh_paramgen_prime_len(512)?; + ctx.set_dh_paramgen_generator(2)?; + ctx.paramgen() + } + + #[test] + fn test_dh_pem() { + let pkey = generate_dh_params().unwrap(); + + // Serialise params to PEM + let pem = Encoder::new(Selection::KeyParameters) + .set_format(KeyFormat::Pem) + .set_structure(Structure::TypeSpecific) + .encode(&pkey) + .unwrap(); + let pem_str = from_utf8(&pem).unwrap(); + + // We should be able to load the params back into a key + assert!( + pem_str.contains("-----BEGIN DH PARAMETERS-----"), + "{pem_str}" + ); + let pem_key = Dh::params_from_pem(&pem).unwrap(); + assert_eq!(pem_key.prime_p(), pkey.dh().unwrap().prime_p()); + } + + #[test] + fn test_dh_der() { + let pkey = generate_dh_params().unwrap(); + + // Serialise parms to PEM + let der = Encoder::new(Selection::KeyParameters) + .set_format(KeyFormat::Der) + .set_structure(Structure::TypeSpecific) + .encode(&pkey) + .unwrap(); + + // DER is not valid UTF-8, so we can't convert it to a string + assert!(from_utf8(&der).is_err()); + + // We should be able to load the DER back into a key + let der_key = Dh::params_from_der(&der).unwrap(); + assert_eq!(der_key.prime_p(), pkey.dh().unwrap().prime_p()); + } + } + + mod public { + use super::*; + + #[test] + fn test_rsa_pem() { + let expected = include_bytes!("../test/rsa.pem.pub"); + let pkey = PKey::public_key_from_pem(expected).unwrap(); + + // Serialise public key to PEM + let pem = Encoder::new(Selection::PublicKey) + .set_format(KeyFormat::Pem) + .set_structure(Structure::SubjectPublicKeyInfo) + .encode(&pkey) + .unwrap(); + + // We should end up with the same PEM as the input + assert_eq!( + from_utf8(&pem).unwrap(), + from_utf8(expected).unwrap().replace("\r\n", "\n") + ); + } + + #[test] + fn test_rsa_pem_pkcs1() { + let expected = include_bytes!("../test/pkcs1.pem.pub"); + let pkey = PKey::public_key_from_pem(expected).unwrap(); + + // Serialise public key to PEM + let pem = Encoder::new(Selection::PublicKey) + .set_format(KeyFormat::Pem) + .set_structure(Structure::PKCS1) + .encode(&pkey) + .unwrap(); + + // We should end up with the same PEM as the input + assert_eq!( + from_utf8(&pem).unwrap(), + from_utf8(expected).unwrap().replace("\r\n", "\n") + ); + } + + #[test] + fn test_rsa_der() { + let expected = include_bytes!("../test/key.der.pub"); + let pkey = PKey::public_key_from_der(expected).unwrap(); + + // Serialise public key to DER + let der = Encoder::new(Selection::PublicKey) + .set_format(KeyFormat::Der) + .set_structure(Structure::SubjectPublicKeyInfo) + .encode(&pkey) + .unwrap(); + + // We should end up with the same DER as the input + assert_eq!(der, expected); + } + + #[test] + fn test_rsa_der_pkcs1() { + let expected = include_bytes!("../test/rsa.pem.pub"); + let pkey = PKey::public_key_from_pem(expected).unwrap(); + + // Serialise public key to DER + let der = Encoder::new(Selection::PublicKey) + .set_format(KeyFormat::Der) + .set_structure(Structure::PKCS1) + .encode(&pkey) + .unwrap(); + + // We should be able to load the DER back into a key + let der_key = Rsa::public_key_from_der_pkcs1(&der).unwrap(); + assert_eq!(der_key.n(), pkey.rsa().unwrap().n()); + assert_eq!(der_key.e(), pkey.rsa().unwrap().e()); + } + } + + mod public_from_private { + use super::*; + + #[test] + fn test_rsa_pem() { + let pkey = PKey::private_key_from_pem(include_bytes!("../test/rsa.pem")).unwrap(); + + // Serialise the public key to PEM + let pem = Encoder::new(Selection::PublicKey) + .set_format(KeyFormat::Pem) + .set_structure(Structure::SubjectPublicKeyInfo) + .encode(&pkey) + .unwrap(); + + // Check that we have a public key PEM, and that we can load it back + let pem_str = from_utf8(&pem).unwrap(); + assert!(pem_str.contains("-----BEGIN PUBLIC KEY-----"), "{pem_str}"); + + let pem_key = Rsa::public_key_from_pem(&pem).unwrap(); + assert_eq!(pem_key.n(), pkey.rsa().unwrap().n()); + assert_eq!(pem_key.e(), pkey.rsa().unwrap().e()); + } + + #[test] + fn test_rsa_pem_pkcs1() { + let pkey = PKey::private_key_from_pem(include_bytes!("../test/rsa.pem")).unwrap(); + + // Serialise the public key to PEM + let pem = Encoder::new(Selection::PublicKey) + .set_format(KeyFormat::Pem) + .set_structure(Structure::PKCS1) + .encode(&pkey) + .unwrap(); + + // Check that we have a public key PEM, and that we can load it back + let pem_str = from_utf8(&pem).unwrap(); + assert!( + pem_str.contains("-----BEGIN RSA PUBLIC KEY-----"), + "{pem_str}" + ); + + let pem_key = Rsa::public_key_from_pem_pkcs1(&pem).unwrap(); + assert_eq!(pem_key.n(), pkey.rsa().unwrap().n()); + assert_eq!(pem_key.e(), pkey.rsa().unwrap().e()); + } + + #[test] + fn test_rsa_der() { + let pkey = PKey::private_key_from_pem(include_bytes!("../test/rsa.pem")).unwrap(); + + // Serialise the public key to DER + let der = Encoder::new(Selection::PublicKey) + .set_format(KeyFormat::Der) + .set_structure(Structure::SubjectPublicKeyInfo) + .encode(&pkey) + .unwrap(); + + // DER is not valid UTF-8, so we can't convert it to a string + assert!(from_utf8(&der).is_err()); + + // We should be able to load the DER back into a key + let der_key = Rsa::public_key_from_der(&der).unwrap(); + assert_eq!(der_key.n(), pkey.rsa().unwrap().n()); + assert_eq!(der_key.e(), pkey.rsa().unwrap().e()); + } + + #[test] + fn test_rsa_der_pkcs1() { + let pkey = PKey::private_key_from_pem(include_bytes!("../test/rsa.pem")).unwrap(); + + // Serialise the public key to DER + let der = Encoder::new(Selection::PublicKey) + .set_format(KeyFormat::Der) + .set_structure(Structure::PKCS1) + .encode(&pkey) + .unwrap(); + + // DER is not valid UTF-8, so we can't convert it to a string + assert!(from_utf8(&der).is_err()); + + // We should be able to load the DER back into a key + let der_key = Rsa::public_key_from_der_pkcs1(&der).unwrap(); + assert_eq!(der_key.n(), pkey.rsa().unwrap().n()); + assert_eq!(der_key.e(), pkey.rsa().unwrap().e()); + } + } + + mod private { + use super::*; + + #[test] + fn test_rsa_pem() { + let expected = include_bytes!("../test/rsa.pem"); + let pkey = PKey::private_key_from_pem(expected).unwrap(); + + // Serialise private key to PEM + let pem = Encoder::new(Selection::Keypair) + .set_format(KeyFormat::Pem) + .set_structure(Structure::PKCS1) + .encode(&pkey) + .unwrap(); + + assert_eq!( + from_utf8(&pem).unwrap(), + from_utf8(expected).unwrap().replace("\r\n", "\n") + ); + } + + #[test] + fn test_rsa_pem_encrypted() { + let pkey = PKey::private_key_from_pem(include_bytes!("../test/rsa.pem")).unwrap(); + + // Serialise private to an encrypted PEM + let passphrase = b"hunter2"; + let pem = Encoder::new(Selection::Keypair) + .set_format(KeyFormat::Pem) + .set_cipher(Cipher::aes_256_cbc()) + .set_passphrase(passphrase) + .encode(&pkey) + .unwrap(); + + // Check that we have an encrypted PEM + let pem_str = from_utf8(&pem).unwrap(); + assert!(pem_str.contains("ENCRYPTED"), "{pem_str}"); + + // Check that we can load the PEM back into a key + let pkey2 = + Rsa::private_key_from_pem_passphrase(pem.as_slice(), passphrase).unwrap(); + assert_eq!(pkey2.p(), pkey.rsa().unwrap().p()); + assert_eq!(pkey2.q(), pkey.rsa().unwrap().q()); + assert_eq!(pkey2.d(), pkey.rsa().unwrap().d()); + } + + #[test] + fn test_rsa_der() { + let expected = include_bytes!("../test/rsa.der"); + let pkey = PKey::private_key_from_der(expected).unwrap(); + + // Serialise private key to DER + let der = Encoder::new(Selection::Keypair) + .set_format(KeyFormat::Der) + .encode(&pkey) + .unwrap(); + + assert_eq!(der, expected); + } + } + } +} diff --git a/openssl/src/lib.rs b/openssl/src/lib.rs index 21407e5e2..a60cf8594 100644 --- a/openssl/src/lib.rs +++ b/openssl/src/lib.rs @@ -160,6 +160,8 @@ pub mod dh; pub mod dsa; pub mod ec; pub mod ecdsa; +#[cfg(ossl300)] +mod encdec; pub mod encrypt; #[cfg(not(any(boringssl, awslc)))] pub mod envelope; diff --git a/openssl/test/dhparams.der b/openssl/test/dhparams.der new file mode 100644 index 0000000000000000000000000000000000000000..bf0cd0cb4b7b5d22fe666647857047968ee8de00 GIT binary patch literal 268 zcmV+n0rUPaf&mBuf&l>lhj3oD|Dk`F&9Lc)T~8>+-~kU{e6LKD@Ks=raG3=qXzN;u z&<&j@s{E~jn~vHgp0-W>UI;xb*uNXA9lN}pxP@0JPRS$OgpGx}<`evT5_|m&AJ*1@ zl+Dg-ty+w|R~uF=DVLn&nH}nPF`Zd9DW{{Nq1vQ7RGReop?n##tf1#Od|)_3kDf%+jL|~CCGf#5$i6m1z_NC00s#Wg9DQ;C literal 0 HcmV?d00001 diff --git a/openssl/test/pkcs1.der.pub b/openssl/test/pkcs1.der.pub new file mode 100644 index 0000000000000000000000000000000000000000..0e54c50f4675fd590a05339350e8edeb2d0966db GIT binary patch literal 270 zcmV+p0rCDYf&mHwf&l>l%C{fxTlm-zsE9XrEs2Hk=7u|p;h1d3f-j9pnlO+{i-so0 z83|q%Mndi#RVvI%77V)=ABJFgFZAN(^nPD)8b!M_AlRJiko{EM!SUEEZl9&#tyANb{yxF z&y9T7yfPq;-`{Euq8GydZ5`UW0!ef@OU&DxN5DB9%g?lhXy;UV-&SfcD1LRD;LqH~ z34AQQDKh>eiA-~M!g_34;DF67VkF1oN53~s`}(nAY}e68?vc;L5I1jUr##O}2Qm#9 U>2lHIIEP2!`A}uG0s{d60V5BH-v9sr literal 0 HcmV?d00001 diff --git a/openssl/test/rsa.der b/openssl/test/rsa.der new file mode 100644 index 0000000000000000000000000000000000000000..500c05fafcad453788d4e3d594a75426771b2cd6 GIT binary patch literal 1191 zcmV;Y1X%kpf&`-i0RRGm0RaG^_!bJ{{dU0|5X50)hbm60UKiY0U~N!+uh1MF3YH9bt7HWa&w8dtzA+ zoeQOWmOMZnKhG~L)4lA7z}w7f#Jlx@?DZg^iarU zl6h$7b2RipPj#nI;W<=pI*N0h0)c@5;2gvd>qw?$T~tO*3Z%Pjpty3kbf&4%AO1)O zc<|k~F&ci5F8Rc@_f&Q!E2tN2vsroY6gMgwC@JIzC=Mmk(ZB>Tyr}}m3gp|R6gW$GTLinK7=RdO9aPS#N0)c@5 zxdX(036-`o^iwRc^TC-EU0*us$KUSUdXlEJeksNqLZEu7T4elS37N&GupR-xjJ7PN zR#JPs#b9{*3#+B=INmJ>nGg0b53a52Yy+B?zaCJ2PH;f_c-w+k$gvXbURlG<2`}Z; z<+T!Z)DnRQDqeVAy7SuOaFPQPKT%oN0)c=B0-slKB&(my6_CYbDc1W8<@g||^L40Z z`O8B9lXyG)yE$$}Ddz7|H$WoYhPs5=?^u%??#0kwr9aOjwlnxsik|IJ=z9rxFr%Mc zl9PMp0!vlfHuvDboy)f=bvS=vTKl%kHoL3mc|0ov&Z}nIxiPx+3svlz`7s>;9b%0F zfq?*r|9bjjwMYKomK!dc&0S|0)18c`J+^5dIGK&-hjDf+U8an}L!~%77HhIS#c=3H z2FidQ_4c~vBWen-qjwIERMDu~9AAwnnec)$9or9Fk3IkTOpTWiE86}DDiMvQV>yV3 z4og*8oaTD+Zoj3n!AhOyvc%n%tKHJlB20Axfq)^0dkk}-t!wd<1JJP(Hniv~jkha) z`%oq-UHCa%$0csK&ul=qV2xQeqb<4@K_;aq1}?3PqXqkZ7k@yMH_6<&agNv^7`*Qf zcOas~b@|&S+73Lm7SM$2B^Qo@ja;kLo3j=gz!)E=_#x;HdeeC}`IInJtqCb1?krg@ FLv^ygK$-vm literal 0 HcmV?d00001 From b0d8f97326caa33345bc5cf9aafd42ba86cf3644 Mon Sep 17 00:00:00 2001 From: Huw Jones Date: Fri, 15 Aug 2025 15:34:48 +0100 Subject: [PATCH 19/21] pkey/Id: implement TryInto<&str> --- openssl/src/pkey.rs | 65 +++++++++++++++++++++++++-------------------- 1 file changed, 36 insertions(+), 29 deletions(-) diff --git a/openssl/src/pkey.rs b/openssl/src/pkey.rs index 8d69e1cdc..895556ee1 100644 --- a/openssl/src/pkey.rs +++ b/openssl/src/pkey.rs @@ -119,6 +119,41 @@ impl Id { } } +impl TryFrom for &'static str { + type Error = (); + fn try_from(id: Id) -> Result { + match id { + Id::RSA => Ok("RSA"), + #[cfg(any(ossl111, libressl310, boringssl, awslc))] + Id::RSA_PSS => Ok("RSA-PSS"), + #[cfg(not(boringssl))] + Id::HMAC => Ok("HMAC"), + #[cfg(not(any(boringssl, awslc)))] + Id::CMAC => Ok("CMAC"), + Id::DSA => Ok("DSA"), + Id::DH => Ok("DH"), + #[cfg(ossl110)] + Id::DHX => Ok("DHX"), + Id::EC => Ok("EC"), + #[cfg(ossl111)] + Id::SM2 => Ok("SM2"), + #[cfg(any(ossl110, boringssl, libressl360, awslc))] + Id::HKDF => Ok("HKDF"), + #[cfg(any(ossl111, boringssl, libressl370, awslc))] + Id::ED25519 => Ok("Ed25519"), + #[cfg(ossl111)] + Id::ED448 => Ok("Ed448"), + #[cfg(any(ossl111, boringssl, libressl370, awslc))] + Id::X25519 => Ok("X25519"), + #[cfg(ossl111)] + Id::X448 => Ok("X448"), + #[cfg(ossl111)] + Id::POLY1305 => Ok("POLY1305"), + _ => Err(()), + } + } +} + /// A trait indicating that a key has parameters. pub unsafe trait HasParams {} @@ -382,35 +417,7 @@ where impl fmt::Debug for PKey { fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result { - let alg = match self.id() { - Id::RSA => "RSA", - #[cfg(any(ossl111, libressl310, boringssl, awslc))] - Id::RSA_PSS => "RSA-PSS", - #[cfg(not(boringssl))] - Id::HMAC => "HMAC", - #[cfg(not(any(boringssl, awslc)))] - Id::CMAC => "CMAC", - Id::DSA => "DSA", - Id::DH => "DH", - #[cfg(ossl110)] - Id::DHX => "DHX", - Id::EC => "EC", - #[cfg(ossl111)] - Id::SM2 => "SM2", - #[cfg(any(ossl110, boringssl, libressl360, awslc))] - Id::HKDF => "HKDF", - #[cfg(any(ossl111, boringssl, libressl370, awslc))] - Id::ED25519 => "Ed25519", - #[cfg(ossl111)] - Id::ED448 => "Ed448", - #[cfg(any(ossl111, boringssl, libressl370, awslc))] - Id::X25519 => "X25519", - #[cfg(ossl111)] - Id::X448 => "X448", - #[cfg(ossl111)] - Id::POLY1305 => "POLY1305", - _ => "unknown", - }; + let alg = self.id().try_into().unwrap_or("unknown"); fmt.debug_struct("PKey").field("algorithm", &alg).finish() // TODO: Print details for each specific type of key } From ecf0fa7cea7c02d7114881d51a5cae50888f7fa3 Mon Sep 17 00:00:00 2001 From: Huw Jones Date: Fri, 15 Aug 2025 15:35:07 +0100 Subject: [PATCH 20/21] pkey/Id: implement TryInto<&CStr> --- openssl/src/pkey.rs | 39 +++++++++++++++++++++++++++++++++++++-- 1 file changed, 37 insertions(+), 2 deletions(-) diff --git a/openssl/src/pkey.rs b/openssl/src/pkey.rs index 895556ee1..1fe6f3871 100644 --- a/openssl/src/pkey.rs +++ b/openssl/src/pkey.rs @@ -51,14 +51,14 @@ use crate::error::ErrorStack; use crate::pkey_ctx::PkeyCtx; use crate::rsa::Rsa; use crate::symm::Cipher; -use crate::util::{invoke_passwd_cb, CallbackState}; +use crate::util::{c_str, invoke_passwd_cb, CallbackState}; use crate::{cvt, cvt_p}; use cfg_if::cfg_if; use foreign_types::{ForeignType, ForeignTypeRef}; use libc::{c_int, c_long}; use openssl_macros::corresponds; use std::convert::{TryFrom, TryInto}; -use std::ffi::CString; +use std::ffi::{CStr, CString}; use std::fmt; #[cfg(all(not(any(boringssl, awslc)), ossl110))] use std::mem; @@ -154,6 +154,41 @@ impl TryFrom for &'static str { } } +impl TryFrom for &'static CStr { + type Error = (); + fn try_from(id: Id) -> Result { + match id { + Id::RSA => Ok(c_str(b"RSA\0")), + #[cfg(any(ossl111, libressl310, boringssl, awslc))] + Id::RSA_PSS => Ok(c_str(b"RSA-PSS\0")), + #[cfg(not(boringssl))] + Id::HMAC => Ok(c_str(b"HMAC\0")), + #[cfg(not(any(boringssl, awslc)))] + Id::CMAC => Ok(c_str(b"CMAC\0")), + Id::DSA => Ok(c_str(b"DSA\0")), + Id::DH => Ok(c_str(b"DH\0")), + #[cfg(ossl110)] + Id::DHX => Ok(c_str(b"DHX\0")), + Id::EC => Ok(c_str(b"EC\0")), + #[cfg(ossl111)] + Id::SM2 => Ok(c_str(b"SM2\0")), + #[cfg(any(ossl110, boringssl, libressl360, awslc))] + Id::HKDF => Ok(c_str(b"HKDF\0")), + #[cfg(any(ossl111, boringssl, libressl370, awslc))] + Id::ED25519 => Ok(c_str(b"Ed25519\0")), + #[cfg(ossl111)] + Id::ED448 => Ok(c_str(b"Ed448\0")), + #[cfg(any(ossl111, boringssl, libressl370, awslc))] + Id::X25519 => Ok(c_str(b"X25519\0")), + #[cfg(ossl111)] + Id::X448 => Ok(c_str(b"X448\0")), + #[cfg(ossl111)] + Id::POLY1305 => Ok(c_str(b"POLY1305\0")), + _ => Err(()), + } + } +} + /// A trait indicating that a key has parameters. pub unsafe trait HasParams {} From 5084601010783acfc9eea0db6c62cb78b258ede2 Mon Sep 17 00:00:00 2001 From: Huw Jones Date: Wed, 13 Aug 2025 22:55:32 +0100 Subject: [PATCH 21/21] Add decoder wrapper --- openssl/src/encdec.rs | 340 ++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 330 insertions(+), 10 deletions(-) diff --git a/openssl/src/encdec.rs b/openssl/src/encdec.rs index 15c9d497d..ba8ef3ec5 100644 --- a/openssl/src/encdec.rs +++ b/openssl/src/encdec.rs @@ -1,13 +1,14 @@ -use crate::bio::MemBio; +use crate::bio::{MemBio, MemBioSlice}; use crate::error::ErrorStack; -use crate::pkey::PKeyRef; -use crate::pkey_ctx::Selection; +use crate::pkey::{Id, PKey, PKeyRef}; +use crate::pkey_ctx::{Selection, SelectionT}; use crate::symm::Cipher; -use crate::util::c_str; +use crate::util::{c_str, invoke_passwd_cb, CallbackState}; use crate::{cvt, cvt_p}; use foreign_types::{ForeignType, ForeignTypeRef}; use openssl_macros::corresponds; use std::ffi::{CStr, CString}; +use std::marker::PhantomData; use std::ptr; #[derive(Debug, Copy, Clone, PartialEq, Eq)] @@ -91,23 +92,200 @@ foreign_type_and_impl_send_sync! { /// A context object which can perform decode operations. pub struct DecoderCtx; - /// A reference to a [`DecoderCtx`]. + /// A reference to a `DecoderCtx`. pub struct DecoderCtxRef; } impl DecoderCtx { - /// Creates a new decoder context using the provided key. - #[corresponds(OSSL_DECODER_CTX_new)] + #[corresponds(OSSL_DECODER_CTX_new_for_pkey)] #[inline] #[allow(dead_code)] - pub fn new() -> Result { + fn new_for_key( + pkey: *mut *mut ffi::EVP_PKEY, + selection: Selection, + input: Option, + structure: Option>, + key_type: Option, + ) -> Result { + let input_ptr = input + .map(|i| { + let input: &CStr = i.into(); + input.as_ptr() + }) + .unwrap_or_else(ptr::null); + let structure_ptr = structure + .map(|s| { + let structure: &CStr = s.into(); + structure.as_ptr() + }) + .unwrap_or_else(ptr::null); + let key_type_ptr = key_type + .and_then(|k| k.try_into().ok()) + .map(|k: &CStr| k.as_ptr()) + .unwrap_or_else(ptr::null); unsafe { - let ptr = cvt_p(ffi::OSSL_DECODER_CTX_new())?; + let ptr = cvt_p(ffi::OSSL_DECODER_CTX_new_for_pkey( + pkey, + input_ptr, + structure_ptr, + key_type_ptr, + selection.into(), + ptr::null_mut(), + ptr::null(), + ))?; Ok(DecoderCtx::from_ptr(ptr)) } } } +impl DecoderCtxRef { + /// Select which parts of the key to decode. + #[corresponds(OSSL_DECODER_CTX_set_selection)] + #[allow(dead_code)] + fn set_selection(&mut self, selection: Selection) -> Result<(), ErrorStack> { + cvt(unsafe { ffi::OSSL_DECODER_CTX_set_selection(self.as_ptr(), selection.into()) }) + .map(|_| ()) + } + + /// Set the input type for the encoded data. + #[corresponds(OSSL_DECODER_CTX_set_input_type)] + #[allow(dead_code)] + fn set_input_type(&mut self, input: KeyFormat) -> Result<(), ErrorStack> { + let input: &CStr = input.into(); + cvt(unsafe { ffi::OSSL_DECODER_CTX_set_input_type(self.as_ptr(), input.as_ptr()) }) + .map(|_| ()) + } + + /// Set the input structure for the encoded data. + #[corresponds(OSSL_DECODER_CTX_set_input_structure)] + #[allow(dead_code)] + fn set_input_structure(&mut self, structure: Structure<'_>) -> Result<(), ErrorStack> { + let structure: &CStr = structure.into(); + cvt(unsafe { ffi::OSSL_DECODER_CTX_set_input_structure(self.as_ptr(), structure.as_ptr()) }) + .map(|_| ()) + } + + /// Set the passphrase to decrypt the encoded data. + #[corresponds(OSSL_DECODER_CTX_set_passphrase)] + #[allow(dead_code)] + fn set_passphrase(&mut self, passphrase: &[u8]) -> Result<(), ErrorStack> { + cvt(unsafe { + ffi::OSSL_DECODER_CTX_set_passphrase( + self.as_ptr(), + passphrase.as_ptr().cast(), + passphrase.len(), + ) + }) + .map(|_| ()) + } + + /// Set the passphrase to decrypt the encoded data. + #[corresponds(OSSL_DECODER_CTX_set_passphrase)] + #[allow(dead_code)] + unsafe fn set_passphrase_callback Result>( + &mut self, + callback: *mut CallbackState, + ) -> Result<(), ErrorStack> { + cvt(unsafe { + ffi::OSSL_DECODER_CTX_set_pem_password_cb( + self.as_ptr(), + Some(invoke_passwd_cb::), + callback as *mut _, + ) + }) + .map(|_| ()) + } + + /// Decode the encoded data + #[corresponds(OSSL_DECODER_from_bio)] + #[allow(dead_code)] + fn decode(&mut self, data: &[u8]) -> Result<(), ErrorStack> { + let bio = MemBioSlice::new(data)?; + + cvt(unsafe { ffi::OSSL_DECODER_from_bio(self.as_ptr(), bio.as_ptr()) }).map(|_| ()) + } +} + +#[allow(dead_code)] +pub(crate) struct Decoder<'a, T: SelectionT> { + selection: PhantomData, + key_type: Option, + format: Option, + structure: Option>, + passphrase: Option<&'a [u8]>, + #[allow(clippy::type_complexity)] + passphrase_callback: Option Result + 'a>>, +} + +impl<'a, T: SelectionT> Decoder<'a, T> { + #[allow(dead_code)] + pub(crate) fn new() -> Self { + Self { + selection: PhantomData, + key_type: None, + format: None, + structure: None, + passphrase: None, + passphrase_callback: None, + } + } + + #[allow(dead_code)] + pub fn set_key_type(mut self, key_type: Id) -> Self { + self.key_type = Some(key_type); + self + } + + #[allow(dead_code)] + pub fn set_format(mut self, format: KeyFormat) -> Self { + self.format = Some(format); + self + } + + #[allow(dead_code)] + pub fn set_structure(mut self, structure: Structure<'a>) -> Self { + self.structure = Some(structure); + self + } + + #[allow(dead_code)] + pub fn set_passphrase(mut self, passphrase: &'a [u8]) -> Self { + self.passphrase = Some(passphrase); + self + } + + #[allow(dead_code)] + pub fn set_passphrase_callback Result + 'a>( + mut self, + callback: F, + ) -> Self { + self.passphrase_callback = Some(Box::new(callback)); + self + } + + #[allow(dead_code)] + pub fn decode(self, data: &[u8]) -> Result, ErrorStack> { + let mut pkey_ptr = ptr::null_mut(); + let mut passphrase_callback_state; + let mut ctx = DecoderCtx::new_for_key( + &mut pkey_ptr, + T::SELECTION, + self.format, + self.structure, + self.key_type, + )?; + if let Some(passphrase) = self.passphrase { + ctx.set_passphrase(passphrase)?; + } + if let Some(passphrase_callback) = self.passphrase_callback { + passphrase_callback_state = CallbackState::new(passphrase_callback); + unsafe { ctx.set_passphrase_callback(&mut passphrase_callback_state)? }; + } + ctx.decode(data)?; + Ok(unsafe { PKey::from_ptr(pkey_ptr) }) + } +} + foreign_type_and_impl_send_sync! { type CType = ffi::OSSL_ENCODER_CTX; fn drop = ffi::OSSL_ENCODER_CTX_free; @@ -320,13 +498,155 @@ mod test { } } + mod decoder { + use super::*; + + mod params { + use super::*; + use crate::pkey::Params; + + #[test] + fn test_dh_pem() { + Decoder::::new() + .set_key_type(Id::DH) + .set_format(KeyFormat::Pem) + .set_structure(Structure::TypeSpecific) + .decode(include_bytes!("../test/dhparams.pem")) + .unwrap() + .dh() + .unwrap(); + } + + #[test] + fn test_dh_der() { + Decoder::::new() + .set_key_type(Id::DH) + .set_format(KeyFormat::Der) + .set_structure(Structure::TypeSpecific) + .decode(include_bytes!("../test/dhparams.der")) + .unwrap() + .dh() + .unwrap(); + } + } + mod public { + use super::*; + use crate::pkey::Public; + + #[test] + fn test_rsa_pem() { + Decoder::::new() + .set_key_type(Id::RSA) + .set_format(KeyFormat::Pem) + .set_structure(Structure::SubjectPublicKeyInfo) + .decode(include_bytes!("../test/rsa.pem.pub")) + .unwrap() + .rsa() + .unwrap(); + } + + #[test] + fn test_rsa_pem_pkcs1() { + Decoder::::new() + .set_key_type(Id::RSA) + .set_format(KeyFormat::Pem) + .set_structure(Structure::PKCS1) + .decode(include_bytes!("../test/pkcs1.pem.pub")) + .unwrap() + .rsa() + .unwrap(); + } + + #[test] + fn test_rsa_der() { + Decoder::::new() + .set_key_type(Id::RSA) + .set_format(KeyFormat::Der) + .set_structure(Structure::SubjectPublicKeyInfo) + .decode(include_bytes!("../test/key.der.pub")) + .unwrap() + .rsa() + .unwrap(); + } + + #[test] + fn test_rsa_der_pkcs1() { + Decoder::::new() + .set_key_type(Id::RSA) + .set_format(KeyFormat::Der) + .set_structure(Structure::PKCS1) + .decode(include_bytes!("../test/pkcs1.der.pub")) + .unwrap() + .rsa() + .unwrap(); + } + } + mod private { + use super::*; + use crate::pkey::Private; + + #[test] + fn test_rsa_pem() { + Decoder::::new() + .set_key_type(Id::RSA) + .set_format(KeyFormat::Pem) + .set_structure(Structure::PKCS1) + .decode(include_bytes!("../test/rsa.pem")) + .unwrap() + .rsa() + .unwrap(); + } + + #[test] + fn test_rsa_pem_passphrase() { + Decoder::::new() + .set_key_type(Id::RSA) + .set_format(KeyFormat::Pem) + .set_structure(Structure::PKCS1) + .set_passphrase(b"mypass") + .decode(include_bytes!("../test/rsa-encrypted.pem")) + .unwrap() + .rsa() + .unwrap(); + } + + #[test] + fn test_rsa_pem_callback() { + let mut password_queried = false; + Decoder::::new() + .set_key_type(Id::RSA) + .set_format(KeyFormat::Pem) + .set_structure(Structure::PKCS1) + .set_passphrase_callback(|password| { + password_queried = true; + password[..6].copy_from_slice(b"mypass"); + Ok(6) + }) + .decode(include_bytes!("../test/rsa-encrypted.pem")) + .unwrap(); + assert!(password_queried); + } + + #[test] + fn test_rsa_der() { + Decoder::::new() + .set_key_type(Id::RSA) + .set_format(KeyFormat::Der) + .set_structure(Structure::PKCS1) + .decode(include_bytes!("../test/key.der")) + .unwrap() + .rsa() + .unwrap(); + } + } + } + mod encoder { use super::*; mod params { use super::*; use crate::dh::Dh; - use crate::pkey::Id; use crate::pkey::Params; use crate::pkey_ctx::PkeyCtx;