// Copyright 2015 Brian Smith. // // Permission to use, copy, modify, and/or distribute this software for any // purpose with or without fee is hereby granted, provided that the above // copyright notice and this permission notice appear in all copies. // // THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHORS DISCLAIM ALL WARRANTIES // WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF // MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR // ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES // WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN // ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF // OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. use super::{ dns_name::{self, DnsNameRef}, ip_address, }; use crate::{ cert::{Cert, EndEntityOrCa}, der, equal, Error, }; pub fn verify_cert_dns_name( cert: &crate::EndEntityCert, dns_name: DnsNameRef, ) -> Result<(), Error> { let cert = cert.inner(); let dns_name = untrusted::Input::from(dns_name.as_ref()); iterate_names( cert.subject, cert.subject_alt_name, Err(Error::CertNotValidForName), &|name| { match name { GeneralName::DnsName(presented_id) => { match dns_name::presented_id_matches_reference_id(presented_id, dns_name) { Some(true) => { return NameIteration::Stop(Ok(())); } Some(false) => (), None => { return NameIteration::Stop(Err(Error::BadDer)); } } } _ => (), } NameIteration::KeepGoing }, ) } // https://tools.ietf.org/html/rfc5280#section-4.2.1.10 pub fn check_name_constraints( input: Option<&mut untrusted::Reader>, subordinate_certs: &Cert, ) -> Result<(), Error> { let input = match input { Some(input) => input, None => { return Ok(()); } }; fn parse_subtrees<'b>( inner: &mut untrusted::Reader<'b>, subtrees_tag: der::Tag, ) -> Result>, Error> { if !inner.peek(subtrees_tag.into()) { return Ok(None); } let subtrees = der::nested(inner, subtrees_tag, Error::BadDer, |tagged| { der::expect_tag_and_get_value(tagged, der::Tag::Sequence) })?; Ok(Some(subtrees)) } let permitted_subtrees = parse_subtrees(input, der::Tag::ContextSpecificConstructed0)?; let excluded_subtrees = parse_subtrees(input, der::Tag::ContextSpecificConstructed1)?; let mut child = subordinate_certs; loop { iterate_names(child.subject, child.subject_alt_name, Ok(()), &|name| { check_presented_id_conforms_to_constraints(name, permitted_subtrees, excluded_subtrees) })?; child = match child.ee_or_ca { EndEntityOrCa::Ca(child_cert) => child_cert, EndEntityOrCa::EndEntity => { break; } }; } Ok(()) } fn check_presented_id_conforms_to_constraints( name: GeneralName, permitted_subtrees: Option, excluded_subtrees: Option, ) -> NameIteration { match check_presented_id_conforms_to_constraints_in_subtree( name, Subtrees::PermittedSubtrees, permitted_subtrees, ) { stop @ NameIteration::Stop(..) => { return stop; } NameIteration::KeepGoing => (), }; check_presented_id_conforms_to_constraints_in_subtree( name, Subtrees::ExcludedSubtrees, excluded_subtrees, ) } #[derive(Clone, Copy)] enum Subtrees { PermittedSubtrees, ExcludedSubtrees, } fn check_presented_id_conforms_to_constraints_in_subtree( name: GeneralName, subtrees: Subtrees, constraints: Option, ) -> NameIteration { let mut constraints = match constraints { Some(constraints) => untrusted::Reader::new(constraints), None => { return NameIteration::KeepGoing; } }; let mut has_permitted_subtrees_match = false; let mut has_permitted_subtrees_mismatch = false; loop { // http://tools.ietf.org/html/rfc5280#section-4.2.1.10: "Within this // profile, the minimum and maximum fields are not used with any name // forms, thus, the minimum MUST be zero, and maximum MUST be absent." // // Since the default value isn't allowed to be encoded according to the // DER encoding rules for DEFAULT, this is equivalent to saying that // neither minimum or maximum must be encoded. fn general_subtree<'b>( input: &mut untrusted::Reader<'b>, ) -> Result, Error> { let general_subtree = der::expect_tag_and_get_value(input, der::Tag::Sequence)?; general_subtree.read_all(Error::BadDer, general_name) } let base = match general_subtree(&mut constraints) { Ok(base) => base, Err(err) => { return NameIteration::Stop(Err(err)); } }; let matches = match (name, base) { (GeneralName::DnsName(name), GeneralName::DnsName(base)) => { dns_name::presented_id_matches_constraint(name, base).ok_or(Error::BadDer) } (GeneralName::DirectoryName(name), GeneralName::DirectoryName(base)) => Ok( presented_directory_name_matches_constraint(name, base, subtrees), ), (GeneralName::IpAddress(name), GeneralName::IpAddress(base)) => { ip_address::presented_id_matches_constraint(name, base) } // RFC 4280 says "If a name constraints extension that is marked as // critical imposes constraints on a particular name form, and an // instance of that name form appears in the subject field or // subjectAltName extension of a subsequent certificate, then the // application MUST either process the constraint or reject the // certificate." Later, the CABForum agreed to support non-critical // constraints, so it is important to reject the cert without // considering whether the name constraint it critical. (GeneralName::Unsupported(name_tag), GeneralName::Unsupported(base_tag)) if name_tag == base_tag => { Err(Error::NameConstraintViolation) } _ => Ok(false), }; match (subtrees, matches) { (Subtrees::PermittedSubtrees, Ok(true)) => { has_permitted_subtrees_match = true; } (Subtrees::PermittedSubtrees, Ok(false)) => { has_permitted_subtrees_mismatch = true; } (Subtrees::ExcludedSubtrees, Ok(true)) => { return NameIteration::Stop(Err(Error::NameConstraintViolation)); } (Subtrees::ExcludedSubtrees, Ok(false)) => (), (_, Err(err)) => { return NameIteration::Stop(Err(err)); } } if constraints.at_end() { break; } } if has_permitted_subtrees_mismatch && !has_permitted_subtrees_match { // If there was any entry of the given type in permittedSubtrees, then // it required that at least one of them must match. Since none of them // did, we have a failure. NameIteration::Stop(Err(Error::NameConstraintViolation)) } else { NameIteration::KeepGoing } } // TODO: document this. fn presented_directory_name_matches_constraint( name: untrusted::Input, constraint: untrusted::Input, subtrees: Subtrees, ) -> bool { match subtrees { Subtrees::PermittedSubtrees => equal(name, constraint), Subtrees::ExcludedSubtrees => true, } } #[derive(Clone, Copy)] enum NameIteration { KeepGoing, Stop(Result<(), Error>), } fn iterate_names( subject: untrusted::Input, subject_alt_name: Option, result_if_never_stopped_early: Result<(), Error>, f: &dyn Fn(GeneralName) -> NameIteration, ) -> Result<(), Error> { match subject_alt_name { Some(subject_alt_name) => { let mut subject_alt_name = untrusted::Reader::new(subject_alt_name); // https://bugzilla.mozilla.org/show_bug.cgi?id=1143085: An empty // subjectAltName is not legal, but some certificates have an empty // subjectAltName. Since we don't support CN-IDs, the certificate // will be rejected either way, but checking `at_end` before // attempting to parse the first entry allows us to return a better // error code. while !subject_alt_name.at_end() { let name = general_name(&mut subject_alt_name)?; match f(name) { NameIteration::Stop(result) => { return result; } NameIteration::KeepGoing => (), } } } None => (), } match f(GeneralName::DirectoryName(subject)) { NameIteration::Stop(result) => result, NameIteration::KeepGoing => result_if_never_stopped_early, } } // It is *not* valid to derive `Eq`, `PartialEq, etc. for this type. In // particular, for the types of `GeneralName`s that we don't understand, we // don't even store the value. Also, the meaning of a `GeneralName` in a name // constraint is different than the meaning of the identically-represented // `GeneralName` in other contexts. #[derive(Clone, Copy)] enum GeneralName<'a> { DnsName(untrusted::Input<'a>), DirectoryName(untrusted::Input<'a>), IpAddress(untrusted::Input<'a>), // The value is the `tag & ~(der::CONTEXT_SPECIFIC | der::CONSTRUCTED)` so // that the name constraint checking matches tags regardless of whether // those bits are set. Unsupported(u8), } fn general_name<'a>(input: &mut untrusted::Reader<'a>) -> Result, Error> { use ring::io::der::{CONSTRUCTED, CONTEXT_SPECIFIC}; #[allow(clippy::identity_op)] const OTHER_NAME_TAG: u8 = CONTEXT_SPECIFIC | CONSTRUCTED | 0; const RFC822_NAME_TAG: u8 = CONTEXT_SPECIFIC | 1; const DNS_NAME_TAG: u8 = CONTEXT_SPECIFIC | 2; const X400_ADDRESS_TAG: u8 = CONTEXT_SPECIFIC | CONSTRUCTED | 3; const DIRECTORY_NAME_TAG: u8 = CONTEXT_SPECIFIC | CONSTRUCTED | 4; const EDI_PARTY_NAME_TAG: u8 = CONTEXT_SPECIFIC | CONSTRUCTED | 5; const UNIFORM_RESOURCE_IDENTIFIER_TAG: u8 = CONTEXT_SPECIFIC | 6; const IP_ADDRESS_TAG: u8 = CONTEXT_SPECIFIC | 7; const REGISTERED_ID_TAG: u8 = CONTEXT_SPECIFIC | 8; let (tag, value) = der::read_tag_and_get_value(input)?; let name = match tag { DNS_NAME_TAG => GeneralName::DnsName(value), DIRECTORY_NAME_TAG => GeneralName::DirectoryName(value), IP_ADDRESS_TAG => GeneralName::IpAddress(value), OTHER_NAME_TAG | RFC822_NAME_TAG | X400_ADDRESS_TAG | EDI_PARTY_NAME_TAG | UNIFORM_RESOURCE_IDENTIFIER_TAG | REGISTERED_ID_TAG => GeneralName::Unsupported(tag & !(CONTEXT_SPECIFIC | CONSTRUCTED)), _ => return Err(Error::BadDer), }; Ok(name) }