use std::convert::TryFrom;
use pki_types::CertificateDer;
use rcgen::{BasicConstraints, Certificate, CertificateParams, DnType, IsCa, KeyUsagePurpose};
use webpki::{extract_trust_anchor, EndEntityCert, Error, KeyUsage, SubjectNameRef, Time};
use x509_parser::extensions::{GeneralName, NameConstraints as X509ParserNameConstraints};
use x509_parser::prelude::FromDer;
use webpki_roots::TLS_SERVER_ROOTS;
#[test]
fn name_constraints() {
for name_constraints in TLS_SERVER_ROOTS
.iter()
.filter_map(|ta| ta.name_constraints.as_ref())
{
let time = Time::from_seconds_since_unix_epoch(0x40000000); let test_case = ConstraintTest::new(name_constraints.as_ref());
let trust_anchors = &[extract_trust_anchor(&test_case.trust_anchor).unwrap()];
for permitted_ee in test_case.permitted_certs {
webpki::EndEntityCert::try_from(&permitted_ee)
.unwrap()
.verify_for_usage(
ALL_ALGORITHMS,
trust_anchors,
&[],
time,
KeyUsage::server_auth(),
None,
)
.unwrap();
}
for forbidden_ee in test_case.forbidden_certs {
let result = webpki::EndEntityCert::try_from(&forbidden_ee)
.unwrap()
.verify_for_usage(
ALL_ALGORITHMS,
trust_anchors,
&[],
time,
KeyUsage::server_auth(),
None,
);
assert!(matches!(result, Err(Error::NameConstraintViolation)));
}
}
}
struct ConstraintTest {
trust_anchor: CertificateDer<'static>,
permitted_certs: Vec<CertificateDer<'static>>,
forbidden_certs: Vec<CertificateDer<'static>>,
}
impl ConstraintTest {
fn new(webpki_name_constraints: &[u8]) -> Self {
let mut trust_anchor = CertificateParams::new([]);
trust_anchor
.distinguished_name
.push(DnType::CommonName, "Name Constraint Test CA");
trust_anchor.is_ca = IsCa::Ca(BasicConstraints::Unconstrained);
trust_anchor.key_usages = vec![
KeyUsagePurpose::KeyCertSign,
KeyUsagePurpose::DigitalSignature,
];
let name_constraints = rcgen_name_constraints(webpki_name_constraints);
trust_anchor.name_constraints = Some(name_constraints.clone());
let trust_anchor = Certificate::from_params(trust_anchor).unwrap();
let certs_for_subtrees = |suffix| {
name_constraints
.permitted_subtrees
.iter()
.filter_map(|subtree| match subtree {
rcgen::GeneralSubtree::DnsName(dns_name) => Some(rcgen_ee_for_name(
format!("valid{}{}", dns_name, suffix),
&trust_anchor,
)),
_ => None,
})
.collect()
};
Self {
trust_anchor: CertificateDer::from(trust_anchor.serialize_der().unwrap()),
permitted_certs: certs_for_subtrees(""),
forbidden_certs: certs_for_subtrees(".invalid"),
}
}
}
fn rcgen_ee_for_name(name: String, issuer: &Certificate) -> CertificateDer<'static> {
let mut ee = CertificateParams::new(vec![name.clone()]);
ee.distinguished_name.push(DnType::CommonName, name);
ee.is_ca = IsCa::NoCa;
CertificateDer::from(
Certificate::from_params(ee)
.unwrap()
.serialize_der_with_signer(issuer)
.unwrap(),
)
}
fn rcgen_name_constraints(der: &[u8]) -> rcgen::NameConstraints {
let wrapped_der = yasna::construct_der(|writer| {
writer.write_sequence(|writer| {
writer.next().write_der(der);
})
});
let (trailing, constraints) = X509ParserNameConstraints::from_der(&wrapped_der).unwrap();
assert!(
trailing.is_empty(),
"unexpected trailing DER in name constraint"
);
assert!(
constraints.permitted_subtrees.is_some(),
"empty permitted subtrees in constraints"
);
assert!(constraints.excluded_subtrees.is_none());
let permitted_subtrees = match constraints.permitted_subtrees {
None => Vec::default(),
Some(subtrees) => subtrees
.iter()
.map(|subtree| match &subtree.base {
GeneralName::DNSName(base) => rcgen::GeneralSubtree::DnsName(base.to_string()),
name => panic!("unexpected subtree base general name type: {}", name),
})
.collect(),
};
rcgen::NameConstraints {
permitted_subtrees,
excluded_subtrees: Vec::default(),
}
}
#[test]
fn tubitak_name_constraint_works() {
let root = CertificateDer::from(&include_bytes!("data/tubitak/root.der")[..]);
let inter = CertificateDer::from(&include_bytes!("data/tubitak/inter.der")[..]);
let subj = CertificateDer::from(&include_bytes!("data/tubitak/subj.der")[..]);
let roots = [extract_trust_anchor(&root).unwrap().to_owned()];
let now = Time::from_seconds_since_unix_epoch(1493668479);
let cert = EndEntityCert::try_from(&subj).unwrap();
cert.verify_for_usage(
ALL_ALGORITHMS,
&roots,
&[inter, root],
now,
KeyUsage::server_auth(),
None,
)
.unwrap();
let subject = SubjectNameRef::try_from_ascii_str("testssl.kamusm.gov.tr").unwrap();
cert.verify_is_valid_for_subject_name(subject).unwrap();
}
static ALL_ALGORITHMS: &[&dyn webpki::SignatureVerificationAlgorithm] = &[
webpki::ECDSA_P256_SHA256,
webpki::ECDSA_P256_SHA384,
webpki::ECDSA_P384_SHA256,
webpki::ECDSA_P384_SHA384,
webpki::RSA_PKCS1_2048_8192_SHA256,
webpki::RSA_PKCS1_2048_8192_SHA384,
webpki::RSA_PKCS1_2048_8192_SHA512,
webpki::RSA_PKCS1_3072_8192_SHA384,
webpki::RSA_PSS_2048_8192_SHA256_LEGACY_KEY,
webpki::RSA_PSS_2048_8192_SHA384_LEGACY_KEY,
webpki::RSA_PSS_2048_8192_SHA512_LEGACY_KEY,
];