| // 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<Option<untrusted::Input<'b>>, 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<untrusted::Input>, |
| excluded_subtrees: Option<untrusted::Input>, |
| ) -> 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<untrusted::Input>, |
| ) -> 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<GeneralName<'b>, 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<untrusted::Input>, |
| 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<GeneralName<'a>, 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) |
| } |