Skip to content

Commit

Permalink
recognize wildcard names
Browse files Browse the repository at this point in the history
  • Loading branch information
Geal committed Jul 5, 2019
1 parent b33ac72 commit 883d845
Show file tree
Hide file tree
Showing 4 changed files with 133 additions and 5 deletions.
132 changes: 130 additions & 2 deletions src/name.rs
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,26 @@ use std::string::String;
#[cfg(feature = "std")]
use std::vec::Vec;

/// A DNS Name suitable for use in the TLS Server Name Indication (SNI)
/// extension and/or for use as the reference hostname for which to verify a
/// certificate.
pub enum GeneralDNSNameRef<'name> {
/// a valid DNS name
DNSName(DNSNameRef<'name>),
/// a DNS name containing a wildcard
Wildcard(WildcardDNSNameRef<'name>),
}

impl<'a> From<GeneralDNSNameRef<'a>> for &'a str {
fn from(d: GeneralDNSNameRef<'a>) -> Self {
match d {
GeneralDNSNameRef::DNSName(name) => name.into(),
GeneralDNSNameRef::Wildcard(name) => name.into(),
}
}
}


/// A DNS Name suitable for use in the TLS Server Name Indication (SNI)
/// extension and/or for use as the reference hostname for which to verify a
/// certificate.
Expand Down Expand Up @@ -137,6 +157,109 @@ impl<'a> From<DNSNameRef<'a>> for untrusted::Input<'a> {
fn from(DNSNameRef(dns_name): DNSNameRef<'a>) -> Self { dns_name }
}

/// A reference to a DNS Name suitable for use in the TLS Server Name Indication
/// (SNI) extension and/or for use as the reference hostname for which to verify
/// a certificate. Compared to `DNSName`, this one will store domain names containing
/// a wildcard.
///
/// A `WildcardDNSName` is guaranteed to be syntactically valid. The validity rules are
/// specified in [RFC 5280 Section 7.2], except that underscores are also
/// allowed, and following [RFC 6125].
///
/// `WildcardDNSName` stores a copy of the input it was constructed from in a `String`
/// and so it is only available when the `std` default feature is enabled.
///
/// `Eq`, `PartialEq`, etc. are not implemented because name comparison
/// frequently should be done case-insensitively and/or with other caveats that
/// depend on the specific circumstances in which the comparison is done.
///
/// [RFC 5280 Section 7.2]: https://tools.ietf.org/html/rfc5280#section-7.2
/// [RFC 6125]: https://tools.ietf.org/html/rfc6125
#[cfg(feature = "std")]
#[derive(Clone, Debug, Eq, PartialEq, Hash)]
pub struct WildcardDNSName(String);

#[cfg(feature = "std")]
impl WildcardDNSName {
/// Returns a `WildcardDNSNameRef` that refers to this `WildcardDNSName`.
pub fn as_ref(&self) -> WildcardDNSNameRef { WildcardDNSNameRef(untrusted::Input::from(self.0.as_bytes())) }
}

#[cfg(feature = "std")]
impl AsRef<str> for WildcardDNSName {
fn as_ref(&self) -> &str { self.0.as_ref() }
}

// Deprecated
#[cfg(feature = "std")]
impl From<WildcardDNSNameRef<'_>> for WildcardDNSName {
fn from(dns_name: WildcardDNSNameRef) -> Self { dns_name.to_owned() }
}

/// A reference to a DNS Name suitable for use in the TLS Server Name Indication
/// (SNI) extension and/or for use as the reference hostname for which to verify
/// a certificate.
///
/// A `WildcardDNSNameRef` is guaranteed to be syntactically valid. The validity rules
/// are specified in [RFC 5280 Section 7.2], except that underscores are also
/// allowed.
///
/// `Eq`, `PartialEq`, etc. are not implemented because name comparison
/// frequently should be done case-insensitively and/or with other caveats that
/// depend on the specific circumstances in which the comparison is done.
///
/// [RFC 5280 Section 7.2]: https://tools.ietf.org/html/rfc5280#section-7.2
#[derive(Clone, Copy)]
pub struct WildcardDNSNameRef<'a>(untrusted::Input<'a>);

impl<'a> WildcardDNSNameRef<'a> {
/// Constructs a `WildcardDNSNameRef` from the given input if the input is a
/// syntactically-valid DNS name.
pub fn try_from_ascii(dns_name: untrusted::Input<'a>) -> Result<Self, InvalidDNSNameError> {
if !is_valid_wildcard_dns_id(dns_name) {
return Err(InvalidDNSNameError);
}

Ok(Self(dns_name))
}

/// Constructs a `WildcardDNSNameRef` from the given input if the input is a
/// syntactically-valid DNS name.
pub fn try_from_ascii_str(dns_name: &'a str) -> Result<Self, InvalidDNSNameError> {
Self::try_from_ascii(untrusted::Input::from(dns_name.as_bytes()))
}

/// Constructs a `WildcardDNSName` from this `WildcardDNSNameRef`
#[cfg(feature = "std")]
pub fn to_owned(&self) -> WildcardDNSName {
// WildcardDNSNameRef is already guaranteed to be valid ASCII, which is a
// subset of UTF-8.
let s: &str = self.clone().into();
WildcardDNSName(s.to_ascii_lowercase())
}
}

