use crate::{
cert::{self, Cert, EndEntityOrCA},
der, name, signed_data, time, Error, SignatureAlgorithm, TrustAnchor,
};
pub fn build_chain(
required_eku_if_present: KeyPurposeId, supported_sig_algs: &[&SignatureAlgorithm],
trust_anchors: &[TrustAnchor], intermediate_certs: &[&[u8]], cert: &Cert, time: time::Time,
sub_ca_count: usize,
) -> Result<(), Error> {
let used_as_ca = used_as_ca(&cert.ee_or_ca);
check_issuer_independent_properties(
cert,
time,
used_as_ca,
sub_ca_count,
required_eku_if_present,
)?;
match used_as_ca {
UsedAsCA::Yes => {
const MAX_SUB_CA_COUNT: usize = 6;
if sub_ca_count >= MAX_SUB_CA_COUNT {
return Err(Error::UnknownIssuer);
}
},
UsedAsCA::No => {
assert_eq!(0, sub_ca_count);
},
}
match loop_while_non_fatal_error(trust_anchors, |trust_anchor: &TrustAnchor| {
let trust_anchor_subject = untrusted::Input::from(trust_anchor.subject);
if cert.issuer != trust_anchor_subject {
return Err(Error::UnknownIssuer);
}
let name_constraints = trust_anchor.name_constraints.map(untrusted::Input::from);
untrusted::read_all_optional(name_constraints, Error::BadDER, |value| {
name::check_name_constraints(value, &cert)
})?;
let trust_anchor_spki = untrusted::Input::from(trust_anchor.spki);
check_signatures(supported_sig_algs, cert, trust_anchor_spki)?;
Ok(())
}) {
Ok(()) => {
return Ok(());
},
Err(..) => {
},
}
loop_while_non_fatal_error(intermediate_certs, |cert_der| {
let potential_issuer =
cert::parse_cert(untrusted::Input::from(*cert_der), EndEntityOrCA::CA(&cert))?;
if potential_issuer.subject != cert.issuer {
return Err(Error::UnknownIssuer);
}
let mut prev = cert;
loop {
if potential_issuer.spki.value() == prev.spki.value()
&& potential_issuer.subject == prev.subject
{
return Err(Error::UnknownIssuer);
}
match &prev.ee_or_ca {
&EndEntityOrCA::EndEntity => {
break;
},
&EndEntityOrCA::CA(child_cert) => {
prev = child_cert;
},
}
}
untrusted::read_all_optional(potential_issuer.name_constraints, Error::BadDER, |value| {
name::check_name_constraints(value, &cert)
})?;
let next_sub_ca_count = match used_as_ca {
UsedAsCA::No => sub_ca_count,
UsedAsCA::Yes => sub_ca_count + 1,
};
build_chain(
required_eku_if_present,
supported_sig_algs,
trust_anchors,
intermediate_certs,
&potential_issuer,
time,
next_sub_ca_count,
)
})
}
fn check_signatures(
supported_sig_algs: &[&SignatureAlgorithm], cert_chain: &Cert,
trust_anchor_key: untrusted::Input,
) -> Result<(), Error> {
let mut spki_value = trust_anchor_key;
let mut cert = cert_chain;
loop {
signed_data::verify_signed_data(supported_sig_algs, spki_value, &cert.signed_data)?;
match &cert.ee_or_ca {
&EndEntityOrCA::CA(child_cert) => {
spki_value = cert.spki.value();
cert = child_cert;
},
&EndEntityOrCA::EndEntity => {
break;
},
}
}
Ok(())
}
fn check_issuer_independent_properties(
cert: &Cert, time: time::Time, used_as_ca: UsedAsCA, sub_ca_count: usize,
required_eku_if_present: KeyPurposeId,
) -> Result<(), Error> {
cert.validity
.read_all(Error::BadDER, |value| check_validity(value, time))?;
untrusted::read_all_optional(cert.basic_constraints, Error::BadDER, |value| {
check_basic_constraints(value, used_as_ca, sub_ca_count)
})?;
untrusted::read_all_optional(cert.eku, Error::BadDER, |value| {
check_eku(value, required_eku_if_present)
})?;
Ok(())
}
fn check_validity(input: &mut untrusted::Reader, time: time::Time) -> Result<(), Error> {
let not_before = der::time_choice(input)?;
let not_after = der::time_choice(input)?;
if not_before > not_after {
return Err(Error::InvalidCertValidity);
}
if time < not_before {
return Err(Error::CertNotValidYet);
}
if time > not_after {
return Err(Error::CertExpired);
}
Ok(())
}
#[derive(Clone, Copy)]
enum UsedAsCA {
Yes,
No,
}
fn used_as_ca(ee_or_ca: &EndEntityOrCA) -> UsedAsCA {
match ee_or_ca {
&EndEntityOrCA::EndEntity => UsedAsCA::No,
&EndEntityOrCA::CA(..) => UsedAsCA::Yes,
}
}
fn check_basic_constraints(
input: Option<&mut untrusted::Reader>, used_as_ca: UsedAsCA, sub_ca_count: usize,
) -> Result<(), Error> {
let (is_ca, path_len_constraint) = match input {
Some(input) => {
let is_ca = der::optional_boolean(input)?;
let path_len_constraint = if !input.at_end() {
let value = der::small_nonnegative_integer(input)?;
Some(value as usize)
} else {
None
};
(is_ca, path_len_constraint)
},
None => (false, None),
};
match (used_as_ca, is_ca, path_len_constraint) {
(UsedAsCA::No, true, _) => Err(Error::CAUsedAsEndEntity),
(UsedAsCA::Yes, false, _) => Err(Error::EndEntityUsedAsCA),
(UsedAsCA::Yes, true, Some(len)) if sub_ca_count > len =>
Err(Error::PathLenConstraintViolated),
_ => Ok(()),
}
}
#[derive(Clone, Copy)]
pub struct KeyPurposeId {
oid_value: untrusted::Input<'static>,
}
pub static EKU_SERVER_AUTH: KeyPurposeId = KeyPurposeId {
oid_value: untrusted::Input::from(&[(40 * 1) + 3, 6, 1, 5, 5, 7, 3, 1]),
};
pub static EKU_CLIENT_AUTH: KeyPurposeId = KeyPurposeId {
oid_value: untrusted::Input::from(&[(40 * 1) + 3, 6, 1, 5, 5, 7, 3, 2]),
};
pub static EKU_OCSP_SIGNING: KeyPurposeId = KeyPurposeId {
oid_value: untrusted::Input::from(&[(40 * 1) + 3, 6, 1, 5, 5, 7, 3, 9]),
};
fn check_eku(
input: Option<&mut untrusted::Reader>, required_eku_if_present: KeyPurposeId,
) -> Result<(), Error> {
match input {
Some(input) => {
loop {
let value = der::expect_tag_and_get_value(input, der::Tag::OID)?;
if value == required_eku_if_present.oid_value {
input.skip_to_end();
break;
}
if input.at_end() {
return Err(Error::RequiredEKUNotFound);
}
}
Ok(())
},
None => {
if required_eku_if_present.oid_value == EKU_OCSP_SIGNING.oid_value {
return Err(Error::RequiredEKUNotFound);
}
Ok(())
},
}
}
fn loop_while_non_fatal_error<V, F>(values: V, f: F) -> Result<(), Error>
where
V: IntoIterator,
F: Fn(V::Item) -> Result<(), Error>,
{
for v in values {
match f(v) {
Ok(()) => {
return Ok(());
},
Err(..) => {
},
}
}
Err(Error::UnknownIssuer)
}