From aae34c01a75495620767ee1fed40ac4c0a34fe74 Mon Sep 17 00:00:00 2001 From: Geoffroy Couprie Date: Thu, 28 Dec 2017 23:44:36 +0100 Subject: [PATCH] add a method to collect the DNS names from a certificate --- src/name.rs | 30 ++++- src/webpki.rs | 13 ++ tests/integration.rs | 111 ++++++++++++++++++ tests/misc/dns_names_and_wildcards.der | Bin 0 -> 1772 bytes .../misc/invalid_subject_alternative_name.der | Bin 0 -> 1772 bytes tests/misc/no_subject_alternative_name.der | Bin 0 -> 797 bytes 6 files changed, 151 insertions(+), 3 deletions(-) create mode 100644 tests/misc/dns_names_and_wildcards.der create mode 100644 tests/misc/invalid_subject_alternative_name.der create mode 100644 tests/misc/no_subject_alternative_name.der diff --git a/src/name.rs b/src/name.rs index 82a85373..40afe88f 100644 --- a/src/name.rs +++ b/src/name.rs @@ -20,6 +20,9 @@ use untrusted; #[cfg(feature = "std")] 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. @@ -148,6 +151,27 @@ pub fn verify_cert_dns_name(cert: &super::EndEntityCert, }) } + +#[cfg(feature = "std")] +pub fn list_cert_dns_names<'names>(cert: &super::EndEntityCert<'names>) + -> Result>, 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) { + Ok(name) => names.borrow_mut().push(name), + Err(()) => { /* keep going */ }, + }; + }, + _ => () + } + NameIteration::KeepGoing + }).map(|_| names.into_inner()) +} + // https://tools.ietf.org/html/rfc5280#section-4.2.1.10 pub fn check_name_constraints<'a>(input: Option<&mut untrusted::Reader<'a>>, subordinate_certs: &Cert) @@ -370,10 +394,10 @@ enum NameIteration { Stop(Result<(), Error>) } -fn iterate_names(subject: untrusted::Input, - subject_alt_name: Option, +fn iterate_names<'input>(subject: untrusted::Input<'input>, + subject_alt_name: Option>, result_if_never_stopped_early: Result<(), Error>, - f: &Fn(GeneralName) -> NameIteration) -> Result<(), Error> { + f: &Fn(GeneralName<'input>) -> NameIteration) -> Result<(), Error> { match subject_alt_name { Some(subject_alt_name) => { let mut subject_alt_name = untrusted::Reader::new(subject_alt_name); diff --git a/src/webpki.rs b/src/webpki.rs index f9f1dcc9..3012e878 100644 --- a/src/webpki.rs +++ b/src/webpki.rs @@ -248,6 +248,19 @@ impl <'a> EndEntityCert<'a> { signed_data::verify_signature(signature_alg, self.inner.spki, msg, signature) } + + /// Returns a list of the DNS names provided in the subject alternative names extension + /// + /// This function must not be used to implement custom DNS name verification. + /// Verification functions are already provided as `verify_is_valid_for_dns_name` + /// and `verify_is_valid_for_at_least_one_dns_name`. + /// + /// Requires the `std` default feature; i.e. this isn't available in + /// `#![no_std]` configurations. + #[cfg(feature = "std")] + pub fn dns_names(&self) -> Result>, Error> { + name::list_cert_dns_names(&self) + } } /// A trust anchor (a.k.a. root CA). diff --git a/tests/integration.rs b/tests/integration.rs index 8dc919b3..99c21cfa 100644 --- a/tests/integration.rs +++ b/tests/integration.rs @@ -130,3 +130,114 @@ fn time_constructor() { let _ = webpki::Time::try_from(std::time::SystemTime::now()).unwrap(); } + +#[cfg(feature = "std")] +#[test] +pub fn list_netflix_names() +{ + let ee = include_bytes!("netflix/ee.der"); + + expect_cert_dns_names(ee, &[ + "account.netflix.com", + "ca.netflix.com", + "netflix.ca", + "netflix.com", + "signup.netflix.com", + "www.netflix.ca", + "www1.netflix.com", + "www2.netflix.com", + "www3.netflix.com", + "develop-stage.netflix.com", + "release-stage.netflix.com", + "www.netflix.com", + ]); +} + +#[cfg(feature = "std")] +#[test] +pub fn invalid_subject_alt_names() +{ + // same as netflix ee certificate, but with the last name in the list + // changed to 'www.netflix:com' + let data = include_bytes!("misc/invalid_subject_alternative_name.der"); + + expect_cert_dns_names(data, &[ + "account.netflix.com", + "ca.netflix.com", + "netflix.ca", + "netflix.com", + "signup.netflix.com", + "www.netflix.ca", + "www1.netflix.com", + "www2.netflix.com", + "www3.netflix.com", + "develop-stage.netflix.com", + "release-stage.netflix.com", + // NOT 'www.netflix:com' + ]); +} + +#[cfg(feature = "std")] +#[test] +pub fn wildcard_subject_alternative_names() +{ + // same as netflix ee certificate, but with the last name in the list + // changed to 'ww*.netflix:com' + let data = include_bytes!("misc/dns_names_and_wildcards.der"); + + expect_cert_dns_names(data, &[ + "account.netflix.com", + // NOT "c*.netflix.com", + "netflix.ca", + "netflix.com", + "signup.netflix.com", + "www.netflix.ca", + "www1.netflix.com", + "www2.netflix.com", + "www3.netflix.com", + "develop-stage.netflix.com", + "release-stage.netflix.com", + "www.netflix.com" + ]); +} + +#[cfg(feature = "std")] +fn expect_cert_dns_names(data: &[u8], expected_names: &[&str]) +{ + use std::iter::FromIterator; + + let input = untrusted::Input::from(data); + let cert = webpki::EndEntityCert::from(input) + .expect("should parse end entity certificate correctly"); + + let expected_names = + std::collections::HashSet::from_iter(expected_names.iter().cloned()); + + let mut actual_names = cert.dns_names() + .expect("should get all DNS names correctly for end entity cert"); + + // Ensure that converting the list to a set doesn't throw away + // any duplicates that aren't supposed to be there + assert_eq!(actual_names.len(), expected_names.len()); + + let actual_names: std::collections::HashSet<&str> = actual_names.drain(..).map(|name| { + name.into() + }).collect(); + + assert_eq!(actual_names, expected_names); +} + +#[cfg(feature = "std")] +#[test] +pub fn no_subject_alt_names() +{ + let data = include_bytes!("misc/no_subject_alternative_name.der"); + + let input = untrusted::Input::from(data); + let cert = webpki::EndEntityCert::from(input) + .expect("should parse end entity certificate correctly"); + + let names = cert.dns_names().expect("we should get a result even without subjectAltNames"); + + assert!(names.is_empty()); +} diff --git a/tests/misc/dns_names_and_wildcards.der b/tests/misc/dns_names_and_wildcards.der new file mode 100644 index 0000000000000000000000000000000000000000..4c9713dd738e7fbb0492a82d949cb71bb199e8f8 GIT binary patch literal 1772 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 NI)mDO*4otA`2b#TM-~78 literal 0 HcmV?d00001