#[cfg(feature = "std")]
impl core::fmt::Debug for WildcardDNSNameRef<'_> {
fn fmt(&self, f: &mut core::fmt::Formatter) -> Result<(), core::fmt::Error> {
let lowercase = self.clone().to_owned();
f.debug_tuple("WildcardDNSNameRef").field(&lowercase.0).finish()
}
}

impl<'a> From<WildcardDNSNameRef<'a>> for &'a str {
fn from(WildcardDNSNameRef(d): WildcardDNSNameRef<'a>) -> Self {
// The unwrap won't fail because DNSNameRefs are guaranteed to be ASCII
// and ASCII is a subset of UTF-8.
core::str::from_utf8(d.as_slice_less_safe()).unwrap()
}
}

impl<'a> From<WildcardDNSNameRef<'a>> for untrusted::Input<'a> {
fn from(WildcardDNSNameRef(dns_name): WildcardDNSNameRef<'a>) -> Self { dns_name }
}


pub fn verify_cert_dns_name(
cert: &super::EndEntityCert, DNSNameRef(dns_name): DNSNameRef,
) -> Result<(), Error> {
Expand Down Expand Up @@ -168,14 +291,15 @@ pub fn verify_cert_dns_name(

#[cfg(feature = "std")]
pub fn list_cert_dns_names<'names>(cert: &super::EndEntityCert<'names>)
-> Result<Vec<DNSNameRef<'names>>, Error> {
-> Result<Vec<GeneralDNSNameRef<'names>>, Error> {
let cert = &cert.inner;
let names = std::cell::RefCell::new(Vec::new());

iterate_names(cert.subject, cert.subject_alt_name, Ok(()), &|name| {
match name {
GeneralName::DNSName(presented_id) => {
match DNSNameRef::try_from_ascii(presented_id) {
match DNSNameRef::try_from_ascii(presented_id).map(GeneralDNSNameRef::DNSName)
.or_else(|_| WildcardDNSNameRef::try_from_ascii(presented_id).map(GeneralDNSNameRef::Wildcard)) {
Ok(name) => names.borrow_mut().push(name),
Err(_) => { /* keep going */ },
};
Expand Down Expand Up @@ -788,6 +912,10 @@ fn is_valid_reference_dns_id(hostname: untrusted::Input) -> bool {
is_valid_dns_id(hostname, IDRole::ReferenceID, AllowWildcards::No)
}

fn is_valid_wildcard_dns_id(hostname: untrusted::Input) -> bool {
is_valid_dns_id(hostname, IDRole::ReferenceID, AllowWildcards::Yes)
}

// https://tools.ietf.org/html/rfc5280#section-4.2.1.6:
//
// When the subjectAltName extension contains a domain name system
Expand Down
4 changes: 2 additions & 2 deletions src/webpki.rs
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@ pub mod trust_anchor_util;
mod verify_cert;

pub use error::Error;
pub use name::{DNSNameRef, InvalidDNSNameError};
pub use name::{GeneralDNSNameRef, DNSNameRef, WildcardDNSNameRef, InvalidDNSNameError};

#[cfg(feature = "std")]
pub use name::DNSName;
Expand Down Expand Up @@ -244,7 +244,7 @@ impl<'a> EndEntityCert<'a> {
/// Requires the `std` default feature; i.e. this isn't available in
/// `#![no_std]` configurations.
#[cfg(feature = "std")]
pub fn dns_names(&self) -> Result<Vec<DNSNameRef<'a>>, Error> {
pub fn dns_names(&self) -> Result<Vec<GeneralDNSNameRef<'a>>, Error> {
name::list_cert_dns_names(&self)
}
}
Expand Down
2 changes: 1 addition & 1 deletion tests/integration.rs
Original file line number Diff line number Diff line change
Expand Up @@ -175,7 +175,7 @@ pub fn wildcard_subject_alternative_names()

expect_cert_dns_names(data, &[
"account.netflix.com",
// NOT "c*.netflix.com",
"*.netflix.com",
"netflix.ca",
"netflix.com",
"signup.netflix.com",
Expand Down
Binary file modified tests/misc/dns_names_and_wildcards.der
Binary file not shown.

0 comments on commit 883d845

Please sign in to comment.