use super::{
dns_name::{self, DnsNameRef},
ip_address::{self, IpAddrRef},
name::SubjectNameRef,
};
use crate::{
cert::{Cert, EndEntityOrCa},
der,
verify_cert::Budget,
Error,
};
#[cfg(feature = "alloc")]
use {
alloc::vec::Vec,
dns_name::{GeneralDnsNameRef, WildcardDnsNameRef},
};
pub(crate) 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().as_bytes());
iterate_names(
Some(cert.subject),
cert.subject_alt_name,
Err(Error::CertNotValidForName),
&mut |name| {
if let GeneralName::DnsName(presented_id) = name {
match dns_name::presented_id_matches_reference_id(presented_id, dns_name) {
Ok(true) => return NameIteration::Stop(Ok(())),
Ok(false) | Err(Error::MalformedDnsIdentifier) => (),
Err(e) => return NameIteration::Stop(Err(e)),
}
}
NameIteration::KeepGoing
},
)
}
pub(crate) fn verify_cert_subject_name(
cert: &crate::EndEntityCert,
subject_name: SubjectNameRef,
) -> Result<(), Error> {
let ip_address = match subject_name {
SubjectNameRef::DnsName(dns_name) => return verify_cert_dns_name(cert, dns_name),
SubjectNameRef::IpAddress(IpAddrRef::V4(_, ref ip_address_octets)) => {
untrusted::Input::from(ip_address_octets)
}
SubjectNameRef::IpAddress(IpAddrRef::V6(_, ref ip_address_octets)) => {
untrusted::Input::from(ip_address_octets)
}
};
iterate_names(
None,
cert.inner().subject_alt_name,
Err(Error::CertNotValidForName),
&mut |name| {
if let GeneralName::IpAddress(presented_id) = name {
match ip_address::presented_id_matches_reference_id(presented_id, ip_address) {
true => return NameIteration::Stop(Ok(())),
false => (),
}
}
NameIteration::KeepGoing
},
)
}
pub(crate) fn check_name_constraints(
input: Option<&mut untrusted::Reader>,
subordinate_certs: &Cert,
budget: &mut Budget,
) -> 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);
}
der::expect_tag_and_get_value(inner, subtrees_tag).map(Some)
}
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(
Some(child.subject),
child.subject_alt_name,
Ok(()),
&mut |name| {
check_presented_id_conforms_to_constraints(
name,
permitted_subtrees,
excluded_subtrees,
budget,
)
},
)?;
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>,
budget: &mut Budget,
) -> NameIteration {
match check_presented_id_conforms_to_constraints_in_subtree(
name,
Subtrees::PermittedSubtrees,
permitted_subtrees,
budget,
) {
stop @ NameIteration::Stop(..) => {
return stop;
}
NameIteration::KeepGoing => (),
};
check_presented_id_conforms_to_constraints_in_subtree(
name,
Subtrees::ExcludedSubtrees,
excluded_subtrees,
budget,
)
}
#[derive(Clone, Copy)]
enum Subtrees {
PermittedSubtrees,
ExcludedSubtrees,
}
fn check_presented_id_conforms_to_constraints_in_subtree(
name: GeneralName,
subtrees: Subtrees,
constraints: Option<untrusted::Input>,
budget: &mut Budget,
) -> 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;
while !constraints.at_end() {
if let Err(e) = budget.consume_name_constraint_comparison() {
return NameIteration::Stop(Err(e));
}
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, GeneralName::from_der)
}
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)
}
(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)
}
(GeneralName::Unsupported(name_tag), GeneralName::Unsupported(base_tag))
if name_tag == base_tag =>
{
Err(Error::NameConstraintViolation)
}
_ => {
continue;
}
};
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 has_permitted_subtrees_mismatch && !has_permitted_subtrees_match {
NameIteration::Stop(Err(Error::NameConstraintViolation))
} else {
NameIteration::KeepGoing
}
}
fn presented_directory_name_matches_constraint(
_name: untrusted::Input,
_constraint: untrusted::Input,
subtrees: Subtrees,
) -> bool {
match subtrees {
Subtrees::PermittedSubtrees => false,
Subtrees::ExcludedSubtrees => true,
}
}
#[derive(Clone, Copy)]
enum NameIteration {
KeepGoing,
Stop(Result<(), Error>),
}
fn iterate_names<'names>(
subject: Option<untrusted::Input<'names>>,
subject_alt_name: Option<untrusted::Input<'names>>,
result_if_never_stopped_early: Result<(), Error>,
f: &mut impl FnMut(GeneralName<'names>) -> NameIteration,
) -> Result<(), Error> {
if let Some(subject_alt_name) = subject_alt_name {
let mut subject_alt_name = untrusted::Reader::new(subject_alt_name);
while !subject_alt_name.at_end() {
let name = GeneralName::from_der(&mut subject_alt_name)?;
match f(name) {
NameIteration::Stop(result) => {
return result;
}
NameIteration::KeepGoing => (),
}
}
}
if let Some(subject) = subject {
match f(GeneralName::DirectoryName(subject)) {
NameIteration::Stop(result) => return result,
NameIteration::KeepGoing => (),
};
}
result_if_never_stopped_early
}
#[cfg(feature = "alloc")]
pub(crate) fn list_cert_dns_names<'names>(
cert: &'names crate::EndEntityCert<'names>,
) -> Result<impl Iterator<Item = GeneralDnsNameRef<'names>>, Error> {
let cert = &cert.inner();
let mut names = Vec::new();
iterate_names(
Some(cert.subject),
cert.subject_alt_name,
Ok(()),
&mut |name| {
if let GeneralName::DnsName(presented_id) = name {
let dns_name = DnsNameRef::try_from_ascii(presented_id.as_slice_less_safe())
.map(GeneralDnsNameRef::DnsName)
.or_else(|_| {
WildcardDnsNameRef::try_from_ascii(presented_id.as_slice_less_safe())
.map(GeneralDnsNameRef::Wildcard)
});
if let Ok(name) = dns_name {
names.push(name)
}
}
NameIteration::KeepGoing
},
)
.map(|_| names.into_iter())
}
#[derive(Clone, Copy)]
enum GeneralName<'a> {
DnsName(untrusted::Input<'a>),
DirectoryName(untrusted::Input<'a>),
IpAddress(untrusted::Input<'a>),
Unsupported(u8),
}
impl<'a> GeneralName<'a> {
fn from_der(input: &mut untrusted::Reader<'a>) -> Result<Self, 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)?;
Ok(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),
})
}
}