diff --git a/openssl/src/kdf.rs b/openssl/src/kdf.rs index 885ca1a66d..d3344d5415 100644 --- a/openssl/src/kdf.rs +++ b/openssl/src/kdf.rs @@ -34,19 +34,19 @@ cfg_if::cfg_if! { use crate::error::ErrorStack; use crate::ossl_param::OsslParamBuilder; - // Safety: these all have null terminators. - // We cen remove these CStr::from_bytes_with_nul_unchecked calls - // when we upgrade to Rust 1.77+ with literal c"" syntax. + cstr_const!(OSSL_KDF_PARAM_PASSWORD, b"pass\0"); + cstr_const!(OSSL_KDF_PARAM_SALT, b"salt\0"); + cstr_const!(OSSL_KDF_PARAM_SECRET, b"secret\0"); + cstr_const!(OSSL_KDF_PARAM_ITER, b"iter\0"); + cstr_const!(OSSL_KDF_PARAM_SIZE, b"size\0"); + cstr_const!(OSSL_KDF_PARAM_THREADS, b"threads\0"); + cstr_const!(OSSL_KDF_PARAM_ARGON2_AD, b"ad\0"); + cstr_const!(OSSL_KDF_PARAM_ARGON2_LANES, b"lanes\0"); + cstr_const!(OSSL_KDF_PARAM_ARGON2_MEMCOST, b"memcost\0"); - const OSSL_KDF_PARAM_PASSWORD: &CStr = unsafe { CStr::from_bytes_with_nul_unchecked(b"pass\0") }; - const OSSL_KDF_PARAM_SALT: &CStr = unsafe { CStr::from_bytes_with_nul_unchecked(b"salt\0") }; - const OSSL_KDF_PARAM_SECRET: &CStr = unsafe { CStr::from_bytes_with_nul_unchecked(b"secret\0") }; - const OSSL_KDF_PARAM_ITER: &CStr = unsafe { CStr::from_bytes_with_nul_unchecked(b"iter\0") }; - const OSSL_KDF_PARAM_SIZE: &CStr = unsafe { CStr::from_bytes_with_nul_unchecked(b"size\0") }; - const OSSL_KDF_PARAM_THREADS: &CStr = unsafe { CStr::from_bytes_with_nul_unchecked(b"threads\0") }; - const OSSL_KDF_PARAM_ARGON2_AD: &CStr = unsafe { CStr::from_bytes_with_nul_unchecked(b"ad\0") }; - const OSSL_KDF_PARAM_ARGON2_LANES: &CStr = unsafe { CStr::from_bytes_with_nul_unchecked(b"lanes\0") }; - const OSSL_KDF_PARAM_ARGON2_MEMCOST: &CStr = unsafe { CStr::from_bytes_with_nul_unchecked(b"memcost\0") }; + cstr_const!(KDF_ARGON2D, b"ARGON2D\0"); + cstr_const!(KDF_ARGON2I, b"ARGON2I\0"); + cstr_const!(KDF_ARGON2ID, b"ARGON2ID\0"); #[allow(clippy::too_many_arguments)] pub fn argon2d( @@ -60,7 +60,7 @@ cfg_if::cfg_if! { memcost: u32, out: &mut [u8], ) -> Result<(), ErrorStack> { - return argon2_helper(CStr::from_bytes_with_nul(b"ARGON2D\0").unwrap(), ctx, pass, salt, ad, secret, iter, lanes, memcost, out); + argon2_helper(KDF_ARGON2D, ctx, pass, salt, ad, secret, iter, lanes, memcost, out) } #[allow(clippy::too_many_arguments)] @@ -75,7 +75,7 @@ cfg_if::cfg_if! { memcost: u32, out: &mut [u8], ) -> Result<(), ErrorStack> { - return argon2_helper(CStr::from_bytes_with_nul(b"ARGON2I\0").unwrap(), ctx, pass, salt, ad, secret, iter, lanes, memcost, out); + argon2_helper(KDF_ARGON2I, ctx, pass, salt, ad, secret, iter, lanes, memcost, out) } #[allow(clippy::too_many_arguments)] @@ -90,7 +90,7 @@ cfg_if::cfg_if! { memcost: u32, out: &mut [u8], ) -> Result<(), ErrorStack> { - return argon2_helper(CStr::from_bytes_with_nul(b"ARGON2ID\0").unwrap(), ctx, pass, salt, ad, secret, iter, lanes, memcost, out); + argon2_helper(KDF_ARGON2ID, ctx, pass, salt, ad, secret, iter, lanes, memcost, out) } /// Derives a key using the argon2* algorithms. diff --git a/openssl/src/lib.rs b/openssl/src/lib.rs index 5ae46337eb..e7af9d1a3e 100644 --- a/openssl/src/lib.rs +++ b/openssl/src/lib.rs @@ -178,6 +178,8 @@ pub mod nid; #[cfg(not(osslconf = "OPENSSL_NO_OCSP"))] pub mod ocsp; #[cfg(ossl300)] +mod ossl_encdec; +#[cfg(ossl300)] mod ossl_param; pub mod pkcs12; pub mod pkcs5; diff --git a/openssl/src/macros.rs b/openssl/src/macros.rs index 8b561822d3..f67bf88484 100644 --- a/openssl/src/macros.rs +++ b/openssl/src/macros.rs @@ -268,3 +268,14 @@ macro_rules! generic_foreign_type_and_impl_send_sync { unsafe impl Sync for $borrowed{} }; } + +#[cfg_attr(not(ossl300), allow(unused_macros))] +macro_rules! cstr_const { + // Safety: these all have null terminators. + // We cen remove these CStr::from_bytes_with_nul_unchecked calls + // when we upgrade to Rust 1.77+ with literal c"" syntax. + ($vis:vis $name:ident, $key:literal) => { + #[allow(dead_code)] + $vis const $name: &std::ffi::CStr = unsafe { std::ffi::CStr::from_bytes_with_nul_unchecked($key) }; + } +} diff --git a/openssl/src/ossl_encdec.rs b/openssl/src/ossl_encdec.rs new file mode 100644 index 0000000000..27522cec7a --- /dev/null +++ b/openssl/src/ossl_encdec.rs @@ -0,0 +1,938 @@ +use crate::bio::{MemBio, MemBioSlice}; +use crate::error::ErrorStack; +use crate::pkey::{Id, PKey, PKeyRef}; +use crate::pkey_ctx::{Selection, SelectionT}; +use crate::symm::Cipher; +use crate::util::{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)] +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"), + } + } +} + +cstr_const!(KEY_FORMAT_TEXT, b"TEXT\0"); +cstr_const!(KEY_FORMAT_DER, b"DER\0"); +cstr_const!(KEY_FORMAT_PEM, b"PEM\0"); +cstr_const!(KEY_FORMAT_MSBLOB, b"MSBLOB\0"); +cstr_const!(KEY_FORMAT_PVK, b"PVK\0"); + +impl From for &CStr { + fn from(o: KeyFormat) -> Self { + match o { + KeyFormat::Text => KEY_FORMAT_TEXT, + KeyFormat::Der => KEY_FORMAT_DER, + KeyFormat::Pem => KEY_FORMAT_PEM, + KeyFormat::MsBlob => KEY_FORMAT_MSBLOB, + KeyFormat::Pvk => KEY_FORMAT_PVK, + } + } +} + +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), + } + } +} + +cstr_const!(STRUCTURE_SUBJECT_PUBLIC_KEY_INFO, b"SubjectPublicKeyInfo\0"); +cstr_const!(STRUCTURE_PKCS1, b"pkcs1\0"); +cstr_const!(STRUCTURE_PKCS8, b"pkcs8\0"); +cstr_const!(STRUCTURE_TYPE_SPECIFIC, b"type-specific\0"); + +impl<'a> From> for &'a CStr { + fn from(o: Structure<'a>) -> Self { + match o { + Structure::SubjectPublicKeyInfo => STRUCTURE_SUBJECT_PUBLIC_KEY_INFO, + Structure::PKCS1 => STRUCTURE_PKCS1, + Structure::PKCS8 => STRUCTURE_PKCS8, + Structure::TypeSpecific => STRUCTURE_TYPE_SPECIFIC, + 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 OsslDecoderCtx; + /// A reference to an `OsslDecoderCtx`. + pub struct OsslDecoderCtxRef; +} + +impl OsslDecoderCtx { + #[corresponds(OSSL_DECODER_CTX_new_for_pkey)] + #[inline] + #[allow(dead_code)] + 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_for_pkey( + pkey, + input_ptr, + structure_ptr, + key_type_ptr, + selection.into(), + ptr::null_mut(), + ptr::null(), + ))?; + Ok(Self::from_ptr(ptr)) + } + } +} + +impl OsslDecoderCtxRef { + /// 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 = OsslDecoderCtx::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; + + /// A context object which can perform encode operations. + pub struct OsslEncoderCtx; + /// A reference to an [`OsslEncoderCtx`]. + pub struct OsslEncoderCtxRef; +} + +impl OsslEncoderCtx { + /// 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(Self::from_ptr(ptr)) + } + } +} + +impl OsslEncoderCtxRef { + // 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 = + OsslEncoderCtx::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 = KEY_FORMAT_TEXT.into(); + let der: KeyFormat = KEY_FORMAT_DER.into(); + let pem: KeyFormat = KEY_FORMAT_PEM.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 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::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/ossl_param.rs b/openssl/src/ossl_param.rs index 5dfaca7687..abb2097876 100644 --- a/openssl/src/ossl_param.rs +++ b/openssl/src/ossl_param.rs @@ -13,11 +13,12 @@ //! 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; -use libc::{c_uint, c_void}; +use foreign_types::{ForeignType, ForeignTypeRef}; +use libc::{c_char, c_void}; use openssl_macros::corresponds; use std::ffi::CStr; use std::marker::PhantomData; @@ -39,6 +40,17 @@ foreign_type_and_impl_send_sync! { } impl OsslParamArray { + /// Locate a parameter by the given key (returning a const reference). + #[corresponds(OSSL_PARAM_locate_const)] + fn locate_const(&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) + } + } + /// Locates the individual `OSSL_PARAM` element representing an /// octet string identified by the key in the `OsslParamArray` /// array and returns a reference to it. @@ -47,8 +59,8 @@ impl OsslParamArray { #[corresponds(OSSL_PARAM_get_octet_string)] #[allow(dead_code)] // TODO: remove when when used by ML-DSA / ML-KEM pub(crate) fn locate_octet_string<'a>(&'a self, key: &CStr) -> Result<&'a [u8], ErrorStack> { + let param = self.locate_const(key).ok_or_else(ErrorStack::get)?; unsafe { - let param = cvt_p(ffi::OSSL_PARAM_locate(self.as_ptr(), key.as_ptr()))?; let mut val: *const c_void = ptr::null_mut(); let mut val_len: usize = 0; cvt(ffi::OSSL_PARAM_get_octet_string_ptr( @@ -59,6 +71,38 @@ impl OsslParamArray { Ok(util::from_raw_parts(val as *const u8, val_len)) } } + + /// Locates the individual `OSSL_PARAM` element representing a BigNum identified by the key in + /// the `OsslParamArray` array and returns a reference to it. + /// + /// Combines OSSL_PARAM_locate and OSSL_PARAM_get_BN. + #[corresponds(OSSL_PARAM_get_BN)] + #[allow(dead_code)] + fn locate_bn(&self, key: &CStr) -> Result { + let param = self.locate_const(key).ok_or_else(ErrorStack::get)?; + let mut bn_ptr = ptr::null_mut(); + cvt(unsafe { ffi::OSSL_PARAM_get_BN(param, &mut bn_ptr) })?; + Ok(unsafe { BigNum::from_ptr(bn_ptr) }) + } +} + +impl OsslParamArrayRef { + /// Merges two `ParamsRef` objects into a new `Params` object. + #[corresponds(OSSL_PARAM_merge)] + #[allow(dead_code)] + pub fn merge(&self, other: &OsslParamArrayRef) -> Result { + // 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 { OsslParamArray::from_ptr(p) }) + } } foreign_type_and_impl_send_sync! { @@ -106,7 +150,7 @@ impl<'a> OsslParamBuilder<'a> { } } - /// Adds a octet string to `OsslParamBuilder`. + /// Adds an octet string to `OsslParamBuilder`. #[corresponds(OSSL_PARAM_BLD_push_octet_string)] #[cfg_attr(any(not(ossl320), osslconf = "OPENSSL_NO_ARGON2"), allow(dead_code))] pub(crate) fn add_octet_string( @@ -125,7 +169,7 @@ impl<'a> OsslParamBuilder<'a> { } } - /// Adds a unsigned int to `OsslParamBuilder`. + /// Adds an unsigned int to `OsslParamBuilder`. #[corresponds(OSSL_PARAM_BLD_push_uint)] #[cfg_attr(any(not(ossl320), osslconf = "OPENSSL_NO_ARGON2"), allow(dead_code))] pub(crate) fn add_uint(&mut self, key: &'a CStr, val: u32) -> Result<(), ErrorStack> { @@ -133,12 +177,39 @@ impl<'a> OsslParamBuilder<'a> { cvt(ffi::OSSL_PARAM_BLD_push_uint( self.as_ptr(), key.as_ptr(), - val as c_uint, + val, )) .map(|_| ()) } } + /// Adds a `BigNum` to `OsslParamBuilder`. + #[corresponds(OSSL_PARAM_BLD_push_BN)] + #[allow(dead_code)] // TODO: remove when when used by EVP_KEY.from_data + pub(crate) fn add_bn(&mut self, key: &'a CStr, bn: &'a BigNumRef) -> Result<(), ErrorStack> { + cvt(unsafe { ffi::OSSL_PARAM_BLD_push_BN(self.as_ptr(), key.as_ptr(), bn.as_ptr()) }) + .map(|_| ()) + } + + /// Adds a utf8 string to `OsslParamBuilder`. + #[corresponds(OSSL_PARAM_BLD_push_utf8_string)] + #[allow(dead_code)] // TODO: remove when when used by EVP_KEY.from_data + pub(crate) fn add_utf8_string( + &mut self, + key: &'a CStr, + value: &'a str, + ) -> Result<(), ErrorStack> { + cvt(unsafe { + ffi::OSSL_PARAM_BLD_push_utf8_string( + self.as_ptr(), + key.as_ptr(), + value.as_ptr().cast::(), + value.len(), + ) + }) + .map(|_| ()) + } + /// Returns a raw pointer to the underlying `OSSL_PARAM_BLD` structure. pub(crate) unsafe fn as_ptr(&mut self) -> *mut ffi::OSSL_PARAM_BLD { self.builder.as_ptr() @@ -148,11 +219,17 @@ impl<'a> OsslParamBuilder<'a> { #[cfg(test)] mod tests { use super::*; + use crate::bn::BigNum; + use crate::pkey::{ + OSSL_PKEY_PARAM_GROUP_NAME, OSSL_PKEY_PARAM_PUB_KEY, OSSL_PKEY_PARAM_RSA_D, + OSSL_PKEY_PARAM_RSA_E, OSSL_PKEY_PARAM_RSA_N, OSSL_SIGNATURE_PARAM_NONCE_TYPE, + }; + #[test] fn test_builder_locate_octet_string() { let mut builder = OsslParamBuilder::new().unwrap(); builder - .add_octet_string(CStr::from_bytes_with_nul(b"key1\0").unwrap(), b"value1") + .add_octet_string(OSSL_PKEY_PARAM_PUB_KEY, b"value1") .unwrap(); let params = builder.to_param().unwrap(); @@ -160,10 +237,129 @@ mod tests { .locate_octet_string(CStr::from_bytes_with_nul(b"invalid\0").unwrap()) .is_err()); assert_eq!( - params - .locate_octet_string(CStr::from_bytes_with_nul(b"key1\0").unwrap()) - .unwrap(), + params.locate_octet_string(OSSL_PKEY_PARAM_PUB_KEY).unwrap(), b"value1" ); } + + fn assert_param(params: &OsslParamArray, key: &CStr, is_null: bool) { + match params.locate_const(key) { + Some(_) => assert!(!is_null, "Unexpectedly found param: {key:?}"), + None => assert!(is_null, "Failed to find param: {key:?}"), + } + } + + fn assert_bn_equal(params: &OsslParamArray, key: &CStr, expected: &BigNum) { + let bn = params.locate_bn(key).unwrap(); + assert_eq!(bn, *expected); + } + + #[test] + fn test_param_builder_uint() { + let mut builder = OsslParamBuilder::new().unwrap(); + builder + .add_uint(OSSL_SIGNATURE_PARAM_NONCE_TYPE, 42) + .unwrap(); + let params = builder.to_param().unwrap(); + + assert_param(¶ms, OSSL_SIGNATURE_PARAM_NONCE_TYPE, false); + assert_param(¶ms, OSSL_PKEY_PARAM_GROUP_NAME, 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 = OsslParamBuilder::new().unwrap(); + builder.add_bn(OSSL_PKEY_PARAM_RSA_N, &n).unwrap(); + builder.add_bn(OSSL_PKEY_PARAM_RSA_E, &e).unwrap(); + builder.add_bn(OSSL_PKEY_PARAM_RSA_D, &d).unwrap(); + let params = builder.to_param().unwrap(); + + for (param, expected) in [ + (OSSL_PKEY_PARAM_RSA_N, n), + (OSSL_PKEY_PARAM_RSA_E, e), + (OSSL_PKEY_PARAM_RSA_D, d), + ] { + assert_bn_equal(¶ms, param, &expected); + } + + assert_param(¶ms, OSSL_PKEY_PARAM_GROUP_NAME, true); + } + + #[test] + fn test_param_builder_string() { + let mut builder = OsslParamBuilder::new().unwrap(); + builder + .add_utf8_string(OSSL_PKEY_PARAM_GROUP_NAME, "primve256v1") + .unwrap(); + let params = builder.to_param().unwrap(); + + assert_param(¶ms, OSSL_PKEY_PARAM_GROUP_NAME, false); + assert_param(¶ms, OSSL_PKEY_PARAM_RSA_N, true); + } + + #[test] + fn test_param_builder_octet_string() { + let mut builder = OsslParamBuilder::new().unwrap(); + builder + .add_octet_string(OSSL_PKEY_PARAM_PUB_KEY, b"foobar") + .unwrap(); + let params = builder.to_param().unwrap(); + + assert_param(¶ms, OSSL_PKEY_PARAM_PUB_KEY, false); + assert_param(¶ms, OSSL_PKEY_PARAM_GROUP_NAME, true); + } + + #[test] + fn test_merge() { + let (n, e, d) = (0xbc747fc5, 0x10001, 0x7b133399); + let mut merged_params: OsslParamArray; + + // Create a param array with just n in a scoped block so that bn_n, and the builder are dropped + { + let bn_n = BigNum::from_u32(n).unwrap(); + let mut builder = OsslParamBuilder::new().unwrap(); + builder.add_bn(OSSL_PKEY_PARAM_RSA_N, &bn_n).unwrap(); + merged_params = builder.to_param().unwrap(); + } + + // We should still be able to pull back n and get the correct value, but not e or d (yet) + let bn_n = BigNum::from_u32(n).unwrap(); + assert_bn_equal(&merged_params, OSSL_PKEY_PARAM_RSA_N, &bn_n); + assert_param(&merged_params, OSSL_PKEY_PARAM_RSA_E, true); + assert_param(&merged_params, OSSL_PKEY_PARAM_RSA_D, true); + + // Create a new param array with just e and merge it in + { + let bn_e = BigNum::from_u32(e).unwrap(); + let mut builder = OsslParamBuilder::new().unwrap(); + builder.add_bn(OSSL_PKEY_PARAM_RSA_E, &bn_e).unwrap(); + let params = builder.to_param().unwrap(); + merged_params = merged_params.merge(¶ms).unwrap(); + } + + // We should still be able to pull back n & e and get the correct value, but not d (yet) + let bn_e = BigNum::from_u32(e).unwrap(); + assert_bn_equal(&merged_params, OSSL_PKEY_PARAM_RSA_N, &bn_n); + assert_bn_equal(&merged_params, OSSL_PKEY_PARAM_RSA_E, &bn_e); + assert_param(&merged_params, OSSL_PKEY_PARAM_RSA_D, true); + + // Again, create a new param array with just d and merge it in + { + let bn_d = BigNum::from_u32(d).unwrap(); + let mut builder = OsslParamBuilder::new().unwrap(); + builder.add_bn(OSSL_PKEY_PARAM_RSA_D, &bn_d).unwrap(); + let params = builder.to_param().unwrap(); + merged_params = merged_params.merge(¶ms).unwrap(); + } + + // We should be able to pull all of n, e & d out and get the correct values + let bn_d = BigNum::from_u32(d).unwrap(); + assert_bn_equal(&merged_params, OSSL_PKEY_PARAM_RSA_N, &bn_n); + assert_bn_equal(&merged_params, OSSL_PKEY_PARAM_RSA_E, &bn_e); + assert_bn_equal(&merged_params, OSSL_PKEY_PARAM_RSA_D, &bn_d); + } } diff --git a/openssl/src/pkey.rs b/openssl/src/pkey.rs index 8d69e1cdcf..3a19346a5d 100644 --- a/openssl/src/pkey.rs +++ b/openssl/src/pkey.rs @@ -47,8 +47,12 @@ use crate::dh::Dh; use crate::dsa::Dsa; use crate::ec::EcKey; use crate::error::ErrorStack; +#[cfg(ossl300)] +use crate::ossl_param::OsslParamArray; #[cfg(any(ossl110, boringssl, libressl370, awslc))] use crate::pkey_ctx::PkeyCtx; +#[cfg(ossl300)] +use crate::pkey_ctx::Selection; use crate::rsa::Rsa; use crate::symm::Cipher; use crate::util::{invoke_passwd_cb, CallbackState}; @@ -58,7 +62,7 @@ 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; @@ -119,6 +123,103 @@ 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(()), + } + } +} + +cstr_const!(ID_RSA, b"RSA\0"); +#[cfg(any(ossl111, libressl310, boringssl, awslc))] +cstr_const!(ID_RSA_PSS, b"RSA-PSS\0"); +#[cfg(not(boringssl))] +cstr_const!(ID_HMAC, b"HMAC\0"); +#[cfg(not(any(boringssl, awslc)))] +cstr_const!(ID_CMAC, b"CMAC\0"); +cstr_const!(ID_DSA, b"DSA\0"); +cstr_const!(ID_DH, b"DH\0"); +#[cfg(ossl110)] +cstr_const!(ID_DHX, b"DHX\0"); +cstr_const!(ID_EC, b"EC\0"); +#[cfg(ossl111)] +cstr_const!(ID_SM2, b"SM2\0"); +#[cfg(any(ossl110, boringssl, libressl360, awslc))] +cstr_const!(ID_HKDF, b"HKDF\0"); +#[cfg(any(ossl111, boringssl, libressl370, awslc))] +cstr_const!(ID_ED25519, b"Ed25519\0"); +#[cfg(ossl111)] +cstr_const!(ID_ED448, b"Ed448\0"); +#[cfg(any(ossl111, boringssl, libressl370, awslc))] +cstr_const!(ID_X25519, b"X25519\0"); +#[cfg(ossl111)] +cstr_const!(ID_X448, b"X448\0"); +#[cfg(ossl111)] +cstr_const!(ID_POLY1305, b"POLY1305\0"); + +impl TryFrom for &'static CStr { + type Error = (); + fn try_from(id: Id) -> Result { + match id { + Id::RSA => Ok(ID_RSA), + #[cfg(any(ossl111, libressl310, boringssl, awslc))] + Id::RSA_PSS => Ok(ID_RSA_PSS), + #[cfg(not(boringssl))] + Id::HMAC => Ok(ID_HMAC), + #[cfg(not(any(boringssl, awslc)))] + Id::CMAC => Ok(ID_CMAC), + Id::DSA => Ok(ID_DSA), + Id::DH => Ok(ID_DH), + #[cfg(ossl110)] + Id::DHX => Ok(ID_DHX), + Id::EC => Ok(ID_EC), + #[cfg(ossl111)] + Id::SM2 => Ok(ID_SM2), + #[cfg(any(ossl110, boringssl, libressl360, awslc))] + Id::HKDF => Ok(ID_HKDF), + #[cfg(any(ossl111, boringssl, libressl370, awslc))] + Id::ED25519 => Ok(ID_ED25519), + #[cfg(ossl111)] + Id::ED448 => Ok(ID_ED448), + #[cfg(any(ossl111, boringssl, libressl370, awslc))] + Id::X25519 => Ok(ID_X25519), + #[cfg(ossl111)] + Id::X448 => Ok(ID_X448), + #[cfg(ossl111)] + Id::POLY1305 => Ok(ID_POLY1305), + _ => Err(()), + } + } +} + /// A trait indicating that a key has parameters. pub unsafe trait HasParams {} @@ -207,6 +308,18 @@ impl PKeyRef { pub fn size(&self) -> usize { unsafe { ffi::EVP_PKEY_size(self.as_ptr()) as usize } } + + /// Converts the `PKey` to an `OsslParamArray`. + /// + /// Use `selection` to control what parameters are included. + #[corresponds(EVP_PKEY_todata)] + #[cfg(ossl300)] + #[allow(dead_code)] // TODO: remove when used by non-deprecated key wrappers + pub(crate) fn to_data(&self, selection: Selection) -> Result { + let mut params = ptr::null_mut(); + cvt(unsafe { ffi::EVP_PKEY_todata(self.as_ptr(), selection.into(), &mut params) })?; + Ok(unsafe { OsslParamArray::from_ptr(params) }) + } } impl PKeyRef @@ -382,35 +495,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 } @@ -914,6 +999,25 @@ impl TryFrom> for Dh { } } +cfg_if! { + if #[cfg(ossl300)] { + cstr_const!(pub(crate) OSSL_PKEY_PARAM_GROUP_NAME, b"group\0"); + + cstr_const!(pub(crate) OSSL_PKEY_PARAM_PUB_KEY, b"pub\0"); + cstr_const!(pub(crate) OSSL_PKEY_PARAM_PRIV_KEY, b"priv\0"); + + cstr_const!(pub(crate) OSSL_PKEY_PARAM_FFC_P, b"p\0"); + cstr_const!(pub(crate) OSSL_PKEY_PARAM_FFC_G, b"g\0"); + cstr_const!(pub(crate) OSSL_PKEY_PARAM_FFC_Q, b"q\0"); + + cstr_const!(pub(crate) OSSL_PKEY_PARAM_RSA_N, b"n\0"); + cstr_const!(pub(crate) OSSL_PKEY_PARAM_RSA_E, b"e\0"); + cstr_const!(pub(crate) OSSL_PKEY_PARAM_RSA_D, b"d\0"); + + cstr_const!(pub(crate) OSSL_SIGNATURE_PARAM_NONCE_TYPE, b"nonce-type\0"); + } +} + #[cfg(test)] mod tests { use std::convert::TryInto; @@ -922,8 +1026,12 @@ mod tests { use crate::dh::Dh; use crate::dsa::Dsa; use crate::ec::EcKey; + #[cfg(ossl300)] + use crate::encrypt::{Decrypter, Encrypter}; use crate::error::Error; use crate::nid::Nid; + #[cfg(ossl300)] + use crate::pkey_ctx::pkey_from_params; use crate::rsa::Rsa; use crate::symm::Cipher; @@ -1219,4 +1327,27 @@ mod tests { assert!(!pkey1.public_eq(&pkey2)); assert!(Error::get().is_none()); } + + #[cfg(ossl300)] + #[test] + fn test_todata() { + let data: &[u8] = b"hello, world"; + let pkey1 = PKey::from_rsa(Rsa::generate(2048).unwrap()).unwrap(); + + // Encrypt some data using the generated pkey + let encrypter = Encrypter::new(&pkey1).unwrap(); + let mut encrypted = vec![0u8; encrypter.encrypt_len(data).unwrap()]; + encrypter.encrypt(data, &mut encrypted).unwrap(); + + // Convert the pkey to OSSL_PARAMs and back into a PKey + let params = pkey1.to_data(Selection::Keypair).unwrap(); + let pkey2: PKey = pkey_from_params(Id::RSA, ¶ms).unwrap(); + + // Decrypt the data using the new pkey + let decrypter = Decrypter::new(&pkey2).unwrap(); + let mut decrypted = vec![0u8; decrypter.decrypt_len(&encrypted).unwrap()]; + let decrypted_len = decrypter.decrypt(&encrypted, &mut decrypted).unwrap(); + decrypted.truncate(decrypted_len); + assert_eq!(data, &decrypted); + } } diff --git a/openssl/src/pkey_ctx.rs b/openssl/src/pkey_ctx.rs index 1b58108aed..0dea2bb956 100644 --- a/openssl/src/pkey_ctx.rs +++ b/openssl/src/pkey_ctx.rs @@ -70,6 +70,14 @@ use crate::cipher::CipherRef; use crate::error::ErrorStack; use crate::md::MdRef; use crate::nid::Nid; +#[cfg(ossl300)] +use crate::ossl_param::OsslParamArrayRef; +#[cfg(ossl320)] +use crate::ossl_param::OsslParamBuilder; +#[cfg(ossl300)] +use crate::pkey::Public; +#[cfg(ossl320)] +use crate::pkey::OSSL_SIGNATURE_PARAM_NONCE_TYPE; use crate::pkey::{HasPrivate, HasPublic, Id, PKey, PKeyRef, Params, Private}; use crate::rsa::Padding; use crate::sign::RsaPssSaltlen; @@ -82,8 +90,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. @@ -127,6 +133,47 @@ impl NonceType { pub const DETERMINISTIC_K: Self = NonceType(1); } +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/todata operation. + pub(crate) trait SelectionT { + const SELECTION: Selection; + } + + impl SelectionT for Params { + const SELECTION: Selection = Selection::KeyParameters; + } + + impl SelectionT for Public { + const SELECTION: Selection = Selection::PublicKey; + } + + impl SelectionT for Private { + const SELECTION: Selection = Selection::Keypair; + } + } +} + generic_foreign_type_and_impl_send_sync! { type CType = ffi::EVP_PKEY_CTX; fn drop = ffi::EVP_PKEY_CTX_free; @@ -434,6 +481,35 @@ impl PkeyCtxRef { Ok(()) } + /// Prepares the context for creating a key from user data. + #[corresponds(EVP_PKEY_fromdata_init)] + #[inline] + #[cfg(ossl300)] + 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)] + pub(crate) fn fromdata( + &mut self, + params: &OsslParamArrayRef, + selection: Selection, + ) -> Result, ErrorStack> { + let mut key_ptr = ptr::null_mut(); + cvt(unsafe { + ffi::EVP_PKEY_fromdata( + self.as_ptr(), + &mut key_ptr, + selection.into(), + 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 @@ -867,6 +943,14 @@ impl PkeyCtxRef { } } + /// Sets parameters on the given context + #[corresponds(EVP_PKEY_CTX_set_params)] + #[cfg(ossl300)] + #[cfg_attr(not(ossl320), allow(dead_code))] + fn set_params(&mut self, params: &OsslParamArrayRef) -> 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). @@ -876,17 +960,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 = CStr::from_bytes_with_nul(b"nonce-type\0").unwrap(); - 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 mut builder = OsslParamBuilder::new()?; + builder.add_uint(OSSL_SIGNATURE_PARAM_NONCE_TYPE, nonce_type.0)?; + let params = builder.to_param()?; + self.set_params(¶ms) } /// Gets the nonce type for a private key context. @@ -898,11 +975,12 @@ 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 mut nonce_type: c_uint = 0; unsafe { - let param_nonce = - ffi::OSSL_PARAM_construct_uint(nonce_field_name.as_ptr(), &mut nonce_type); + let param_nonce = ffi::OSSL_PARAM_construct_uint( + OSSL_SIGNATURE_PARAM_NONCE_TYPE.as_ptr(), + &mut nonce_type, + ); let param_end = ffi::OSSL_PARAM_construct_end(); let mut params = [param_nonce, param_end]; @@ -915,6 +993,18 @@ impl PkeyCtxRef { } } +/// Creates a new `PKey` from the given ID and parameters. +#[cfg(ossl300)] +#[allow(dead_code)] // TODO: remove when used by pkey creation +pub(crate) fn pkey_from_params( + id: Id, + params: &OsslParamArrayRef, +) -> Result, ErrorStack> { + let mut ctx = PkeyCtx::new_id(id)?; + ctx.fromdata_init()?; + ctx.fromdata(params, K::SELECTION) +} + #[cfg(test)] mod test { use super::*; @@ -925,7 +1015,11 @@ mod test { use crate::hash::{hash, MessageDigest}; use crate::md::Md; use crate::nid::Nid; + #[cfg(ossl300)] + use crate::ossl_param::OsslParamBuilder; use crate::pkey::PKey; + #[cfg(ossl300)] + use crate::pkey::{OSSL_PKEY_PARAM_RSA_D, OSSL_PKEY_PARAM_RSA_E, OSSL_PKEY_PARAM_RSA_N}; use crate::rsa::Rsa; use crate::sign::Verifier; #[cfg(not(boringssl))] @@ -1301,4 +1395,26 @@ mxJ7imIrEg9nIQ== assert_eq!(output, expected_output); assert!(ErrorStack::get().errors().is_empty()); } + + #[test] + #[cfg(ossl300)] + fn test_pkey_from_params() { + let n = BigNum::from_u32(0xbc747fc5).unwrap(); + let e = BigNum::from_u32(0x10001).unwrap(); + let d = BigNum::from_u32(0x7b133399).unwrap(); + + let mut builder = OsslParamBuilder::new().unwrap(); + builder.add_bn(OSSL_PKEY_PARAM_RSA_N, &n).unwrap(); + builder.add_bn(OSSL_PKEY_PARAM_RSA_E, &e).unwrap(); + builder.add_bn(OSSL_PKEY_PARAM_RSA_D, &d).unwrap(); + let params = builder.to_param().unwrap(); + + let pkey: PKey = pkey_from_params(Id::RSA, ¶ms).unwrap(); + + let rsa = pkey.rsa().unwrap(); + + assert_eq!(rsa.n(), &n); + assert_eq!(rsa.e(), &e); + assert_eq!(rsa.d(), &d); + } } diff --git a/openssl/test/dhparams.der b/openssl/test/dhparams.der new file mode 100644 index 0000000000..bf0cd0cb4b Binary files /dev/null and b/openssl/test/dhparams.der differ diff --git a/openssl/test/pkcs1.der.pub b/openssl/test/pkcs1.der.pub new file mode 100644 index 0000000000..0e54c50f46 Binary files /dev/null and b/openssl/test/pkcs1.der.pub differ diff --git a/openssl/test/rsa.der b/openssl/test/rsa.der new file mode 100644 index 0000000000..500c05fafc Binary files /dev/null and b/openssl/test/rsa.der differ