Skip to content

Commit

Permalink
expose DANE functions to openssl crate
Browse files Browse the repository at this point in the history
  • Loading branch information
wez committed Oct 14, 2023
1 parent 9229407 commit c82347f
Show file tree
Hide file tree
Showing 3 changed files with 298 additions and 1 deletion.
1 change: 1 addition & 0 deletions openssl-sys/src/handwritten/ssl.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}
1 change: 1 addition & 0 deletions openssl-sys/src/x509_vfy.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
297 changes: 296 additions & 1 deletion openssl/src/ssl/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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::*;
Expand Down Expand Up @@ -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<crate::md::MdRef>,
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
Expand Down Expand Up @@ -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<bool, ErrorStack> {
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<DaneAuthority<'_>, 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<DaneTlsaUsed<'_>, 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<Public>>,

/// 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 {
Expand Down

0 comments on commit c82347f

Please sign in to comment.