From c82347f028e40ce102db4147ac12c5b1380669e6 Mon Sep 17 00:00:00 2001 From: Wez Furlong Date: Sat, 14 Oct 2023 15:11:26 -0700 Subject: [PATCH] expose DANE functions to openssl crate --- openssl-sys/src/handwritten/ssl.rs | 1 + openssl-sys/src/x509_vfy.rs | 1 + openssl/src/ssl/mod.rs | 297 ++++++++++++++++++++++++++++- 3 files changed, 298 insertions(+), 1 deletion(-) diff --git a/openssl-sys/src/handwritten/ssl.rs b/openssl-sys/src/handwritten/ssl.rs index 9a0b3ee601..34bed5fd3f 100644 --- a/openssl-sys/src/handwritten/ssl.rs +++ b/openssl-sys/src/handwritten/ssl.rs @@ -961,4 +961,5 @@ extern "C" { pub fn SSL_CTX_dane_clear_flags(ctx: *mut SSL_CTX, flags: c_ulong) -> c_ulong; pub fn SSL_dane_set_flags(ssl: *mut SSL, flags: c_ulong) -> c_ulong; pub fn SSL_dane_clear_flags(ssl: *mut SSL, flags: c_ulong) -> c_ulong; + pub fn SSL_add1_host(s: *mut SSL, hostname: *const c_char) -> c_int; } diff --git a/openssl-sys/src/x509_vfy.rs b/openssl-sys/src/x509_vfy.rs index 2fa176fed5..91fb077103 100644 --- a/openssl-sys/src/x509_vfy.rs +++ b/openssl-sys/src/x509_vfy.rs @@ -91,6 +91,7 @@ cfg_if! { pub const X509_V_ERR_INVALID_CALL: c_int = 69; pub const X509_V_ERR_STORE_LOOKUP: c_int = 70; pub const X509_V_ERR_NO_VALID_SCTS: c_int = 71; + pub const DANE_FLAG_NO_DANE_EE_NAMECHECKS: c_ulong = 1; } else if #[cfg(ossl102h)] { pub const X509_V_ERR_INVALID_CALL: c_int = 65; pub const X509_V_ERR_STORE_LOOKUP: c_int = 66; diff --git a/openssl/src/ssl/mod.rs b/openssl/src/ssl/mod.rs index bdfbfc14f0..9b30f31622 100644 --- a/openssl/src/ssl/mod.rs +++ b/openssl/src/ssl/mod.rs @@ -67,7 +67,7 @@ use crate::ex_data::Index; use crate::hash::MessageDigest; #[cfg(any(ossl110, libressl270))] use crate::nid::Nid; -use crate::pkey::{HasPrivate, PKeyRef, Params, Private}; +use crate::pkey::{HasPrivate, PKeyRef, Params, Private, Public}; use crate::srtp::{SrtpProtectionProfile, SrtpProtectionProfileRef}; use crate::ssl::bio::BioMethod; use crate::ssl::callbacks::*; @@ -1718,6 +1718,89 @@ impl SslContextBuilder { unsafe { cvt(ffi::SSL_CTX_set_num_tickets(self.as_ptr(), num_tickets)).map(|_| ()) } } + /// initialize the shared state required for DANE support. + /// + /// Requires OpenSSL 1.1.0 or newer. + #[corresponds(SSL_CTX_dane_enable)] + #[cfg(ossl110)] + pub fn dane_enable(&mut self) -> Result<(), ErrorStack> { + unsafe { cvt(ffi::SSL_CTX_dane_enable(self.as_ptr())).map(|_| ()) } + } + + /// Adjust the supported digest algorithms. + /// This must be done before any SSL handles are created for the context. + /// + /// The mtype argument specifies a DANE TLSA matching type and the md argument + /// specifies the associated digest algorithm handle. + /// The ord argument specifies a strength ordinal. + /// + /// Algorithms with a larger strength ordinal are considered more secure. + /// + /// Strength ordinals are used to implement RFC7671 digest algorithm agility. + /// + /// Specifying None for the digest algorithm for a matching type disables + /// support for that matching type. + /// + /// Matching type Full(0) cannot be modified or disabled. + /// + /// By default, matching type SHA2-256(1) (see RFC7218 for definitions of + /// the DANE TLSA parameter acronyms) is mapped to EVP_sha256() with a strength + /// ordinal of 1 and matching type SHA2-512(2) is mapped to EVP_sha512() + /// with a strength ordinal of 2. + /// + /// Requires OpenSSL 1.1.0 or newer. + #[corresponds(SSL_CTX_dane_mtype_set)] + #[cfg(ossl110)] + pub fn dane_mtype_set( + &mut self, + md: Option, + mtype: u8, + ord: u8, + ) -> Result<(), ErrorStack> { + unsafe { + cvt(ffi::SSL_CTX_dane_mtype_set( + self.as_ptr(), + md.map(|md| md.as_ptr()).unwrap_or(std::ptr::null_mut()), + mtype, + ord, + )) + .map(|_| ()) + } + } + + /// Enable optional DANE verification features. + /// + /// Currently, the only supported feature is `DANE_FLAG_NO_DANE_EE_NAMECHECKS` + /// which can be used to disable server name checks when authenticating via DANE-EE(3) TLSA + /// records. For some applications, primarily web browsers, it is not safe to disable name + /// checks due to "unknown key share" attacks, in which a malicious server can convince a + /// client that a connection to a victim server is instead a secure connection to the malicious + /// server. The malicious server may then be able to violate cross-origin scripting + /// restrictions. Thus, despite the text of RFC7671, name checks are by default enabled for + /// DANE-EE(3) TLSA records, and can be disabled in applications where it is safe to do so. In + /// particular, SMTP and XMPP clients should set this option as SRV and MX records already make + /// it possible for a remote domain to redirect client connections to any server of its choice, + /// and in any case SMTP and XMPP clients do not execute scripts downloaded from remote + /// servers. + /// + /// Requires OpenSSL 1.1.0 or newer. + #[corresponds(SSL_CTX_dane_set_flags)] + #[cfg(ossl110)] + pub fn dane_set_flags(&mut self, flags: libc::c_ulong) -> libc::c_ulong { + unsafe { ffi::SSL_CTX_dane_set_flags(self.as_ptr(), flags) } + } + + /// Disable optional DANE verification features. + /// + /// See `dane_set_flags` for more information. + /// + /// Requires OpenSSL 1.1.0 or newer. + #[corresponds(SSL_CTX_dane_clear_flags)] + #[cfg(ossl110)] + pub fn dane_clear_flags(&mut self, flags: libc::c_ulong) -> libc::c_ulong { + unsafe { ffi::SSL_CTX_dane_clear_flags(self.as_ptr(), flags) } + } + /// Consumes the builder, returning a new `SslContext`. pub fn build(self) -> SslContext { self.0 @@ -2309,6 +2392,218 @@ impl Ssl { { SslStreamBuilder::new(self, stream).accept() } + + /// Enable optional DANE verification features. + /// + /// Currently, the only supported feature is `DANE_FLAG_NO_DANE_EE_NAMECHECKS` + /// which can be used to disable server name checks when authenticating via DANE-EE(3) TLSA + /// records. For some applications, primarily web browsers, it is not safe to disable name + /// checks due to "unknown key share" attacks, in which a malicious server can convince a + /// client that a connection to a victim server is instead a secure connection to the malicious + /// server. The malicious server may then be able to violate cross-origin scripting + /// restrictions. Thus, despite the text of RFC7671, name checks are by default enabled for + /// DANE-EE(3) TLSA records, and can be disabled in applications where it is safe to do so. In + /// particular, SMTP and XMPP clients should set this option as SRV and MX records already make + /// it possible for a remote domain to redirect client connections to any server of its choice, + /// and in any case SMTP and XMPP clients do not execute scripts downloaded from remote + /// servers. + /// + /// Requires OpenSSL 1.1.0 or newer. + #[corresponds(SSL_dane_set_flags)] + #[cfg(ossl110)] + pub fn dane_set_flags(&mut self, flags: libc::c_ulong) -> libc::c_ulong { + unsafe { ffi::SSL_dane_set_flags(self.as_ptr(), flags) } + } + + /// Disable optional DANE verification features. + /// + /// See `dane_set_flags` for more information. + /// + /// Requires OpenSSL 1.1.0 or newer. + #[corresponds(SSL_dane_clear_flags)] + #[cfg(ossl110)] + pub fn dane_clear_flags(&mut self, flags: libc::c_ulong) -> libc::c_ulong { + unsafe { ffi::SSL_dane_clear_flags(self.as_ptr(), flags) } + } + + /// adds name as an additional reference identifier that can match the peer's certificate. Any + /// previous names set via SSL_set1_host() or SSL_add1_host() are retained, no change is made + /// if name is NULL or empty. When multiple names are configured, the peer is considered + /// verified when any name matches. This function is required for DANE TLSA in the presence of + /// service name indirection via CNAME, MX or SRV records as specified in RFC7671, RFC7672 or + /// RFC7673. + /// + /// Requires OpenSSL 1.1.0 or newer. + #[corresponds(SSL_add1_host)] + #[cfg(ossl110)] + pub fn add1_host(&mut self, hostname: &str) -> Result<(), ErrorStack> { + let cstr = CString::new(hostname).unwrap(); + unsafe { cvt(ffi::SSL_add1_host(self.as_ptr(), cstr.as_ptr())).map(|_| ()) } + } + + /// must be called before the SSL handshake is initiated with SSL_connect(3) + /// if (and only if) you want to enable DANE for that connection. + /// + /// The connection must be associated with a DANE-enabled SSL context. + /// + /// The base_domain argument specifies the RFC7671 TLSA base domain, + /// which will be the primary peer reference identifier for certificate name checks. + /// + /// Additional server names can be specified via `add1_host`. + /// + /// The base_domain is used as the default SNI hint if none has yet been + /// specified via SSL_set_tlsext_host_name(3). + /// + /// Requires OpenSSL 1.1.0 or newer. + #[corresponds(SSL_dane_enable)] + #[cfg(ossl110)] + pub fn dane_enable(&mut self, base_domain: &str) -> Result<(), ErrorStack> { + let cstr = CString::new(base_domain).unwrap(); + unsafe { cvt(ffi::SSL_dane_enable(self.as_ptr(), cstr.as_ptr())).map(|_| ()) } + } + + /// may be called one or more times to load each of the TLSA records that apply + /// to the remote TLS peer. + /// + /// This must be done prior to the beginning of the SSL handshake. + /// + /// The arguments specify the fields of the TLSA record. + /// The data field is provided in binary (wire RDATA) form, + /// not the hexadecimal ASCII presentation form. + /// + /// Returns a boolean to indicate whether the record was usable or not. + /// + /// Requires OpenSSL 1.1.0 or newer. + #[corresponds(SSL_dane_tlsa_add)] + #[cfg(ossl110)] + pub fn dane_tlsa_add( + &mut self, + usage: u8, + selector: u8, + mtype: u8, + data: &[u8], + ) -> Result { + let usable = unsafe { + cvt_n(ffi::SSL_dane_tlsa_add( + self.as_ptr(), + usage, + selector, + mtype, + data.as_ptr() as *const c_uchar, + data.len(), + )) + }?; + + Ok(usable > 0) + } + + /// get more detailed information about the matched DANE trust-anchor after + /// successful connection completion. + /// + /// Returns an error if DANE verification failed or was not enabled. + /// + /// Returns a DaneAuthority struct on success. + /// + /// The complete verified chain can be retrieved via `verified_chain`. + /// The `depth` field is an index into this verified chain. + /// + /// Requires OpenSSL 1.1.0 or newer. + #[corresponds(SSL_get0_dane_authority)] + #[cfg(ossl110)] + pub fn dane_authority(&self) -> Result, ErrorStack> { + let mut mcert = std::ptr::null_mut(); + let mut mspki = std::ptr::null_mut(); + let depth = unsafe { + cvt_n(ffi::SSL_get0_dane_authority( + self.as_ptr(), + &mut mcert, + &mut mspki, + ))? + } as usize; + + let cert = if mcert.is_null() { + None + } else { + unsafe { Some(X509Ref::from_ptr(mcert)) } + }; + + let pkey = if mspki.is_null() { + None + } else { + unsafe { PKeyRef::from_const_ptr_opt(mspki) } + }; + + Ok(DaneAuthority { depth, cert, pkey }) + } + + /// retrieve the fields of the TLSA record that matched the peer certificate chain. + /// + /// Returns an error if DANE verification failed or was not enabled. + /// + /// Returns a DaneTlsaUsed struct on success; the fields are populated with + /// the information from the TLSA record that matched the peer certificate chain. + /// + /// Requires OpenSSL 1.1.0 or newer. + #[corresponds(SSL_get0_dane_tlsa)] + #[cfg(ossl110)] + pub fn dane_tlsa(&self) -> Result, ErrorStack> { + let mut usage = 0; + let mut selector = 0; + let mut mtype = 0; + let mut data = std::ptr::null(); + let mut dlen = 0; + + let depth = unsafe { + cvt_n(ffi::SSL_get0_dane_tlsa( + self.as_ptr(), + &mut usage, + &mut selector, + &mut mtype, + &mut data, + &mut dlen, + ))? + } as usize; + + Ok(DaneTlsaUsed { + usage, + selector, + mtype, + depth, + data: unsafe { std::slice::from_raw_parts(data, dlen) }, + }) + } +} + +/// Returns information about the matched DANE trust-anchor +pub struct DaneAuthority<'a> { + /// If a TLSA record matched a chain certificate, this holds + /// that certificate + pub cert: Option<&'a X509Ref>, + + /// If no TLSA records directly matched any elements of the certificate chain, + /// but a DANE-TA(2) SPKI(1) Full(0) record provided the public key that signed + /// an element of the chain, then that key is returned here + pub pkey: Option<&'a PKeyRef>, + + /// The match depth. + /// 0 if an EE TLSA record directly matched the leaf certificate, or a positive + /// number indicating the depth at which a TA record matched an issuer certificate. + pub depth: usize, +} + +/// Represents the fields of the TLSA DNS record that matched the peer +/// certificate chain when DANE verification was successful. +#[derive(Debug)] +pub struct DaneTlsaUsed<'a> { + pub usage: u8, + pub selector: u8, + pub mtype: u8, + + /// The binary data in wire form + pub data: &'a [u8], + + /// The match depth + pub depth: usize, } impl fmt::Debug for SslRef {