Skip to content

Commit 482575b

Browse files
committed
Resolve an injection vulnerability in SAN creation
1 parent 319200a commit 482575b

File tree

5 files changed

+185
-24
lines changed

5 files changed

+185
-24
lines changed

openssl-sys/src/handwritten/x509.rs

+7
Original file line numberDiff line numberDiff line change
@@ -550,6 +550,13 @@ extern "C" {
550550
pub fn X509_EXTENSION_get_object(ext: *mut X509_EXTENSION) -> *mut ASN1_OBJECT;
551551
pub fn X509_EXTENSION_get_data(ext: *mut X509_EXTENSION) -> *mut ASN1_OCTET_STRING;
552552
}
553+
554+
const_ptr_api! {
555+
extern "C" {
556+
pub fn i2d_X509_EXTENSION(ext: #[const_ptr_if(ossl300)] X509_EXTENSION, pp: *mut *mut c_uchar) -> c_int;
557+
}
558+
}
559+
553560
const_ptr_api! {
554561
extern "C" {
555562
// in X509

openssl-sys/src/handwritten/x509v3.rs

+1
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ use libc::*;
44
pub enum CONF_METHOD {}
55

66
extern "C" {
7+
pub fn GENERAL_NAME_new() -> *mut GENERAL_NAME;
78
pub fn GENERAL_NAME_free(name: *mut GENERAL_NAME);
89
}
910

openssl/src/x509/extension.rs

+48-21
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,8 @@ use std::fmt::Write;
2020

2121
use crate::error::ErrorStack;
2222
use crate::nid::Nid;
23-
use crate::x509::{X509Extension, X509v3Context};
23+
use crate::x509::{Asn1Object, GeneralName, Stack, X509Extension, X509v3Context};
24+
use foreign_types::ForeignType;
2425

2526
/// An extension which indicates whether a certificate is a CA certificate.
2627
pub struct BasicConstraints {
@@ -463,11 +464,19 @@ impl AuthorityKeyIdentifier {
463464
}
464465
}
465466

467+
enum RustGeneralName {
468+
Dns(String),
469+
Email(String),
470+
Uri(String),
471+
Ip(String),
472+
Rid(String),
473+
}
474+
466475
/// An extension that allows additional identities to be bound to the subject
467476
/// of the certificate.
468477
pub struct SubjectAlternativeName {
469478
critical: bool,
470-
names: Vec<String>,
479+
items: Vec<RustGeneralName>,
471480
}
472481

473482
impl Default for SubjectAlternativeName {
@@ -481,7 +490,7 @@ impl SubjectAlternativeName {
481490
pub fn new() -> SubjectAlternativeName {
482491
SubjectAlternativeName {
483492
critical: false,
484-
names: vec![],
493+
items: vec![],
485494
}
486495
}
487496

@@ -493,55 +502,73 @@ impl SubjectAlternativeName {
493502

494503
/// Sets the `email` flag.
495504
pub fn email(&mut self, email: &str) -> &mut SubjectAlternativeName {
496-
self.names.push(format!("email:{}", email));
505+
self.items.push(RustGeneralName::Email(email.to_string()));
497506
self
498507
}
499508

500509
/// Sets the `uri` flag.
501510
pub fn uri(&mut self, uri: &str) -> &mut SubjectAlternativeName {
502-
self.names.push(format!("URI:{}", uri));
511+
self.items.push(RustGeneralName::Uri(uri.to_string()));
503512
self
504513
}
505514

506515
/// Sets the `dns` flag.
507516
pub fn dns(&mut self, dns: &str) -> &mut SubjectAlternativeName {
508-
self.names.push(format!("DNS:{}", dns));
517+
self.items.push(RustGeneralName::Dns(dns.to_string()));
509518
self
510519
}
511520

512521
/// Sets the `rid` flag.
513522
pub fn rid(&mut self, rid: &str) -> &mut SubjectAlternativeName {
514-
self.names.push(format!("RID:{}", rid));
523+
self.items.push(RustGeneralName::Rid(rid.to_string()));
515524
self
516525
}
517526

518527
/// Sets the `ip` flag.
519528
pub fn ip(&mut self, ip: &str) -> &mut SubjectAlternativeName {
520-
self.names.push(format!("IP:{}", ip));
529+
self.items.push(RustGeneralName::Ip(ip.to_string()));
521530
self
522531
}
523532

524533
/// Sets the `dirName` flag.
525-
pub fn dir_name(&mut self, dir_name: &str) -> &mut SubjectAlternativeName {
526-
self.names.push(format!("dirName:{}", dir_name));
527-
self
534+
///
535+
/// Not currently actually supported, always panics.
536+
#[deprecated = "dir_name is deprecated and always panics. Please file a bug if you have a use case for this."]
537+
pub fn dir_name(&mut self, _dir_name: &str) -> &mut SubjectAlternativeName {
538+
unimplemented!(
539+
"This has not yet been adapted for the new internals. File a bug if you need this."
540+
);
528541
}
529542

530543
/// Sets the `otherName` flag.
531-
pub fn other_name(&mut self, other_name: &str) -> &mut SubjectAlternativeName {
532-
self.names.push(format!("otherName:{}", other_name));
533-
self
544+
///
545+
/// Not currently actually supported, always panics.
546+
#[deprecated = "other_name is deprecated and always panics. Please file a bug if you have a use case for this."]
547+
pub fn other_name(&mut self, _other_name: &str) -> &mut SubjectAlternativeName {
548+
unimplemented!(
549+
"This has not yet been adapted for the new internals. File a bug if you need this."
550+
);
534551
}
535552

536553
/// Return a `SubjectAlternativeName` extension as an `X509Extension`.
537-
pub fn build(&self, ctx: &X509v3Context<'_>) -> Result<X509Extension, ErrorStack> {
538-
let mut value = String::new();
539-
let mut first = true;
540-
append(&mut value, &mut first, self.critical, "critical");
541-
for name in &self.names {
542-
append(&mut value, &mut first, true, name);
554+
pub fn build(&self, _ctx: &X509v3Context<'_>) -> Result<X509Extension, ErrorStack> {
555+
let mut stack = Stack::new()?;
556+
for item in &self.items {
557+
let gn = match item {
558+
RustGeneralName::Dns(s) => GeneralName::new_dns(s.as_bytes())?,
559+
RustGeneralName::Email(s) => GeneralName::new_email(s.as_bytes())?,
560+
RustGeneralName::Uri(s) => GeneralName::new_uri(s.as_bytes())?,
561+
RustGeneralName::Ip(s) => {
562+
GeneralName::new_ip(s.parse().map_err(|_| ErrorStack::get())?)?
563+
}
564+
RustGeneralName::Rid(s) => GeneralName::new_rid(Asn1Object::from_str(s)?)?,
565+
};
566+
stack.push(gn)?;
567+
}
568+
569+
unsafe {
570+
X509Extension::new_internal(Nid::SUBJECT_ALT_NAME, self.critical, stack.as_ptr().cast())
543571
}
544-
X509Extension::new_nid(None, Some(ctx), Nid::SUBJECT_ALT_NAME, &value)
545572
}
546573
}
547574

openssl/src/x509/mod.rs

+91-3
Original file line numberDiff line numberDiff line change
@@ -9,9 +9,9 @@
99
1010
use cfg_if::cfg_if;
1111
use foreign_types::{ForeignType, ForeignTypeRef, Opaque};
12-
use libc::{c_int, c_long, c_uint};
12+
use libc::{c_int, c_long, c_uint, c_void};
1313
use std::cmp::{self, Ordering};
14-
use std::convert::TryFrom;
14+
use std::convert::{TryFrom, TryInto};
1515
use std::error::Error;
1616
use std::ffi::{CStr, CString};
1717
use std::fmt;
@@ -24,7 +24,8 @@ use std::slice;
2424
use std::str;
2525

2626
use crate::asn1::{
27-
Asn1BitStringRef, Asn1IntegerRef, Asn1ObjectRef, Asn1StringRef, Asn1TimeRef, Asn1Type,
27+
Asn1BitStringRef, Asn1IntegerRef, Asn1Object, Asn1ObjectRef, Asn1StringRef, Asn1TimeRef,
28+
Asn1Type,
2829
};
2930
use crate::bio::MemBioSlice;
3031
use crate::conf::ConfRef;
@@ -851,6 +852,15 @@ impl X509Extension {
851852
}
852853
}
853854

855+
pub(crate) unsafe fn new_internal(
856+
nid: Nid,
857+
critical: bool,
858+
value: *mut c_void,
859+
) -> Result<X509Extension, ErrorStack> {
860+
ffi::init();
861+
cvt_p(ffi::X509V3_EXT_i2d(nid.as_raw(), critical as _, value)).map(X509Extension)
862+
}
863+
854864
/// Adds an alias for an extension
855865
///
856866
/// # Safety
@@ -863,6 +873,15 @@ impl X509Extension {
863873
}
864874
}
865875

876+
impl X509ExtensionRef {
877+
to_der! {
878+
/// Serializes the Extension to its standard DER encoding.
879+
#[corresponds(i2d_X509_EXTENSION)]
880+
to_der,
881+
ffi::i2d_X509_EXTENSION
882+
}
883+
}
884+
866885
/// A builder used to construct an `X509Name`.
867886
pub struct X509NameBuilder(X509Name);
868887

@@ -1715,6 +1734,75 @@ foreign_type_and_impl_send_sync! {
17151734
pub struct GeneralNameRef;
17161735
}
17171736

1737+
impl GeneralName {
1738+
unsafe fn new(
1739+
type_: c_int,
1740+
asn1_type: Asn1Type,
1741+
value: &[u8],
1742+
) -> Result<GeneralName, ErrorStack> {
1743+
ffi::init();
1744+
let gn = GeneralName::from_ptr(cvt_p(ffi::GENERAL_NAME_new())?);
1745+
(*gn.as_ptr()).type_ = type_;
1746+
let s = cvt_p(ffi::ASN1_STRING_type_new(asn1_type.as_raw()))?;
1747+
ffi::ASN1_STRING_set(s, value.as_ptr().cast(), value.len().try_into().unwrap());
1748+
1749+
#[cfg(boringssl)]
1750+
{
1751+
(*gn.as_ptr()).d.ptr = s.cast();
1752+
}
1753+
#[cfg(not(boringssl))]
1754+
{
1755+
(*gn.as_ptr()).d = s.cast();
1756+
}
1757+
1758+
Ok(gn)
1759+
}
1760+
1761+
pub(crate) fn new_email(email: &[u8]) -> Result<GeneralName, ErrorStack> {
1762+
unsafe { GeneralName::new(ffi::GEN_EMAIL, Asn1Type::IA5STRING, email) }
1763+
}
1764+
1765+
pub(crate) fn new_dns(dns: &[u8]) -> Result<GeneralName, ErrorStack> {
1766+
unsafe { GeneralName::new(ffi::GEN_DNS, Asn1Type::IA5STRING, dns) }
1767+
}
1768+
1769+
pub(crate) fn new_uri(uri: &[u8]) -> Result<GeneralName, ErrorStack> {
1770+
unsafe { GeneralName::new(ffi::GEN_URI, Asn1Type::IA5STRING, uri) }
1771+
}
1772+
1773+
pub(crate) fn new_ip(ip: IpAddr) -> Result<GeneralName, ErrorStack> {
1774+
match ip {
1775+
IpAddr::V4(addr) => unsafe {
1776+
GeneralName::new(ffi::GEN_IPADD, Asn1Type::OCTET_STRING, &addr.octets())
1777+
},
1778+
IpAddr::V6(addr) => unsafe {
1779+
GeneralName::new(ffi::GEN_IPADD, Asn1Type::OCTET_STRING, &addr.octets())
1780+
},
1781+
}
1782+
}
1783+
1784+
pub(crate) fn new_rid(oid: Asn1Object) -> Result<GeneralName, ErrorStack> {
1785+
unsafe {
1786+
ffi::init();
1787+
let gn = cvt_p(ffi::GENERAL_NAME_new())?;
1788+
(*gn).type_ = ffi::GEN_RID;
1789+
1790+
#[cfg(boringssl)]
1791+
{
1792+
(*gn).d.registeredID = oid.as_ptr();
1793+
}
1794+
#[cfg(not(boringssl))]
1795+
{
1796+
(*gn).d = oid.as_ptr().cast();
1797+
}
1798+
1799+
mem::forget(oid);
1800+
1801+
Ok(GeneralName::from_ptr(gn))
1802+
}
1803+
}
1804+
}
1805+
17181806
impl GeneralNameRef {
17191807
fn ia5_string(&self, ffi_type: c_int) -> Option<&str> {
17201808
unsafe {

openssl/src/x509/tests.rs

+38
Original file line numberDiff line numberDiff line change
@@ -287,6 +287,44 @@ fn x509_builder() {
287287
assert_eq!(serial, x509.serial_number().to_bn().unwrap());
288288
}
289289

290+
#[test]
291+
fn x509_extension_to_der() {
292+
let builder = X509::builder().unwrap();
293+
294+
for (ext, expected) in [
295+
(
296+
BasicConstraints::new().critical().ca().build().unwrap(),
297+
b"0\x0f\x06\x03U\x1d\x13\x01\x01\xff\x04\x050\x03\x01\x01\xff" as &[u8],
298+
),
299+
(
300+
SubjectAlternativeName::new()
301+
.dns("example.com,DNS:example2.com")
302+
.build(&builder.x509v3_context(None, None))
303+
.unwrap(),
304+
b"0'\x06\x03U\x1d\x11\x04 0\x1e\x82\x1cexample.com,DNS:example2.com",
305+
),
306+
(
307+
SubjectAlternativeName::new()
308+
.rid("1.2.3.4")
309+
.uri("https://example.com")
310+
.build(&builder.x509v3_context(None, None))
311+
.unwrap(),
312+
b"0#\x06\x03U\x1d\x11\x04\x1c0\x1a\x88\x03*\x03\x04\x86\x13https://example.com",
313+
),
314+
(
315+
ExtendedKeyUsage::new()
316+
.server_auth()
317+
.other("2.999.1")
318+
.other("clientAuth")
319+
.build()
320+
.unwrap(),
321+
b"0\x22\x06\x03U\x1d%\x04\x1b0\x19\x06\x08+\x06\x01\x05\x05\x07\x03\x01\x06\x03\x887\x01\x06\x08+\x06\x01\x05\x05\x07\x03\x02",
322+
),
323+
] {
324+
assert_eq!(&ext.to_der().unwrap(), expected);
325+
}
326+
}
327+
290328
#[test]
291329
fn x509_req_builder() {
292330
let pkey = pkey();

0 commit comments

Comments
 (0)