From 72a2574edad0dc537a0ab06e166f8e5732e65d79 Mon Sep 17 00:00:00 2001 From: Jakub Jelen Date: Thu, 13 Mar 2025 11:31:20 +0100 Subject: [PATCH 1/4] Add internal module to simplify working with OSSL_PARAM structure We discussed that this API is not well suitable for the end users but still, it required for several operations in OpenSSL 3.* so instead of calling to FFI for every use of this API, this introduces simple wrappers that allow building of the params and their usage. Signed-off-by: Jakub Jelen --- openssl-sys/build/run_bindgen.rs | 1 + openssl-sys/src/handwritten/mod.rs | 6 + openssl-sys/src/handwritten/param_build.rs | 32 ++++ openssl-sys/src/handwritten/params.rs | 23 ++- openssl-sys/src/handwritten/types.rs | 3 + openssl/src/lib.rs | 2 + openssl/src/ossl_param.rs | 178 +++++++++++++++++++++ systest/build.rs | 3 +- 8 files changed, 244 insertions(+), 4 deletions(-) create mode 100644 openssl-sys/src/handwritten/param_build.rs create mode 100644 openssl/src/ossl_param.rs diff --git a/openssl-sys/build/run_bindgen.rs b/openssl-sys/build/run_bindgen.rs index 27bd482b3..a90fb9293 100644 --- a/openssl-sys/build/run_bindgen.rs +++ b/openssl-sys/build/run_bindgen.rs @@ -53,6 +53,7 @@ const INCLUDES: &str = " #endif #if OPENSSL_VERSION_NUMBER >= 0x30000000 +#include #include #endif diff --git a/openssl-sys/src/handwritten/mod.rs b/openssl-sys/src/handwritten/mod.rs index 47b3360fd..33e405221 100644 --- a/openssl-sys/src/handwritten/mod.rs +++ b/openssl-sys/src/handwritten/mod.rs @@ -15,6 +15,9 @@ pub use self::hmac::*; pub use self::kdf::*; pub use self::object::*; pub use self::ocsp::*; +#[cfg(ossl300)] +pub use self::param_build::*; +#[cfg(ossl300)] pub use self::params::*; pub use self::pem::*; pub use self::pkcs12::*; @@ -54,6 +57,9 @@ mod hmac; mod kdf; mod object; mod ocsp; +#[cfg(ossl300)] +mod param_build; +#[cfg(ossl300)] mod params; mod pem; mod pkcs12; diff --git a/openssl-sys/src/handwritten/param_build.rs b/openssl-sys/src/handwritten/param_build.rs new file mode 100644 index 000000000..7efbdb99c --- /dev/null +++ b/openssl-sys/src/handwritten/param_build.rs @@ -0,0 +1,32 @@ +use super::super::*; +use libc::*; + +/* OpenSSL 3.* only */ + +extern "C" { + pub fn OSSL_PARAM_BLD_new() -> *mut OSSL_PARAM_BLD; + pub fn OSSL_PARAM_BLD_free(bld: *mut OSSL_PARAM_BLD); + pub fn OSSL_PARAM_BLD_push_BN( + bld: *mut OSSL_PARAM_BLD, + key: *const c_char, + bn: *const BIGNUM, + ) -> c_int; + pub fn OSSL_PARAM_BLD_push_utf8_string( + bld: *mut OSSL_PARAM_BLD, + key: *const c_char, + buf: *const c_char, + bsize: usize, + ) -> c_int; + pub fn OSSL_PARAM_BLD_push_octet_string( + bld: *mut OSSL_PARAM_BLD, + key: *const c_char, + buf: *const c_void, + bsize: usize, + ) -> c_int; + pub fn OSSL_PARAM_BLD_push_uint( + bld: *mut OSSL_PARAM_BLD, + key: *const c_char, + buf: c_uint, + ) -> c_int; + pub fn OSSL_PARAM_BLD_to_param(bld: *mut OSSL_PARAM_BLD) -> *mut OSSL_PARAM; +} diff --git a/openssl-sys/src/handwritten/params.rs b/openssl-sys/src/handwritten/params.rs index 542cef337..913cc0e23 100644 --- a/openssl-sys/src/handwritten/params.rs +++ b/openssl-sys/src/handwritten/params.rs @@ -2,15 +2,32 @@ use super::super::*; use libc::*; extern "C" { - #[cfg(ossl300)] + pub fn OSSL_PARAM_free(p: *mut OSSL_PARAM); pub fn OSSL_PARAM_construct_uint(key: *const c_char, buf: *mut c_uint) -> OSSL_PARAM; - #[cfg(ossl300)] pub fn OSSL_PARAM_construct_end() -> OSSL_PARAM; - #[cfg(ossl300)] pub fn OSSL_PARAM_construct_octet_string( key: *const c_char, buf: *mut c_void, bsize: size_t, ) -> OSSL_PARAM; + pub fn OSSL_PARAM_locate(p: *mut OSSL_PARAM, key: *const c_char) -> *mut OSSL_PARAM; + pub fn OSSL_PARAM_get_BN(p: *const OSSL_PARAM, val: *mut *mut BIGNUM) -> c_int; + pub fn OSSL_PARAM_get_utf8_string( + p: *const OSSL_PARAM, + val: *mut *mut c_char, + max_len: usize, + ) -> c_int; + pub fn OSSL_PARAM_get_utf8_string_ptr(p: *const OSSL_PARAM, val: *mut *const c_char) -> c_int; + pub fn OSSL_PARAM_get_octet_string( + p: *const OSSL_PARAM, + val: *mut *mut c_void, + max_len: usize, + used_len: *mut usize, + ) -> c_int; + pub fn OSSL_PARAM_get_octet_string_ptr( + p: *const OSSL_PARAM, + val: *mut *const c_void, + used_len: *mut usize, + ) -> c_int; } diff --git a/openssl-sys/src/handwritten/types.rs b/openssl-sys/src/handwritten/types.rs index d465a4414..6fda6fa6e 100644 --- a/openssl-sys/src/handwritten/types.rs +++ b/openssl-sys/src/handwritten/types.rs @@ -1140,6 +1140,9 @@ pub struct OSSL_PARAM { return_size: size_t, } +#[cfg(ossl300)] +pub enum OSSL_PARAM_BLD {} + #[cfg(ossl300)] pub enum EVP_KDF {} #[cfg(ossl300)] diff --git a/openssl/src/lib.rs b/openssl/src/lib.rs index c58e5bf59..b482c8084 100644 --- a/openssl/src/lib.rs +++ b/openssl/src/lib.rs @@ -177,6 +177,8 @@ pub mod memcmp; pub mod nid; #[cfg(not(osslconf = "OPENSSL_NO_OCSP"))] pub mod ocsp; +#[cfg(ossl300)] +mod ossl_param; pub mod pkcs12; pub mod pkcs5; #[cfg(not(boringssl))] diff --git a/openssl/src/ossl_param.rs b/openssl/src/ossl_param.rs new file mode 100644 index 000000000..0434e462a --- /dev/null +++ b/openssl/src/ossl_param.rs @@ -0,0 +1,178 @@ +//! OSSL_PARAM management for OpenSSL 3.* +//! +//! The OSSL_PARAM structure represents generic attribute that can represent various +//! properties in OpenSSL, including keys and operations. +//! +//! For convinience, the OSSL_PARAM_BLD builder can be used to dynamically construct +//! these structure. +//! +//! Note, that this module is available only in OpenSSL 3.* and +//! only internally for this crate! +//! +use crate::bn::{BigNum, BigNumRef}; +use crate::error::ErrorStack; +use crate::util; +use crate::{cvt, cvt_p}; +use foreign_types::{ForeignType, ForeignTypeRef}; +use libc::{c_char, c_uint, c_void}; +use openssl_macros::corresponds; +use std::ffi::CStr; +use std::ptr; + +foreign_type_and_impl_send_sync! { + type CType = ffi::OSSL_PARAM; + fn drop = ffi::OSSL_PARAM_free; + + /// `OsslParam` constructed using `OsslParamBuilder`. + pub struct OsslParam; + /// Reference to `OsslParam`. + pub struct OsslParamRef; +} + +impl OsslParam {} + +impl OsslParamRef { + /// Locates the `OsslParam` in the `OsslParam` array + #[corresponds(OSSL_PARAM_locate)] + pub fn locate(&self, key: &[u8]) -> Result<&OsslParamRef, ErrorStack> { + unsafe { + let param = cvt_p(ffi::OSSL_PARAM_locate( + self.as_ptr(), + key.as_ptr() as *const c_char, + ))?; + Ok(OsslParamRef::from_ptr(param)) + } + } + + /// Get `BigNum` from the current `OsslParam` + #[corresponds(OSSL_PARAM_get_BN)] + pub fn get_bn(&self) -> Result { + unsafe { + let mut bn: *mut ffi::BIGNUM = ptr::null_mut(); + cvt(ffi::OSSL_PARAM_get_BN(self.as_ptr(), &mut bn))?; + Ok(BigNum::from_ptr(bn)) + } + } + + /// Get `&str` from the current `OsslParam` + #[corresponds(OSSL_PARAM_get_utf8_string)] + pub fn get_utf8_string(&self) -> Result<&str, ErrorStack> { + unsafe { + let mut val: *const c_char = ptr::null_mut(); + cvt(ffi::OSSL_PARAM_get_utf8_string_ptr(self.as_ptr(), &mut val))?; + Ok(CStr::from_ptr(val).to_str().unwrap()) + } + } + + /// Get octet string (as `&[u8]) from the current `OsslParam` + #[corresponds(OSSL_PARAM_get_octet_string)] + pub fn get_octet_string(&self) -> Result<&[u8], ErrorStack> { + unsafe { + let mut val: *const c_void = ptr::null_mut(); + let mut val_len: usize = 0; + cvt(ffi::OSSL_PARAM_get_octet_string_ptr( + self.as_ptr(), + &mut val, + &mut val_len, + ))?; + Ok(util::from_raw_parts(val as *const u8, val_len)) + } + } +} + +foreign_type_and_impl_send_sync! { + type CType = ffi::OSSL_PARAM_BLD; + fn drop = ffi::OSSL_PARAM_BLD_free; + + /// Builder used to construct `OsslParam`. + pub struct OsslParamBuilder; + /// Reference to `OsslParamBuilder`. + pub struct OsslParamBuilderRef; +} + +impl OsslParamBuilder { + /// Returns a builder for a OsslParam arrays. + /// + /// The array is initially empty. + #[corresponds(OSSL_PARAM_BLD_new)] + pub fn new() -> Result { + unsafe { + ffi::init(); + + cvt_p(ffi::OSSL_PARAM_BLD_new()).map(OsslParamBuilder) + } + } + + /// Constructs the `OsslParam`. + #[corresponds(OSSL_PARAM_BLD_to_param)] + pub fn to_param(&self) -> Result { + unsafe { + let params = cvt_p(ffi::OSSL_PARAM_BLD_to_param(self.0))?; + Ok(OsslParam::from_ptr(params)) + } + } +} + +impl OsslParamBuilderRef { + /// Adds a `BigNum` to `OsslParamBuilder`. + /// + /// Note, that both key and bn need to exist until the `to_param` is called! + #[corresponds(OSSL_PARAM_BLD_push_BN)] + pub fn add_bn(&self, key: &[u8], bn: &BigNumRef) -> Result<(), ErrorStack> { + unsafe { + cvt(ffi::OSSL_PARAM_BLD_push_BN( + self.as_ptr(), + key.as_ptr() as *const c_char, + bn.as_ptr(), + )) + .map(|_| ()) + } + } + + /// Adds a utf8 string to `OsslParamBuilder`. + /// + /// Note, that both `key` and `buf` need to exist until the `to_param` is called! + #[corresponds(OSSL_PARAM_BLD_push_utf8_string)] + pub fn add_utf8_string(&self, key: &[u8], buf: &str) -> Result<(), ErrorStack> { + unsafe { + cvt(ffi::OSSL_PARAM_BLD_push_utf8_string( + self.as_ptr(), + key.as_ptr() as *const c_char, + buf.as_ptr() as *const c_char, + buf.len(), + )) + .map(|_| ()) + } + } + + /// Adds a octet string to `OsslParamBuilder`. + /// + /// Note, that both `key` and `buf` need to exist until the `to_param` is called! + #[corresponds(OSSL_PARAM_BLD_push_octet_string)] + pub fn add_octet_string(&self, key: &[u8], buf: &[u8]) -> Result<(), ErrorStack> { + unsafe { + cvt(ffi::OSSL_PARAM_BLD_push_octet_string( + self.as_ptr(), + key.as_ptr() as *const c_char, + buf.as_ptr() as *const c_void, + buf.len(), + )) + .map(|_| ()) + } + } + + /// Adds a unsigned int to `OsslParamBuilder`. + /// + /// Note, that both `key` and `buf` need to exist until the `to_param` is called! + #[corresponds(OSSL_PARAM_BLD_push_uint)] + pub fn add_uint(&self, key: &[u8], val: u32) -> Result<(), ErrorStack> { + unsafe { + cvt(ffi::OSSL_PARAM_BLD_push_uint( + self.as_ptr(), + key.as_ptr() as *const c_char, + val as c_uint, + )) + .map(|_| ()) + } + } +} diff --git a/systest/build.rs b/systest/build.rs index fc970f410..43b5c2334 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 341c313766ebe6e3d680410ab17160b750a10f25 Mon Sep 17 00:00:00 2001 From: Jakub Jelen Date: Thu, 13 Mar 2025 13:01:33 +0100 Subject: [PATCH 2/4] kdf: Use the OsslParam API to simplify building OSSL_PARAM Signed-off-by: Jakub Jelen --- openssl/src/kdf.rs | 107 ++++++++++++++++++--------------------------- 1 file changed, 42 insertions(+), 65 deletions(-) diff --git a/openssl/src/kdf.rs b/openssl/src/kdf.rs index a5da35250..13ad2899e 100644 --- a/openssl/src/kdf.rs +++ b/openssl/src/kdf.rs @@ -25,14 +25,23 @@ impl Drop for EvpKdfCtx { cfg_if::cfg_if! { if #[cfg(all(ossl320, not(osslconf = "OPENSSL_NO_ARGON2")))] { use std::cmp; - use std::ffi::c_void; - use std::mem::MaybeUninit; use std::ptr; use foreign_types::ForeignTypeRef; use libc::c_char; use crate::{cvt, cvt_p}; use crate::lib_ctx::LibCtxRef; use crate::error::ErrorStack; + use crate::ossl_param::OsslParamBuilder; + + const OSSL_KDF_PARAM_PASSWORD: &[u8; 5] = b"pass\0"; + const OSSL_KDF_PARAM_SALT: &[u8; 5] = b"salt\0"; + const OSSL_KDF_PARAM_SECRET: &[u8; 7] = b"secret\0"; + const OSSL_KDF_PARAM_ITER: &[u8; 5] = b"iter\0"; + const OSSL_KDF_PARAM_SIZE: &[u8; 5] = b"size\0"; + const OSSL_KDF_PARAM_THREADS: &[u8; 8] = b"threads\0"; + const OSSL_KDF_PARAM_ARGON2_AD: &[u8; 3] = b"ad\0"; + const OSSL_KDF_PARAM_ARGON2_LANES: &[u8; 6] = b"lanes\0"; + const OSSL_KDF_PARAM_ARGON2_MEMCOST: &[u8; 8] = b"memcost\0"; /// Derives a key using the argon2id algorithm. /// @@ -48,72 +57,40 @@ cfg_if::cfg_if! { salt: &[u8], ad: Option<&[u8]>, secret: Option<&[u8]>, - mut iter: u32, - mut lanes: u32, - mut memcost: u32, + iter: u32, + lanes: u32, + memcost: u32, out: &mut [u8], ) -> Result<(), ErrorStack> { - unsafe { + let libctx = ctx.map_or(ptr::null_mut(), ForeignTypeRef::as_ptr); + let max_threads = unsafe { ffi::init(); - let libctx = ctx.map_or(ptr::null_mut(), ForeignTypeRef::as_ptr); - - let max_threads = ffi::OSSL_get_max_threads(libctx); - let mut threads = 1; - // If max_threads is 0, then this isn't a threaded build. - // If max_threads is > u32::MAX we need to clamp since - // argon2id's threads parameter is a u32. - if max_threads > 0 { - threads = cmp::min(lanes, cmp::min(max_threads, u32::MAX as u64) as u32); - } - let mut params: [ffi::OSSL_PARAM; 10] = - core::array::from_fn(|_| MaybeUninit::::zeroed().assume_init()); - let mut idx = 0; - params[idx] = ffi::OSSL_PARAM_construct_octet_string( - b"pass\0".as_ptr() as *const c_char, - pass.as_ptr() as *mut c_void, - pass.len(), - ); - idx += 1; - params[idx] = ffi::OSSL_PARAM_construct_octet_string( - b"salt\0".as_ptr() as *const c_char, - salt.as_ptr() as *mut c_void, - salt.len(), - ); - idx += 1; - params[idx] = - ffi::OSSL_PARAM_construct_uint(b"threads\0".as_ptr() as *const c_char, &mut threads); - idx += 1; - params[idx] = - ffi::OSSL_PARAM_construct_uint(b"lanes\0".as_ptr() as *const c_char, &mut lanes); - idx += 1; - params[idx] = - ffi::OSSL_PARAM_construct_uint(b"memcost\0".as_ptr() as *const c_char, &mut memcost); - idx += 1; - params[idx] = - ffi::OSSL_PARAM_construct_uint(b"iter\0".as_ptr() as *const c_char, &mut iter); - idx += 1; - let mut size = out.len() as u32; - params[idx] = - ffi::OSSL_PARAM_construct_uint(b"size\0".as_ptr() as *const c_char, &mut size); - idx += 1; - if let Some(ad) = ad { - params[idx] = ffi::OSSL_PARAM_construct_octet_string( - b"ad\0".as_ptr() as *const c_char, - ad.as_ptr() as *mut c_void, - ad.len(), - ); - idx += 1; - } - if let Some(secret) = secret { - params[idx] = ffi::OSSL_PARAM_construct_octet_string( - b"secret\0".as_ptr() as *const c_char, - secret.as_ptr() as *mut c_void, - secret.len(), - ); - idx += 1; - } - params[idx] = ffi::OSSL_PARAM_construct_end(); - + ffi::OSSL_get_max_threads(libctx) + }; + let mut threads = 1; + // If max_threads is 0, then this isn't a threaded build. + // If max_threads is > u32::MAX we need to clamp since + // argon2id's threads parameter is a u32. + if max_threads > 0 { + threads = cmp::min(lanes, cmp::min(max_threads, u32::MAX as u64) as u32); + } + let bld = OsslParamBuilder::new()?; + bld.add_octet_string(OSSL_KDF_PARAM_PASSWORD, pass)?; + bld.add_octet_string(OSSL_KDF_PARAM_SALT, salt)?; + bld.add_uint(OSSL_KDF_PARAM_THREADS, threads)?; + bld.add_uint(OSSL_KDF_PARAM_ARGON2_LANES, lanes)?; + bld.add_uint(OSSL_KDF_PARAM_ARGON2_MEMCOST, memcost)?; + bld.add_uint(OSSL_KDF_PARAM_ITER, iter)?; + let size = out.len() as u32; + bld.add_uint(OSSL_KDF_PARAM_SIZE, size)?; + if let Some(ad) = ad { + bld.add_octet_string(OSSL_KDF_PARAM_ARGON2_AD, ad)?; + } + if let Some(secret) = secret { + bld.add_octet_string(OSSL_KDF_PARAM_SECRET, secret)?; + } + let params = bld.to_param()?; + unsafe { let argon2 = EvpKdf(cvt_p(ffi::EVP_KDF_fetch( libctx, b"ARGON2ID\0".as_ptr() as *const c_char, From b0afadd7fe4bc94ad367a3ed556b64278e48e110 Mon Sep 17 00:00:00 2001 From: Jakub Jelen Date: Thu, 13 Mar 2025 11:50:47 +0100 Subject: [PATCH 3/4] Use the new OpenSSL 3.* API for managing EVP_PKEY The OpenSSL 3.* users now do not have a way to use non-deprecated API by using this rust bindings, which is not sustainable in the long term as either distributions will stop building with the deprecated API or it will be eventually removed. This is now mostly PoC on using RSA keys using the new API. It does not expose OSSL_PARAM API as that is considered fragile. This is partially based on #2051 which was abandoned. Fixes: #2047 Signed-off-by: Jakub Jelen --- openssl-sys/src/core_dispatch.rs | 11 + openssl-sys/src/evp.rs | 9 + openssl-sys/src/handwritten/evp.rs | 27 +++ openssl-sys/src/lib.rs | 4 + openssl/src/lib.rs | 2 + openssl/src/pkey_ctx.rs | 56 +++++ openssl/src/pkey_rsa.rs | 331 +++++++++++++++++++++++++++++ 7 files changed, 440 insertions(+) create mode 100644 openssl-sys/src/core_dispatch.rs create mode 100644 openssl/src/pkey_rsa.rs diff --git a/openssl-sys/src/core_dispatch.rs b/openssl-sys/src/core_dispatch.rs new file mode 100644 index 000000000..446dfc96e --- /dev/null +++ b/openssl-sys/src/core_dispatch.rs @@ -0,0 +1,11 @@ +use super::*; +use libc::*; + +/* OpenSSL 3.* only */ + +pub const OSSL_KEYMGMT_SELECT_PRIVATE_KEY: c_int = 0x01; +pub const OSSL_KEYMGMT_SELECT_PUBLIC_KEY: c_int = 0x02; +pub const OSSL_KEYMGMT_SELECT_DOMAIN_PARAMETERS: c_int = 0x04; +pub const OSSL_KEYMGMT_SELECT_OTHER_PARAMETERS: c_int = 0x80; +pub const OSSL_KEYMGMT_SELECT_ALL_PARAMETERS: c_int = + OSSL_KEYMGMT_SELECT_DOMAIN_PARAMETERS | OSSL_KEYMGMT_SELECT_OTHER_PARAMETERS; diff --git a/openssl-sys/src/evp.rs b/openssl-sys/src/evp.rs index 4d26f0f60..6ace42b86 100644 --- a/openssl-sys/src/evp.rs +++ b/openssl-sys/src/evp.rs @@ -38,6 +38,15 @@ pub const EVP_CTRL_GCM_SET_IVLEN: c_int = 0x9; pub const EVP_CTRL_GCM_GET_TAG: c_int = 0x10; pub const EVP_CTRL_GCM_SET_TAG: c_int = 0x11; +#[cfg(ossl300)] +pub const EVP_PKEY_KEY_PARAMETERS: c_int = OSSL_KEYMGMT_SELECT_ALL_PARAMETERS; +#[cfg(ossl300)] +pub const EVP_PKEY_PRIVATE_KEY: c_int = EVP_PKEY_KEY_PARAMETERS | OSSL_KEYMGMT_SELECT_PRIVATE_KEY; +#[cfg(ossl300)] +pub const EVP_PKEY_PUBLIC_KEY: c_int = EVP_PKEY_KEY_PARAMETERS | OSSL_KEYMGMT_SELECT_PUBLIC_KEY; +#[cfg(ossl300)] +pub const EVP_PKEY_KEYPAIR: c_int = EVP_PKEY_PUBLIC_KEY | OSSL_KEYMGMT_SELECT_PRIVATE_KEY; + pub unsafe fn EVP_get_digestbynid(type_: c_int) -> *const EVP_MD { EVP_get_digestbyname(OBJ_nid2sn(type_)) } diff --git a/openssl-sys/src/handwritten/evp.rs b/openssl-sys/src/handwritten/evp.rs index a1be1da68..6fe190b3f 100644 --- a/openssl-sys/src/handwritten/evp.rs +++ b/openssl-sys/src/handwritten/evp.rs @@ -489,6 +489,27 @@ extern "C" { #[cfg(any(ossl110, libressl270))] pub fn EVP_PKEY_up_ref(pkey: *mut EVP_PKEY) -> c_int; + #[cfg(ossl300)] + pub fn EVP_PKEY_fromdata_init(ctx: *mut EVP_PKEY_CTX) -> c_int; + + #[cfg(ossl300)] + pub fn EVP_PKEY_fromdata( + ctx: *mut EVP_PKEY_CTX, + ppkey: *mut *mut EVP_PKEY, + selection: c_int, + param: *mut OSSL_PARAM, + ) -> c_int; + + #[cfg(ossl300)] + pub fn EVP_PKEY_todata( + ppkey: *const EVP_PKEY, + selection: c_int, + param: *mut *mut OSSL_PARAM, + ) -> c_int; + + #[cfg(ossl300)] + pub fn EVP_PKEY_generate(ctx: *mut EVP_PKEY_CTX, k: *mut *mut EVP_PKEY) -> c_int; + pub fn d2i_AutoPrivateKey( a: *mut *mut EVP_PKEY, pp: *mut *const c_uchar, @@ -535,6 +556,12 @@ extern "C" { pub fn EVP_PKEY_CTX_new(k: *mut EVP_PKEY, e: *mut ENGINE) -> *mut EVP_PKEY_CTX; pub fn EVP_PKEY_CTX_new_id(id: c_int, e: *mut ENGINE) -> *mut EVP_PKEY_CTX; + #[cfg(ossl300)] + pub fn EVP_PKEY_CTX_new_from_name( + libctx: *mut OSSL_LIB_CTX, + name: *const c_char, + propquery: *const c_char, + ) -> *mut EVP_PKEY_CTX; pub fn EVP_PKEY_CTX_free(ctx: *mut EVP_PKEY_CTX); pub fn EVP_PKEY_CTX_ctrl( diff --git a/openssl-sys/src/lib.rs b/openssl-sys/src/lib.rs index 0a262db89..c2adacae6 100644 --- a/openssl-sys/src/lib.rs +++ b/openssl-sys/src/lib.rs @@ -42,6 +42,8 @@ mod openssl { pub use self::bio::*; pub use self::bn::*; pub use self::cms::*; + #[cfg(ossl300)] + pub use self::core_dispatch::*; pub use self::crypto::*; pub use self::dtls1::*; pub use self::ec::*; @@ -72,6 +74,8 @@ mod openssl { mod bio; mod bn; mod cms; + #[cfg(ossl300)] + mod core_dispatch; mod crypto; mod dtls1; mod ec; diff --git a/openssl/src/lib.rs b/openssl/src/lib.rs index b482c8084..576eeeb71 100644 --- a/openssl/src/lib.rs +++ b/openssl/src/lib.rs @@ -186,6 +186,8 @@ pub mod pkcs7; pub mod pkey; pub mod pkey_ctx; #[cfg(ossl300)] +pub mod pkey_rsa; +#[cfg(ossl300)] pub mod provider; pub mod rand; pub mod rsa; diff --git a/openssl/src/pkey_ctx.rs b/openssl/src/pkey_ctx.rs index 47188111c..ee310db3c 100644 --- a/openssl/src/pkey_ctx.rs +++ b/openssl/src/pkey_ctx.rs @@ -67,6 +67,8 @@ let cmac_key = ctx.keygen().unwrap(); #[cfg(not(boringssl))] use crate::cipher::CipherRef; use crate::error::ErrorStack; +#[cfg(ossl300)] +use crate::lib_ctx::LibCtxRef; use crate::md::MdRef; use crate::pkey::{HasPrivate, HasPublic, Id, PKey, PKeyRef, Private}; use crate::rsa::Padding; @@ -81,6 +83,8 @@ use openssl_macros::corresponds; use std::convert::TryFrom; #[cfg(ossl320)] use std::ffi::CStr; +#[cfg(ossl300)] +use std::ffi::CString; use std::ptr; /// HKDF modes of operation. @@ -156,6 +160,26 @@ impl PkeyCtx<()> { Ok(PkeyCtx::from_ptr(ptr)) } } + + /// Creates a new pkey context from the algorithm name. + #[corresponds(EVP_PKEY_CTX_new_from_name)] + #[cfg(ossl300)] + pub fn new_from_name( + libctx: Option<&LibCtxRef>, + name: &str, + propquery: Option<&str>, + ) -> Result { + unsafe { + let propquery = propquery.map(|s| CString::new(s).unwrap()); + let name = CString::new(name).unwrap(); + let ptr = cvt_p(ffi::EVP_PKEY_CTX_new_from_name( + libctx.map_or(ptr::null_mut(), ForeignTypeRef::as_ptr), + name.as_ptr(), + propquery.map_or(ptr::null_mut(), |s| s.as_ptr()), + ))?; + Ok(PkeyCtx::from_ptr(ptr)) + } + } } impl PkeyCtxRef @@ -756,6 +780,20 @@ impl PkeyCtxRef { Ok(()) } + /// Generates a new public/private keypair. + /// + /// New OpenSSL 3.0 function, that should do the same thing as keygen() + #[corresponds(EVP_PKEY_generate)] + #[cfg(ossl300)] + #[inline] + pub fn generate(&mut self) -> Result, ErrorStack> { + unsafe { + let mut key = ptr::null_mut(); + cvt(ffi::EVP_PKEY_generate(self.as_ptr(), &mut key))?; + Ok(PKey::from_ptr(key)) + } + } + /// Gets the nonce type for a private key context. /// /// The nonce for DSA and ECDSA can be either random (the default) or deterministic (as defined by RFC 6979). @@ -780,6 +818,14 @@ impl PkeyCtxRef { } Ok(NonceType(nonce_type)) } + + /// Initializes a conversion from `OsslParam` to `PKey` on given `PkeyCtx`. + #[corresponds(EVP_PKEY_fromdata_init)] + #[cfg(ossl300)] + pub fn fromdata_init(&mut self) -> Result<(), ErrorStack> { + unsafe { cvt(ffi::EVP_PKEY_fromdata_init(self.as_ptr()))? }; + Ok(()) + } } #[cfg(test)] @@ -1107,4 +1153,14 @@ mxJ7imIrEg9nIQ== assert_eq!(output, expected_output); assert!(ErrorStack::get().errors().is_empty()); } + + #[test] + #[cfg(ossl300)] + fn test_pkeyctx_from_name() { + let lib_ctx = crate::lib_ctx::LibCtx::new().unwrap(); + let _: PkeyCtx<()> = PkeyCtx::new_from_name(Some(lib_ctx.as_ref()), "RSA", None).unwrap(); + + /* no libctx is ok */ + let _: PkeyCtx<()> = PkeyCtx::new_from_name(None, "RSA", None).unwrap(); + } } diff --git a/openssl/src/pkey_rsa.rs b/openssl/src/pkey_rsa.rs new file mode 100644 index 000000000..3b9f20b61 --- /dev/null +++ b/openssl/src/pkey_rsa.rs @@ -0,0 +1,331 @@ +//! Rivest–Shamir–Adleman cryptosystem used through the OpenSSL 3.* API +//! +//! RSA is one of the earliest asymmetric public key encryption schemes. +//! Like many other cryptosystems, RSA relies on the presumed difficulty of a hard +//! mathematical problem, namely factorization of the product of two large prime +//! numbers. At the moment there does not exist an algorithm that can factor such +//! large numbers in reasonable time. RSA is used in a wide variety of +//! applications including digital signatures and key exchanges such as +//! establishing a TLS/SSL connection. +//! +//! The RSA acronym is derived from the first letters of the surnames of the +//! algorithm's founding trio. +//! +//! # Example +//! +//! Generate a 3096-bit RSA key pair and use the public key to encrypt some data. +//! +//! ```rust +//! use openssl::pkey::{PKey, Private}; +//! use openssl::pkey_ctx::PkeyCtx; +//! use openssl::pkey_rsa::PKeyRsaBuilder; +//! +//! let bld = PKeyRsaBuilder::::new_generate(3096, None).unwrap(); +//! let key = bld.generate().unwrap(); +//! +//! let mut ctx = PkeyCtx::new(&key).unwrap(); +//! ctx.encrypt_init().unwrap(); +//! +//! let data = b"Some Crypto Text"; +//! let mut ciphertext = vec![]; +//! ctx.encrypt_to_vec(data, &mut ciphertext).unwrap(); +//! ``` + +use foreign_types::ForeignType; +use libc::c_int; +use std::ptr; + +use crate::bn::{BigNum, BigNumRef}; +use crate::error::ErrorStack; +use crate::ossl_param::{OsslParam, OsslParamBuilder}; +use crate::pkey::{PKey, Private, Public}; +use crate::pkey_ctx::PkeyCtx; +use crate::{cvt, cvt_p}; +use openssl_macros::corresponds; + +const OSSL_PKEY_PARAM_RSA_BITS: &[u8; 5] = b"bits\0"; + +const OSSL_PKEY_PARAM_RSA_N: &[u8; 2] = b"n\0"; +const OSSL_PKEY_PARAM_RSA_E: &[u8; 2] = b"e\0"; +const OSSL_PKEY_PARAM_RSA_D: &[u8; 2] = b"d\0"; +const OSSL_PKEY_PARAM_RSA_FACTOR1: &[u8; 12] = b"rsa-factor1\0"; +const OSSL_PKEY_PARAM_RSA_FACTOR2: &[u8; 12] = b"rsa-factor2\0"; +const OSSL_PKEY_PARAM_RSA_EXPONENT1: &[u8; 14] = b"rsa-exponent1\0"; +const OSSL_PKEY_PARAM_RSA_EXPONENT2: &[u8; 14] = b"rsa-exponent2\0"; +const OSSL_PKEY_PARAM_RSA_COEFFICIENT1: &[u8; 17] = b"rsa-coefficient1\0"; + +pub struct PKeyRsaBuilder { + bld: OsslParamBuilder, + _m: ::std::marker::PhantomData, +} + +impl PKeyRsaBuilder { + /// Creates a new `PKeyRsaBuilder` to build RSA private or public keys. + /// + /// `n` is the modulus common to both public and private key. + /// `e` is the public exponent and `d` is the private exponent. + /// + pub fn new( + n: &BigNumRef, + e: &BigNumRef, + d: Option<&BigNumRef>, + ) -> Result, ErrorStack> { + let bld = OsslParamBuilder::new()?; + bld.add_bn(OSSL_PKEY_PARAM_RSA_N, n)?; + bld.add_bn(OSSL_PKEY_PARAM_RSA_E, e)?; + if let Some(d) = d { + bld.add_bn(OSSL_PKEY_PARAM_RSA_D, d)? + }; + Ok(PKeyRsaBuilder:: { + bld, + _m: ::std::marker::PhantomData, + }) + } + + /// Sets the factors of the private Rsa key for the builder. + /// + /// `p` and `q` are the first and second factors of `n`. + pub fn set_factors( + self, + p: &BigNumRef, + q: &BigNumRef, + ) -> Result, ErrorStack> { + self.bld.add_bn(OSSL_PKEY_PARAM_RSA_FACTOR1, p)?; + self.bld.add_bn(OSSL_PKEY_PARAM_RSA_FACTOR2, q)?; + Ok(self) + } + + /// Sets the Chinese Remainder Theorem params of the private Rsa key. + /// + /// `dmp1`, `dmq1`, and `iqmp` are the exponents and coefficient for + /// CRT calculations which is used to speed up RSA operations. + pub fn set_crt_params( + self, + dmp1: &BigNumRef, + dmq1: &BigNumRef, + iqmp: &BigNumRef, + ) -> Result, ErrorStack> { + self.bld.add_bn(OSSL_PKEY_PARAM_RSA_EXPONENT1, dmp1)?; + self.bld.add_bn(OSSL_PKEY_PARAM_RSA_EXPONENT2, dmq1)?; + self.bld.add_bn(OSSL_PKEY_PARAM_RSA_COEFFICIENT1, iqmp)?; + Ok(self) + } + + /// Build PKey. Internal. + #[corresponds(EVP_PKEY_fromdata)] + fn build_internal(self, selection: c_int) -> Result, ErrorStack> { + let mut ctx = PkeyCtx::new_from_name(None, "RSA", None)?; + ctx.fromdata_init()?; + let params = self.bld.to_param()?; + unsafe { + let evp = cvt_p(ffi::EVP_PKEY_new())?; + let pkey = PKey::from_ptr(evp); + cvt(ffi::EVP_PKEY_fromdata( + ctx.as_ptr(), + &mut pkey.as_ptr(), + selection, + params.as_ptr(), + ))?; + Ok(pkey) + } + } +} + +impl PKeyRsaBuilder { + /// Returns the Private RSA PKey from the provided parameters. + #[corresponds(EVP_PKEY_fromdata)] + pub fn build(self) -> Result, ErrorStack> { + self.build_internal(ffi::EVP_PKEY_PRIVATE_KEY) + } + + /// Creates a new `PKeyRsaBuilder` to generate a new RSA key pair + /// + pub fn new_generate(bits: u32, e: Option) -> Result, ErrorStack> { + let bld = OsslParamBuilder::new()?; + bld.add_uint(OSSL_PKEY_PARAM_RSA_BITS, bits)?; + if let Some(e) = e { + bld.add_uint(OSSL_PKEY_PARAM_RSA_E, e)? + }; + Ok(PKeyRsaBuilder:: { + bld, + _m: ::std::marker::PhantomData, + }) + } + + /// Generate RSA PKey. + pub fn generate(self) -> Result, ErrorStack> { + let mut ctx = PkeyCtx::new_from_name(None, "RSA", None)?; + ctx.keygen_init()?; + let params = self.bld.to_param()?; + unsafe { + cvt(ffi::EVP_PKEY_CTX_set_params(ctx.as_ptr(), params.as_ptr()))?; + } + ctx.generate() + } +} + +impl PKeyRsaBuilder { + /// Builds the Public RSA PKey from the provideded parameters. + #[corresponds(EVP_PKEY_fromdata)] + pub fn build(self) -> Result, ErrorStack> { + self.build_internal(ffi::EVP_PKEY_PUBLIC_KEY) + } +} + +pub struct PKeyRsaParams { + params: OsslParam, + _m: ::std::marker::PhantomData, +} + +impl PKeyRsaParams { + /// Creates a new `PKeyRsaParams` from existing RSA PKey. Internal. + #[corresponds(EVP_PKEY_todata)] + fn _new_from_pkey(pkey: &PKey, selection: c_int) -> Result, ErrorStack> { + unsafe { + let mut params: *mut ffi::OSSL_PARAM = ptr::null_mut(); + cvt(ffi::EVP_PKEY_todata(pkey.as_ptr(), selection, &mut params))?; + Ok(PKeyRsaParams:: { + params: OsslParam::from_ptr(params), + _m: ::std::marker::PhantomData, + }) + } + } + + /// Returns the modulus of the key. + pub fn n(&self) -> Result { + self.params.locate(OSSL_PKEY_PARAM_RSA_N)?.get_bn() + } + + /// Returns the public exponent of the key. + pub fn e(&self) -> Result { + self.params.locate(OSSL_PKEY_PARAM_RSA_E)?.get_bn() + } +} + +impl PKeyRsaParams { + /// Creates a new `PKeyRsaParams` from existing Private RSA PKey. + #[corresponds(EVP_PKEY_todata)] + pub fn from_pkey(pkey: &PKey) -> Result, ErrorStack> { + Self::_new_from_pkey(pkey, ffi::EVP_PKEY_PRIVATE_KEY) + } + + /// Returns the private exponent of the key. + pub fn d(&self) -> Result { + self.params.locate(OSSL_PKEY_PARAM_RSA_D)?.get_bn() + } + + /// Returns the first factor of the exponent of the key. + pub fn p(&self) -> Result { + self.params.locate(OSSL_PKEY_PARAM_RSA_FACTOR1)?.get_bn() + } + + /// Returns the second factor of the exponent of the key. + pub fn q(&self) -> Result { + self.params.locate(OSSL_PKEY_PARAM_RSA_FACTOR2)?.get_bn() + } + + /// Returns the first exponent used for CRT calculations. + pub fn dmp1(&self) -> Result { + self.params.locate(OSSL_PKEY_PARAM_RSA_EXPONENT1)?.get_bn() + } + + /// Returns the second exponent used for CRT calculations. + pub fn dmq1(&self) -> Result { + self.params.locate(OSSL_PKEY_PARAM_RSA_EXPONENT2)?.get_bn() + } + + /// Returns the coefficient used for CRT calculations. + pub fn iqmp(&self) -> Result { + self.params + .locate(OSSL_PKEY_PARAM_RSA_COEFFICIENT1)? + .get_bn() + } +} + +impl PKeyRsaParams { + /// Creates a new `PKeyRsaParams` from existing Public RSA PKey. + #[corresponds(EVP_PKEY_todata)] + pub fn from_pkey(pkey: &PKey) -> Result, ErrorStack> { + Self::_new_from_pkey(pkey, ffi::EVP_PKEY_PUBLIC_KEY) + } +} + +#[cfg(test)] +mod tests { + + use crate::error::Error; + use crate::rsa::Rsa; + + use super::*; + + #[test] + fn test_build_pkey_rsa_private() { + /* First, generate the key with old API */ + let rsa = Rsa::generate(2048).unwrap(); + let pkey1 = PKey::from_rsa(rsa.clone()).unwrap(); + + /* Now, build the new PKey from the old key with new API */ + let bld = PKeyRsaBuilder::::new(rsa.n(), rsa.e(), Some(rsa.d())) + .unwrap() + .set_factors(rsa.p().unwrap(), rsa.q().unwrap()) + .unwrap() + .set_crt_params( + rsa.dmp1().unwrap(), + rsa.dmq1().unwrap(), + rsa.iqmp().unwrap(), + ) + .unwrap(); + let pkey2 = bld.build().unwrap(); + + /* Verify it works the same way as the old one */ + assert!(pkey1.public_eq(&pkey2)); + assert!(Error::get().is_none()); + + let params = PKeyRsaParams::::from_pkey(&pkey2).unwrap(); + assert_eq!(¶ms.n().unwrap(), rsa.n()); + assert_eq!(¶ms.e().unwrap(), rsa.e()); + assert_eq!(¶ms.d().unwrap(), rsa.d()); + assert_eq!(¶ms.p().unwrap(), rsa.p().unwrap()); + assert_eq!(¶ms.q().unwrap(), rsa.q().unwrap()); + assert_eq!(¶ms.dmp1().unwrap(), rsa.dmp1().unwrap()); + assert_eq!(¶ms.dmq1().unwrap(), rsa.dmq1().unwrap()); + assert_eq!(¶ms.iqmp().unwrap(), rsa.iqmp().unwrap()); + } + + #[test] + fn test_build_pkey_rsa_public() { + /* First, generate the key with old API */ + let rsa = Rsa::generate(2048).unwrap(); + let pkey1 = PKey::from_rsa(rsa.clone()).unwrap(); + + /* Now, build the new public PKey from the old key with new API */ + let bld = PKeyRsaBuilder::::new(rsa.n(), rsa.e(), None).unwrap(); + let pkey2 = bld.build().unwrap(); + + /* Verify it works the same way as the old one */ + assert!(pkey1.public_eq(&pkey2)); + assert!(Error::get().is_none()); + + let params = PKeyRsaParams::::from_pkey(&pkey2).unwrap(); + assert_eq!(¶ms.n().unwrap(), rsa.n()); + assert_eq!(¶ms.e().unwrap(), rsa.e()); + } + + #[test] + fn test_generate_rsa() { + let bld = PKeyRsaBuilder::::new_generate(3096, None).unwrap(); + let key = bld.generate().unwrap(); + + let mut ctx = PkeyCtx::new(&key).unwrap(); + ctx.encrypt_init().unwrap(); + let pt = "Some Crypto Text".as_bytes(); + let mut ct = vec![]; + ctx.encrypt_to_vec(pt, &mut ct).unwrap(); + + ctx.decrypt_init().unwrap(); + let mut out = vec![]; + ctx.decrypt_to_vec(&ct, &mut out).unwrap(); + + assert_eq!(pt, out); + } +} From 6fb3d94b2401bcbdcf61e2a172ad8cbf98d1a674 Mon Sep 17 00:00:00 2001 From: Jakub Jelen Date: Thu, 6 Mar 2025 18:05:09 +0100 Subject: [PATCH 4/4] ECDSA support through OpenSSL 3.* API Signed-off-by: Jakub Jelen --- openssl/src/lib.rs | 2 + openssl/src/pkey_ecdsa.rs | 209 ++++++++++++++++++++++++++++++++++++++ 2 files changed, 211 insertions(+) create mode 100644 openssl/src/pkey_ecdsa.rs diff --git a/openssl/src/lib.rs b/openssl/src/lib.rs index 576eeeb71..bb0a265ab 100644 --- a/openssl/src/lib.rs +++ b/openssl/src/lib.rs @@ -186,6 +186,8 @@ pub mod pkcs7; pub mod pkey; pub mod pkey_ctx; #[cfg(ossl300)] +pub mod pkey_ecdsa; +#[cfg(ossl300)] pub mod pkey_rsa; #[cfg(ossl300)] pub mod provider; diff --git a/openssl/src/pkey_ecdsa.rs b/openssl/src/pkey_ecdsa.rs new file mode 100644 index 000000000..58a36f209 --- /dev/null +++ b/openssl/src/pkey_ecdsa.rs @@ -0,0 +1,209 @@ +//! Elliptic Curve using OpenSSL 3.* API +//! +//! Cryptography relies on the difficulty of solving mathematical problems, such as the factor +//! of large integers composed of two large prime numbers and the discrete logarithm of a +//! random elliptic curve. This module provides low-level features of the latter. +//! Elliptic Curve protocols can provide the same security with smaller keys. + +use foreign_types::ForeignType; +use libc::c_int; +use std::ptr; + +use crate::bn::{BigNum, BigNumRef}; +use crate::error::ErrorStack; +use crate::ossl_param::{OsslParam, OsslParamBuilder}; +use crate::pkey::{PKey, Private, Public}; +use crate::pkey_ctx::PkeyCtx; +use crate::{cvt, cvt_p}; +use openssl_macros::corresponds; + +const OSSL_PKEY_PARAM_GROUP_NAME: &[u8; 6] = b"group\0"; +const OSSL_PKEY_PARAM_PUB_KEY: &[u8; 4] = b"pub\0"; +const OSSL_PKEY_PARAM_PRIV_KEY: &[u8; 5] = b"priv\0"; + +pub struct PKeyEcdsaBuilder { + bld: OsslParamBuilder, + _m: ::std::marker::PhantomData, +} + +impl PKeyEcdsaBuilder { + /// Creates a new `PKeyEcdsaBuilder` to build ECDSA private or public keys. + /// + /// `n` is the modulus common to both public and private key. + /// `e` is the public exponent and `d` is the private exponent. + /// + pub fn new( + group: &str, + point: &[u8], + private: Option<&BigNumRef>, + ) -> Result, ErrorStack> { + let bld = OsslParamBuilder::new()?; + bld.add_utf8_string(OSSL_PKEY_PARAM_GROUP_NAME, group)?; + bld.add_octet_string(OSSL_PKEY_PARAM_PUB_KEY, point)?; + if let Some(private) = private { + bld.add_bn(OSSL_PKEY_PARAM_PRIV_KEY, private)? + }; + Ok(PKeyEcdsaBuilder:: { + bld, + _m: ::std::marker::PhantomData, + }) + } + + /// Build PKey. Internal. + #[corresponds(EVP_PKEY_fromdata)] + fn build_internal(self, selection: c_int) -> Result, ErrorStack> { + let mut ctx = PkeyCtx::new_from_name(None, "EC", None)?; + ctx.fromdata_init()?; + let params = self.bld.to_param()?; + unsafe { + let evp = cvt_p(ffi::EVP_PKEY_new())?; + let pkey = PKey::from_ptr(evp); + cvt(ffi::EVP_PKEY_fromdata( + ctx.as_ptr(), + &mut pkey.as_ptr(), + selection, + params.as_ptr(), + ))?; + Ok(pkey) + } + } +} + +impl PKeyEcdsaBuilder { + /// Returns the Private ECDSA PKey from the provided parameters. + #[corresponds(EVP_PKEY_fromdata)] + pub fn build(self) -> Result, ErrorStack> { + /* The ECDSA requires here a keypair as the private key does not work without public point! */ + self.build_internal(ffi::EVP_PKEY_KEYPAIR) + } +} + +impl PKeyEcdsaBuilder { + /// Returns the Public ECDSA PKey from the provided parameters. + #[corresponds(EVP_PKEY_fromdata)] + pub fn build(self) -> Result, ErrorStack> { + self.build_internal(ffi::EVP_PKEY_PUBLIC_KEY) + } +} + +pub struct PKeyEcdsaParams { + params: OsslParam, + _m: ::std::marker::PhantomData, +} + +impl PKeyEcdsaParams { + /// Creates a new `PKeyEcdsaParams` from existing ECDSA PKey. Internal. + #[corresponds(EVP_PKEY_todata)] + fn _new_from_pkey(pkey: &PKey, selection: c_int) -> Result, ErrorStack> { + unsafe { + let mut params: *mut ffi::OSSL_PARAM = ptr::null_mut(); + cvt(ffi::EVP_PKEY_todata(pkey.as_ptr(), selection, &mut params))?; + Ok(PKeyEcdsaParams:: { + params: OsslParam::from_ptr(params), + _m: ::std::marker::PhantomData, + }) + } + } + + /// Returns a reference to the public key. + pub fn public_key(&self) -> Result<&[u8], ErrorStack> { + self.params + .locate(OSSL_PKEY_PARAM_PUB_KEY)? + .get_octet_string() + } + + /// Returns a reference to a group name + pub fn group(&self) -> Result<&str, ErrorStack> { + self.params + .locate(OSSL_PKEY_PARAM_GROUP_NAME)? + .get_utf8_string() + } +} + +impl PKeyEcdsaParams { + /// Creates a new `PKeyEcdsaParams` from existing Public ECDSA PKey. + #[corresponds(EVP_PKEY_todata)] + pub fn from_pkey(pkey: &PKey) -> Result, ErrorStack> { + Self::_new_from_pkey(pkey, ffi::EVP_PKEY_PUBLIC_KEY) + } +} + +impl PKeyEcdsaParams { + /// Creates a new `PKeyEcdsaParams` from existing Private ECDSA PKey. + #[corresponds(EVP_PKEY_todata)] + pub fn from_pkey(pkey: &PKey) -> Result, ErrorStack> { + Self::_new_from_pkey(pkey, ffi::EVP_PKEY_KEYPAIR) + } + + /// Returns the private key. + pub fn private_key(&self) -> Result { + self.params.locate(OSSL_PKEY_PARAM_PRIV_KEY)?.get_bn() + } +} + +#[cfg(test)] +mod tests { + + use crate::bn::BigNumContext; + use crate::ec::{EcKey, PointConversionForm}; + use crate::error::Error; + use crate::nid::Nid; + + use super::*; + + #[test] + fn test_build_pkey_ecdsa_private() { + /* First, generate the key with old API */ + let nid: Nid = Nid::SECP256K1; + let curve_name = nid.short_name().unwrap(); + let group = crate::ec::EcGroup::from_curve_name(nid).unwrap(); + let ec_key = EcKey::generate(&group).unwrap(); + let pkey1 = PKey::from_ec_key(ec_key.clone()).unwrap(); + + /* Now, build the new PKey from the old key with new API */ + let mut ctx = BigNumContext::new().unwrap(); + let pubkey = ec_key + .public_key() + .to_bytes(&group, PointConversionForm::UNCOMPRESSED, &mut ctx) + .unwrap(); + let bld = PKeyEcdsaBuilder::::new(curve_name, &pubkey, Some(ec_key.private_key())) + .unwrap(); + let pkey2 = bld.build().unwrap(); + + /* Verify it works the same way as the old one */ + assert!(pkey1.public_eq(&pkey2)); + assert!(Error::get().is_none()); + + let params = PKeyEcdsaParams::::from_pkey(&pkey2).unwrap(); + assert_eq!(params.group().unwrap(), curve_name); + assert_eq!(¶ms.private_key().unwrap(), ec_key.private_key()); + assert_eq!(params.public_key().unwrap(), pubkey); + } + + #[test] + fn test_build_pkey_ecdsa_public() { + /* First, generate the key with old API */ + let nid: Nid = Nid::SECP256K1; + let curve_name = nid.short_name().unwrap(); + let group = crate::ec::EcGroup::from_curve_name(nid).unwrap(); + let ec_key = EcKey::generate(&group).unwrap(); + let pkey1 = PKey::from_ec_key(ec_key.clone()).unwrap(); + + /* Now, build the new PKey from the old key with new API */ + let mut ctx = BigNumContext::new().unwrap(); + let pubkey = ec_key + .public_key() + .to_bytes(&group, PointConversionForm::UNCOMPRESSED, &mut ctx) + .unwrap(); + let bld = PKeyEcdsaBuilder::::new(curve_name, &pubkey, None).unwrap(); + let pkey2 = bld.build().unwrap(); + + /* Verify it works the same way as the old one */ + assert!(pkey1.public_eq(&pkey2)); + assert!(Error::get().is_none()); + + let params = PKeyEcdsaParams::::from_pkey(&pkey2).unwrap(); + assert_eq!(params.group().unwrap(), curve_name); + assert_eq!(params.public_key().unwrap(), pubkey); + } +}