From fec04cebf48a95be41e679889e02f6dbfb74ef46 Mon Sep 17 00:00:00 2001 From: Jakub Jelen Date: Fri, 14 Feb 2025 21:20:46 +0100 Subject: [PATCH] New OpenSSL 3.* API for managing EVP_PKEY objects 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 and ECDSA keys using the new API in tests. It does not expose all possible API that are available as I did not have a good way to test the unused API yet. I do not know if this API is available in some other *SSL libraries right now so for now all of the additions are marked with #[cfg(ossl300)]. This is partially based on #2051 which was abandoned. Fixes: #2047 --- openssl-sys/build/run_bindgen.rs | 1 + openssl-sys/src/core_dispatch.rs | 11 + openssl-sys/src/evp.rs | 9 + openssl-sys/src/handwritten/evp.rs | 34 ++ 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-sys/src/lib.rs | 4 + openssl/src/lib.rs | 2 + openssl/src/ossl_param.rs | 341 +++++++++++++++++++++ openssl/src/pkey.rs | 70 +++++ openssl/src/pkey_ctx.rs | 72 ++++- systest/build.rs | 3 +- 14 files changed, 606 insertions(+), 5 deletions(-) create mode 100644 openssl-sys/src/core_dispatch.rs 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 27bd482b38..a90fb9293a 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/core_dispatch.rs b/openssl-sys/src/core_dispatch.rs new file mode 100644 index 0000000000..446dfc96e6 --- /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 4d26f0f607..6ace42b864 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 a1be1da682..1c543e1bb5 100644 --- a/openssl-sys/src/handwritten/evp.rs +++ b/openssl-sys/src/handwritten/evp.rs @@ -489,6 +489,34 @@ 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_set_bn_param( + k: *mut EVP_PKEY, + key_name: *const c_char, + bn: *const BIGNUM, + ) -> 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 +563,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/handwritten/mod.rs b/openssl-sys/src/handwritten/mod.rs index 47b3360fd8..33e405221c 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 0000000000..7efbdb99cc --- /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 542cef3374..913cc0e236 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 d465a44148..6fda6fa6e1 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-sys/src/lib.rs b/openssl-sys/src/lib.rs index 0e23386fd3..17c594b17d 100644 --- a/openssl-sys/src/lib.rs +++ b/openssl-sys/src/lib.rs @@ -45,6 +45,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::*; @@ -75,6 +77,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 c58e5bf598..796d64ad32 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)] +pub 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 0000000000..607c68ceed --- /dev/null +++ b/openssl/src/ossl_param.rs @@ -0,0 +1,341 @@ +//! 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.* +//! +//! # Example: Generate RSA Key +//! +//! let mut ctx = PkeyCtx::new_from_name(None, "RSA", None).unwrap(); +//! ctx.keygen_init().unwrap(); +//! let mut bld = OsslParamBuilder::new().unwrap(); +//! bld.add_uint("bits\0", 3096).unwrap(); +//! let params = bld.to_params().unwrap(); +//! ctx.set_params(params).unwrap(); +//! let key = ctx.generate().unwrap(); +//! +use crate::bn::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: &str) -> 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 `BigNumRef` from the current `OsslParam` + #[corresponds(OSSL_PARAM_get_BN)] + pub fn get_bn(&self) -> Result<&BigNumRef, ErrorStack> { + unsafe { + let mut bn: *mut ffi::BIGNUM = ptr::null_mut(); + cvt(ffi::OSSL_PARAM_get_BN(self.as_ptr(), &mut bn))?; + Ok(BigNumRef::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_params(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_params` is called! + #[corresponds(OSSL_PARAM_BLD_push_BN)] + pub fn add_bn(&mut self, key: &str, 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_params` is called! + #[corresponds(OSSL_PARAM_BLD_push_utf8_string)] + pub fn add_utf8_string(&mut self, key: &str, 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_params` is called! + #[corresponds(OSSL_PARAM_BLD_push_octet_string)] + pub fn add_octet_string(&mut self, key: &str, 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_params` is called! + #[corresponds(OSSL_PARAM_BLD_push_uint)] + pub fn add_uint(&mut self, key: &str, 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(|_| ()) + } + } +} + +#[cfg(test)] +mod tests { + + use crate::ec::EcKey; + use crate::error::Error; + use crate::nid::Nid; + use crate::pkey::{PKey, Private}; + use crate::pkey_ctx::PkeyCtx; + use crate::rsa::Rsa; + + use super::*; + + #[test] + fn test_build_pkey_rsa() { + /* First, generate the key with old API */ + let rsa = Rsa::generate(2048).unwrap(); + let pkey1 = PKey::from_rsa(rsa.clone()).unwrap(); + + /* Now, construct the OSSL_PARAM manually from the old key */ + let mut bld = OsslParamBuilder::new().unwrap(); + // TODO do we want a better API with the parameter names? + bld.add_bn("n\0", rsa.n()).unwrap(); + bld.add_bn("e\0", rsa.e()).unwrap(); + bld.add_bn("d\0", rsa.d()).unwrap(); + bld.add_bn("rsa-factor1\0", rsa.p().unwrap()).unwrap(); + bld.add_bn("rsa-factor2\0", rsa.q().unwrap()).unwrap(); + bld.add_bn("rsa-exponent1\0", rsa.dmp1().unwrap()).unwrap(); + bld.add_bn("rsa-exponent2\0", rsa.dmq1().unwrap()).unwrap(); + bld.add_bn("rsa-coefficient1\0", rsa.iqmp().unwrap()) + .unwrap(); + let params = bld.to_params().unwrap(); + + let mut ctx = PkeyCtx::new_from_name(None, "RSA", None).unwrap(); + ctx.fromdata_init().unwrap(); + let pkey2 = PKey::::fromdata(ctx, params).unwrap(); + + /* Verify it works the same way as the old one */ + assert!(pkey1.public_eq(&pkey2)); + assert!(Error::get().is_none()); + + // FIXME use of ffi in test is not intended -- we will need some constants + let params = pkey2.todata(ffi::EVP_PKEY_KEYPAIR).unwrap(); + assert_eq!(params.locate("n\0").unwrap().get_bn().unwrap(), rsa.n()); + assert_eq!(params.locate("e\0").unwrap().get_bn().unwrap(), rsa.e()); + assert_eq!(params.locate("d\0").unwrap().get_bn().unwrap(), rsa.d()); + assert_eq!( + params.locate("rsa-factor1\0").unwrap().get_bn().unwrap(), + rsa.p().unwrap() + ); + assert_eq!( + params.locate("rsa-factor2\0").unwrap().get_bn().unwrap(), + rsa.q().unwrap() + ); + assert_eq!( + params.locate("rsa-exponent1\0").unwrap().get_bn().unwrap(), + rsa.dmp1().unwrap() + ); + assert_eq!( + params.locate("rsa-exponent2\0").unwrap().get_bn().unwrap(), + rsa.dmq1().unwrap() + ); + assert_eq!( + params + .locate("rsa-coefficient1\0") + .unwrap() + .get_bn() + .unwrap(), + rsa.iqmp().unwrap() + ); + } + + #[test] + fn test_build_pkey_ecdsa() { + use crate::bn::BigNumContext; + use crate::ec::PointConversionForm; + + /* First, generate the key with old API */ + let group = crate::ec::EcGroup::from_curve_name(Nid::SECP256K1).unwrap(); + let ec_key = EcKey::generate(&group).unwrap(); + let pkey1 = PKey::from_ec_key(ec_key.clone()).unwrap(); + + /* is there a better way? */ + let mut ctx = BigNumContext::new().unwrap(); + let pubkey = ec_key + .public_key() + .to_bytes(&group, PointConversionForm::UNCOMPRESSED, &mut ctx) + .unwrap(); + + /* Now, construct the OSSL_PARAM manually from the old key */ + let mut bld = OsslParamBuilder::new().unwrap(); + // TODO do we want a better API with the parameter names? + bld.add_utf8_string("group\0", "secp256k1").unwrap(); + bld.add_octet_string("pub\0", pubkey.as_slice()).unwrap(); + bld.add_bn("priv\0", ec_key.private_key()).unwrap(); + let params = bld.to_params().unwrap(); + + /* Build new key */ + let mut ctx = PkeyCtx::new_from_name(None, "EC", None).unwrap(); + ctx.fromdata_init().unwrap(); + let pkey2 = PKey::::fromdata(ctx, params).unwrap(); + + /* Verify it works the same way as the old one */ + assert!(pkey1.public_eq(&pkey2)); + assert!(Error::get().is_none()); + + // FIXME use of ffi in test is not intended -- we will need some constants + let params = pkey2.todata(ffi::EVP_PKEY_KEYPAIR).unwrap(); + assert_eq!( + params.locate("priv\0").unwrap().get_bn().unwrap(), + ec_key.private_key() + ); + assert_eq!( + params.locate("group\0").unwrap().get_utf8_string().unwrap(), + "secp256k1" + ); + assert_eq!( + params.locate("pub\0").unwrap().get_octet_string().unwrap(), + pubkey.as_slice() + ); + } + + #[test] + fn test_generate_rsa() { + use crate::pkey::Id; + + let mut ctx = PkeyCtx::new_from_name(None, "RSA", None).unwrap(); + ctx.keygen_init().unwrap(); + + let mut bld = OsslParamBuilder::new().unwrap(); + bld.add_uint("bits\0", 3096).unwrap(); + let params = bld.to_params().unwrap(); + ctx.set_params(params).unwrap(); + let key = ctx.generate().unwrap(); + + assert_eq!(key.id(), Id::RSA); + assert!(key.dsa().is_err()); + + let rsa = key.rsa().unwrap(); + // FIXME use of ffi in test is not intended -- we will need some constants + let params = key.todata(ffi::EVP_PKEY_KEYPAIR).unwrap(); + assert_eq!(rsa.e(), params.locate("e\0").unwrap().get_bn().unwrap()); + assert_eq!(rsa.n(), params.locate("n\0").unwrap().get_bn().unwrap()); + assert_eq!(rsa.d(), params.locate("d\0").unwrap().get_bn().unwrap()); + assert_eq!( + rsa.p().unwrap(), + params.locate("rsa-factor1\0").unwrap().get_bn().unwrap() + ); + assert_eq!( + rsa.q().unwrap(), + params.locate("rsa-factor2\0").unwrap().get_bn().unwrap() + ); + } +} diff --git a/openssl/src/pkey.rs b/openssl/src/pkey.rs index f2cedd271c..59439d3324 100644 --- a/openssl/src/pkey.rs +++ b/openssl/src/pkey.rs @@ -41,12 +41,16 @@ //! ``` #![allow(clippy::missing_safety_doc)] use crate::bio::{MemBio, MemBioSlice}; +#[cfg(ossl300)] +use crate::bn::BigNumRef; #[cfg(ossl110)] use crate::cipher::CipherRef; use crate::dh::Dh; use crate::dsa::Dsa; use crate::ec::EcKey; use crate::error::ErrorStack; +#[cfg(ossl300)] +use crate::ossl_param::{OsslParam, OsslParamRef}; #[cfg(any(ossl110, boringssl, libressl370))] use crate::pkey_ctx::PkeyCtx; use crate::rsa::Rsa; @@ -55,6 +59,8 @@ use crate::util::{invoke_passwd_cb, CallbackState}; use crate::{cvt, cvt_p}; use cfg_if::cfg_if; use foreign_types::{ForeignType, ForeignTypeRef}; +#[cfg(ossl300)] +use libc::c_char; use libc::{c_int, c_long}; use openssl_macros::corresponds; use std::convert::{TryFrom, TryInto}; @@ -207,6 +213,36 @@ impl PKeyRef { pub fn size(&self) -> usize { unsafe { ffi::EVP_PKEY_size(self.as_ptr()) as usize } } + + /// Sets a `BigNum` parameter to the given `PKey` + #[corresponds(EVP_PKEY_set_bn_param)] + #[cfg(ossl300)] + pub fn set_bn_param(&mut self, key: &str, bn: &BigNumRef) -> Result<(), ErrorStack> { + unsafe { + cvt(ffi::EVP_PKEY_set_bn_param( + self.as_ptr(), + key.as_ptr() as *const c_char, + bn.as_ptr(), + )) + .map(|_| ())? + } + Ok(()) + } + + /// Creates a new `OsslParam` from `PKey`. + #[corresponds(EVP_PKEY_todata)] + #[cfg(ossl300)] + pub fn todata(&self, selection: c_int) -> Result<&OsslParamRef, ErrorStack> { + unsafe { + let mut params: *mut ffi::OSSL_PARAM = ptr::null_mut(); + cvt(ffi::EVP_PKEY_todata( + self.as_ptr(), + selection as c_int, + &mut params, + ))?; + Ok(OsslParamRef::from_ptr(params)) + } + } } impl PKeyRef @@ -759,6 +795,23 @@ impl PKey { .map(|p| PKey::from_ptr(p)) } } + + /// Creates a new `PKey` from `OsslParam`. + #[corresponds(EVP_PKEY_fromdata)] + #[cfg(ossl300)] + pub fn fromdata(ctx: PkeyCtx<()>, params: OsslParam) -> Result, ErrorStack> { + 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(), + ffi::EVP_PKEY_PRIVATE_KEY, + params.as_ptr(), + ))?; + Ok(pkey) + } + } } impl PKey { @@ -810,6 +863,23 @@ impl PKey { .map(|p| PKey::from_ptr(p)) } } + + /// Creates a new `PKey` from `OsslParam`. + #[corresponds(EVP_PKEY_fromdata)] + #[cfg(ossl300)] + pub fn fromdata(ctx: PkeyCtx<()>, params: OsslParam) -> Result, ErrorStack> { + 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(), + ffi::EVP_PKEY_PUBLIC_KEY, + params.as_ptr(), + ))?; + Ok(pkey) + } + } } cfg_if! { diff --git a/openssl/src/pkey_ctx.rs b/openssl/src/pkey_ctx.rs index f30f06973a..f8437d623f 100644 --- a/openssl/src/pkey_ctx.rs +++ b/openssl/src/pkey_ctx.rs @@ -67,7 +67,11 @@ 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; +#[cfg(ossl300)] +use crate::ossl_param::OsslParam; use crate::pkey::{HasPrivate, HasPublic, Id, PKey, PKeyRef, Private}; use crate::rsa::Padding; use crate::sign::RsaPssSaltlen; @@ -81,6 +85,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. @@ -148,7 +154,7 @@ impl PkeyCtx { impl PkeyCtx<()> { /// Creates a new pkey context for the specified algorithm ID. - #[corresponds(EVP_PKEY_new_id)] + #[corresponds(EVP_PKEY_CTX_new_id)] #[inline] pub fn new_id(id: Id) -> Result { unsafe { @@ -156,6 +162,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 +782,32 @@ impl PkeyCtxRef { Ok(()) } + /// Sets params for the pkey context. + /// + /// Requires OpenSSL 3.0.0 or newer. + #[corresponds(EVP_PKEY_CTX_set_params)] + #[cfg(ossl300)] + pub fn set_params(&mut self, params: OsslParam) -> Result<(), ErrorStack> { + unsafe { + cvt(ffi::EVP_PKEY_CTX_set_params(self.as_ptr(), params.as_ptr()))?; + } + 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 +832,14 @@ impl PkeyCtxRef { } Ok(NonceType(nonce_type)) } + + /// Initializes a conversion from `OsllParam` 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 +1167,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/systest/build.rs b/systest/build.rs index fc970f410a..43b5c23341 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");