From 56e94e335ce7519b0c5e2ae7e530730a83220d18 Mon Sep 17 00:00:00 2001 From: Felix Huettner Date: Mon, 1 May 2023 21:14:10 +0200 Subject: [PATCH] add other name support the issue with other name SANs is that they can contain arbitary data. As we can no longer use the old method for other_name for security reasons we now add `other_name2` as an alternative. --- openssl-sys/src/handwritten/asn1.rs | 9 ++++++++ openssl-sys/src/handwritten/x509v3.rs | 5 +++++ openssl/src/asn1.rs | 1 + openssl/src/x509/extension.rs | 23 +++++++++++++++----- openssl/src/x509/mod.rs | 31 +++++++++++++++++++++++++++ openssl/src/x509/tests.rs | 28 ++++++++++++++++++++++++ 6 files changed, 92 insertions(+), 5 deletions(-) diff --git a/openssl-sys/src/handwritten/asn1.rs b/openssl-sys/src/handwritten/asn1.rs index fa43a7a5c1..16ffcccfe7 100644 --- a/openssl-sys/src/handwritten/asn1.rs +++ b/openssl-sys/src/handwritten/asn1.rs @@ -10,6 +10,7 @@ pub struct ASN1_ENCODING { extern "C" { pub fn ASN1_OBJECT_free(x: *mut ASN1_OBJECT); + pub fn OBJ_dup(x: *const ASN1_OBJECT) -> *mut ASN1_OBJECT; } stack!(stack_st_ASN1_OBJECT); @@ -94,7 +95,14 @@ extern "C" { #[cfg(ossl110)] pub fn ASN1_ENUMERATED_get_int64(pr: *mut i64, a: *const ASN1_ENUMERATED) -> c_int; + pub fn ASN1_TYPE_new() -> *mut ASN1_TYPE; + pub fn ASN1_TYPE_set(a: *mut ASN1_TYPE, type_: c_int, value: *mut c_void); pub fn ASN1_TYPE_free(x: *mut ASN1_TYPE); + pub fn d2i_ASN1_TYPE( + k: *mut *mut ASN1_TYPE, + buf: *mut *const u8, + len: c_long, + ) -> *mut ASN1_TYPE; } const_ptr_api! { @@ -102,5 +110,6 @@ const_ptr_api! { pub fn ASN1_STRING_to_UTF8(out: *mut *mut c_uchar, s: #[const_ptr_if(any(ossl110, libressl280))] ASN1_STRING) -> c_int; pub fn ASN1_STRING_type(x: #[const_ptr_if(any(ossl110, libressl280))] ASN1_STRING) -> c_int; pub fn ASN1_generate_v3(str: #[const_ptr_if(any(ossl110, libressl280))] c_char, cnf: *mut X509V3_CTX) -> *mut ASN1_TYPE; + pub fn i2d_ASN1_TYPE(a: #[const_ptr_if(ossl300)] ASN1_TYPE, pp: *mut *mut c_uchar) -> c_int; } } diff --git a/openssl-sys/src/handwritten/x509v3.rs b/openssl-sys/src/handwritten/x509v3.rs index f92441134e..2ee0452597 100644 --- a/openssl-sys/src/handwritten/x509v3.rs +++ b/openssl-sys/src/handwritten/x509v3.rs @@ -6,6 +6,11 @@ pub enum CONF_METHOD {} extern "C" { pub fn GENERAL_NAME_new() -> *mut GENERAL_NAME; pub fn GENERAL_NAME_free(name: *mut GENERAL_NAME); + pub fn GENERAL_NAME_set0_othername( + gen: *mut GENERAL_NAME, + oid: *mut ASN1_OBJECT, + value: *mut ASN1_TYPE, + ) -> c_int; } #[repr(C)] diff --git a/openssl/src/asn1.rs b/openssl/src/asn1.rs index d75e05166e..0e720ae0b3 100644 --- a/openssl/src/asn1.rs +++ b/openssl/src/asn1.rs @@ -655,6 +655,7 @@ impl Asn1OctetStringRef { foreign_type_and_impl_send_sync! { type CType = ffi::ASN1_OBJECT; fn drop = ffi::ASN1_OBJECT_free; + fn clone = ffi::OBJ_dup; /// Object Identifier /// diff --git a/openssl/src/x509/extension.rs b/openssl/src/x509/extension.rs index 075227dec3..11e0151530 100644 --- a/openssl/src/x509/extension.rs +++ b/openssl/src/x509/extension.rs @@ -434,6 +434,7 @@ enum RustGeneralName { Uri(String), Ip(String), Rid(String), + OtherName(Asn1Object, Vec), } /// An extension that allows additional identities to be bound to the subject @@ -506,12 +507,21 @@ impl SubjectAlternativeName { /// Sets the `otherName` flag. /// - /// Not currently actually supported, always panics. - #[deprecated = "other_name is deprecated and always panics. Please file a bug if you have a use case for this."] + /// Not currently actually supported, always panics. Please use other_name2 + #[deprecated = "other_name is deprecated and always panics. Please use other_name2."] pub fn other_name(&mut self, _other_name: &str) -> &mut SubjectAlternativeName { - unimplemented!( - "This has not yet been adapted for the new internals. File a bug if you need this." - ); + unimplemented!("This has not yet been adapted for the new internals. Use other_name2."); + } + + /// Sets the `otherName` flag. + /// + /// `content` must be a valid der encoded ASN1_TYPE + /// + /// If you want to add just a ia5string use `other_name_ia5string` + pub fn other_name2(&mut self, oid: Asn1Object, content: &[u8]) -> &mut SubjectAlternativeName { + self.items + .push(RustGeneralName::OtherName(oid, content.into())); + self } /// Return a `SubjectAlternativeName` extension as an `X509Extension`. @@ -526,6 +536,9 @@ impl SubjectAlternativeName { GeneralName::new_ip(s.parse().map_err(|_| ErrorStack::get())?)? } RustGeneralName::Rid(s) => GeneralName::new_rid(Asn1Object::from_str(s)?)?, + RustGeneralName::OtherName(oid, content) => { + GeneralName::new_other_name(oid.clone(), content)? + } }; stack.push(gn)?; } diff --git a/openssl/src/x509/mod.rs b/openssl/src/x509/mod.rs index 2b2f8a50d8..4325b132e3 100644 --- a/openssl/src/x509/mod.rs +++ b/openssl/src/x509/mod.rs @@ -2054,6 +2054,37 @@ impl GeneralName { Ok(GeneralName::from_ptr(gn)) } } + + pub(crate) fn new_other_name( + oid: Asn1Object, + value: &Vec, + ) -> Result { + unsafe { + ffi::init(); + + let typ = cvt_p(ffi::d2i_ASN1_TYPE( + ptr::null_mut(), + &mut value.as_ptr().cast(), + value.len().try_into().unwrap(), + ))?; + + let gn = cvt_p(ffi::GENERAL_NAME_new())?; + (*gn).type_ = ffi::GEN_OTHERNAME; + + if let Err(e) = cvt(ffi::GENERAL_NAME_set0_othername( + gn, + oid.as_ptr().cast(), + typ, + )) { + ffi::GENERAL_NAME_free(gn); + return Err(e); + } + + mem::forget(oid); + + Ok(GeneralName::from_ptr(gn)) + } + } } impl GeneralNameRef { diff --git a/openssl/src/x509/tests.rs b/openssl/src/x509/tests.rs index a3f3cd8803..da3ce2fed2 100644 --- a/openssl/src/x509/tests.rs +++ b/openssl/src/x509/tests.rs @@ -27,6 +27,9 @@ use crate::x509::{CrlReason, X509Builder}; use crate::x509::{ CrlStatus, X509Crl, X509Extension, X509Name, X509Req, X509StoreContext, X509VerifyResult, X509, }; + +#[cfg(ossl110)] +use foreign_types::ForeignType; use hex::{self, FromHex}; #[cfg(any(ossl102, libressl261))] use libc::time_t; @@ -1105,6 +1108,31 @@ fn ipv6_as_subject_alternative_name_is_formatted_in_debug() { ]); } +#[cfg(ossl110)] +#[test] +fn other_name_as_subject_alternative_name() { + let oid = Asn1Object::from_str("1.3.6.1.5.5.7.8.11").unwrap(); + // this is the hex representation of "test" encoded as a ia5string + let content = [0x16, 0x04, 0x74, 0x65, 0x73, 0x74]; + + let mut builder = X509Builder::new().unwrap(); + let san = SubjectAlternativeName::new() + .other_name2(oid, &content) + .build(&builder.x509v3_context(None, None)) + .unwrap(); + builder.append_extension(san).unwrap(); + let cert = builder.build(); + let general_name = cert + .subject_alt_names() + .into_iter() + .flatten() + .next() + .unwrap(); + unsafe { + assert_eq!((*general_name.as_ptr()).type_, 0); + } +} + #[test] fn test_dist_point() { let cert = include_bytes!("../../test/certv3.pem");