Skip to content

Commit

Permalink
wip: further test refinement
Browse files Browse the repository at this point in the history
  • Loading branch information
cpu committed Sep 11, 2023
1 parent 4192529 commit 2831ffc
Showing 1 changed file with 161 additions and 165 deletions.
326 changes: 161 additions & 165 deletions tests/generic.rs
Original file line number Diff line number Diff line change
Expand Up @@ -111,219 +111,215 @@ mod test_csr_exts {
rcgen::ExtendedKeyUsagePurpose::ServerAuth,
rcgen::ExtendedKeyUsagePurpose::ClientAuth,
];
let permitted_subtree_dns = "example.com";
let excluded_subtree_dns = "example.org";
params.name_constraints = Some(rcgen::NameConstraints {
permitted_subtrees: vec![GeneralSubtree::DnsName("example.com".into())],
excluded_subtrees: vec![GeneralSubtree::DnsName("example.org".into())],
permitted_subtrees: vec![GeneralSubtree::DnsName(permitted_subtree_dns.into())],
excluded_subtrees: vec![GeneralSubtree::DnsName(excluded_subtree_dns.into())],
});
let distribution_point_uri = "http://example.com";
params.crl_distribution_points = vec![CrlDistributionPoint {
uris: vec!["http://example.com".into()],
uris: vec![distribution_point_uri.into()],
}];
let test_cert = Certificate::from_params(params).unwrap();
let test_cert_der = test_cert.serialize_der().unwrap();

// We should be able to parse it and find the expected extensions present.
let (_, x509_test_cert) = X509Certificate::from_der(&test_cert_der).unwrap();

macro_rules! assert_x509_ext {
($oid:ident, $critical:expr, $pattern:pat $(if $guard:expr)? $(,)?, $parsed_expr:expr) => {{
let ext = x509_test_cert
let cert = Certificate::from_params(params).unwrap();
let cert_der = cert.serialize_der().unwrap();
let csr_der = cert.serialize_request_der().unwrap();

// Parse the self-signed test certificate, and a CSR generated from the test certificate with x509-parser.
let (_, x509_cert) = X509Certificate::from_der(&cert_der).unwrap();
let (_, x509_csr) = X509CertificationRequest::from_der(&csr_der).unwrap();

// Helper macro that tests both the parsed cert and CSR have an extension with specific
// properties.
macro_rules! assert_paired_ext {
($oid:ident, $critical:expr, $pattern:pat, $parsed_expr:expr) => {{
// 1. Find the extension in the certificate.
let cert_ext = x509_cert
.get_extension_unique(&x509_parser::oid_registry::$oid)
.expect(concat!("malformed ", stringify!($oid)))
.expect(concat!("missing ", stringify!($oid)));
assert_eq!(ext.critical, $critical);

match ext.parsed_extension() {
$pattern $(if $guard)? => $parsed_expr,
_ => panic!("unexpected extension"),
}
.expect(concat!("malformed cert ext for ", stringify!($oid)))
.expect(concat!("missing cert ext for ", stringify!($oid)));

// 2. Verify criticality.
assert_eq!(
cert_ext.critical, $critical,
concat!("wrong criticality for ", stringify!($oid))
);

// 3. Verify the parsed representation of the extension.
match cert_ext.parsed_extension() {
$pattern => $parsed_expr,
_ => panic!(concat!(
"unexpected parsed extension for ",
stringify!($oid)
)),
};

// 4. Verify the parsed CSR has the extension, and that it has the correct
// parsed representation.
x509_csr
.requested_extensions()
.expect("missing CSR requested extensions")
.find_map(|ext| match ext {
$pattern => Some($parsed_expr),
_ => None,
})
.expect(concat!("missing CSR extension for ", stringify!($oid)))
}};
}

// Check SAN is present, not critical, and has the expected DNSName.
let assert_san = |san: &x509_parser::prelude::SubjectAlternativeName| {
san.general_names
.iter()
.find(|name| match name {
x509_parser::prelude::GeneralName::DNSName(name) => name == &san_name,
_ => false,
})
.expect("missing expected SAN");
};
assert_x509_ext!(
assert_paired_ext!(
OID_X509_EXT_SUBJECT_ALT_NAME,
false,
ParsedExtension::SubjectAlternativeName(san),
assert_san(san)
{
san.general_names
.iter()
.find(|name| match name {
x509_parser::prelude::GeneralName::DNSName(name) => name == &san_name,
_ => false,
})
.expect("missing expected SAN");
}
);

// Check basic constraints are present, critical, and have the expected path len constraint.
let assert_bc = |bc: &x509_parser::prelude::BasicConstraints| {
assert!(bc.ca);
assert_eq!(
bc.path_len_constraint.expect("missing path len constraint"),
path_len_constraint as u32
)
};
assert_x509_ext!(
assert_paired_ext!(
OID_X509_EXT_BASIC_CONSTRAINTS,
true,
ParsedExtension::BasicConstraints(bc),
assert_bc(bc)
{
assert!(bc.ca);
assert_eq!(
bc.path_len_constraint.expect("missing path len constraint"),
path_len_constraint as u32
);
}
);

// Check name constraints are present, critical and non-empty.
let assert_nc = |nc: &x509_parser::prelude::NameConstraints| {
assert!(!nc
.permitted_subtrees
.clone()
.expect("missing permitted subtrees")
.is_empty());
assert!(!nc
.excluded_subtrees
.clone()
.expect("missing excluded subtrees")
.is_empty());
// TODO: find the subtree base names we expect.
};
assert_x509_ext!(
fn assert_subtree_dns(
subtrees: Vec<x509_parser::prelude::GeneralSubtree>,
expected_dns: &str,
) {
subtrees
.iter()
.find(
|subtree|
matches!(subtree.base, x509_parser::prelude::GeneralName::DNSName(dns) if dns == expected_dns)
)
.expect("missing expected subtree URI");
}
assert_paired_ext!(
OID_X509_EXT_NAME_CONSTRAINTS,
true,
ParsedExtension::NameConstraints(name_constraints),
assert_nc(name_constraints)
{
assert_subtree_dns(
name_constraints
.permitted_subtrees
.clone()
.expect("missing permitted subtrees"),
&permitted_subtree_dns,
);
assert_subtree_dns(
name_constraints
.excluded_subtrees
.clone()
.expect("missing excluded subtrees"),
&excluded_subtree_dns,
);
}
);

// Check CRL distribution point is present and non-critical.
let assert_crl_dps = |crl_dps: &x509_parser::prelude::CRLDistributionPoints| {
assert!(!crl_dps.points.is_empty());
// TODO: find the point URI we expect.
};
assert_x509_ext!(
fn assert_crl_dps_uri(
crl_dps: &x509_parser::prelude::CRLDistributionPoints,
expected: &str,
) {
crl_dps
.iter()
.find(|dp| {
let full_names = match dp
.distribution_point
.clone()
.expect("missing distribution point name")
{
x509_parser::prelude::DistributionPointName::FullName(full_names) => {
full_names
},
_ => panic!("missing full names"),
};

full_names.iter().find(|general_name|
matches!(general_name, x509_parser::prelude::GeneralName::URI(uri) if uri == &expected)).is_some()
})
.expect("missing expected CRL distribution point URI");
}
assert_paired_ext!(
OID_X509_EXT_CRL_DISTRIBUTION_POINTS,
false,
ParsedExtension::CRLDistributionPoints(crl_dps),
assert_crl_dps(crl_dps)
assert_crl_dps_uri(crl_dps, &distribution_point_uri)
);

// Check key usage is present, and critical.
let assert_ku = |ku: &x509_parser::prelude::KeyUsage| {
assert!(ku.digital_signature());
assert!(ku.key_encipherment());
// TODO: negative assert other usages. Do an eq test...
};
assert_x509_ext!(
assert_paired_ext!(
OID_X509_EXT_KEY_USAGE,
true,
ParsedExtension::KeyUsage(key_usage),
assert_ku(key_usage)
ParsedExtension::KeyUsage(ku),
{
assert!(ku.digital_signature());
assert!(ku.key_encipherment());
assert!(!ku.non_repudiation());
assert!(!ku.key_agreement());
assert!(!ku.key_cert_sign());
assert!(!ku.encipher_only());
assert!(!ku.decipher_only());
}
);

// Check extended key usage is present, and non-critical.
let assert_eku = |eku: &x509_parser::prelude::ExtendedKeyUsage| {
assert!(eku.server_auth);
assert!(eku.client_auth);
assert!(!eku.any);
assert!(eku.other.is_empty());
assert!(!eku.code_signing);
assert!(!eku.ocsp_signing);
assert!(!eku.email_protection);
assert!(!eku.time_stamping);
};
assert_x509_ext!(
assert_paired_ext!(
OID_X509_EXT_EXTENDED_KEY_USAGE,
false,
ParsedExtension::ExtendedKeyUsage(extended_key_usage),
assert_eku(extended_key_usage)
ParsedExtension::ExtendedKeyUsage(eku),
{
assert!(eku.server_auth);
assert!(eku.client_auth);
assert!(!eku.any);
assert!(eku.other.is_empty());
assert!(!eku.code_signing);
assert!(!eku.ocsp_signing);
assert!(!eku.email_protection);
assert!(!eku.time_stamping);
}
);

// Check subject key identifier is present, non-critical, and matches the expected value.
let assert_ski = |ski: &x509_parser::prelude::KeyIdentifier| {
assert_eq!(ski.0, &test_cert.get_key_identifier());
};
assert_x509_ext!(
assert_paired_ext!(
OID_X509_EXT_SUBJECT_KEY_IDENTIFIER,
false,
ParsedExtension::SubjectKeyIdentifier(ski),
assert_ski(ski)
assert_eq!(ski.0, &cert.get_key_identifier())
);

// Check authority key identifier is present, non-critical and matches the expected value.
assert_x509_ext!(
OID_X509_EXT_AUTHORITY_KEY_IDENTIFIER,
false,
ParsedExtension::AuthorityKeyIdentifier(aki),
assert_eq!(
// We should find the AKI extension in the self-signed certificate.
let aki = x509_cert
.get_extension_unique(&x509_parser::oid_registry::OID_X509_EXT_AUTHORITY_KEY_IDENTIFIER)
.expect("malformed OID_X509_EXT_AUTHORITY_KEY_IDENTIFIER")
.expect("missing OID_X509_EXT_AUTHORITY_KEY_IDENTIFIER");
assert!(!aki.critical);
match aki.parsed_extension() {
ParsedExtension::AuthorityKeyIdentifier(aki) => assert_eq!(
aki.clone()
.key_identifier
.expect("missing key identifier")
.0,
&test_cert.get_key_identifier()
)
);

// Generate a CSR from our cert, parse it with x509-parser.
let test_cert_csr_der = test_cert.serialize_request_der();
let test_cert_csr_der = test_cert_csr_der.unwrap();
let (_, x509_csr) = X509CertificationRequest::from_der(&test_cert_csr_der).unwrap();

let parsed_exts = x509_csr
.requested_extensions()
.expect("missing CSR requested extensions")
.collect::<Vec<_>>();

macro_rules! assert_x509_csr_ext {
($expr:expr, $pattern:pat $(if $guard:expr)? $(,)?, $parsed_expr:expr) => {{
$expr.iter().find_map(|ext| match ext {
$pattern $(if $guard)? => Some($parsed_expr),
_ => None,
}).expect(concat!("missing ", stringify!($pattern)))
}};
}

// The basic constraints we specified should be there.
assert_x509_csr_ext!(
parsed_exts,
ParsedExtension::BasicConstraints(bc),
assert_bc(bc)
);

assert_x509_csr_ext!(
parsed_exts,
ParsedExtension::SubjectKeyIdentifier(ski),
assert_ski(ski)
);

assert_x509_csr_ext!(
parsed_exts,
ParsedExtension::CRLDistributionPoints(crl_dps),
assert_crl_dps(crl_dps)
);

assert_x509_csr_ext!(
parsed_exts,
ParsedExtension::NameConstraints(name_constraints),
assert_nc(name_constraints)
);

assert_x509_csr_ext!(
parsed_exts,
ParsedExtension::ExtendedKeyUsage(eku),
assert_eku(eku)
);

assert_x509_csr_ext!(parsed_exts, ParsedExtension::KeyUsage(ku), assert_ku(ku));

assert_x509_csr_ext!(
parsed_exts,
ParsedExtension::SubjectAlternativeName(san),
assert_san(san)
);
&cert.get_key_identifier()
),
_ => panic!("unexpected parsed extension type"),
};

// We should not find the AKI extension in the CSR. That's provided by an issuer
// when issuing the certificate (e.g. as we did above w/ serialize_der, making a
// self signed cert that contained an AKI).
// when issuing the certificate.
assert_eq!(
parsed_exts
.iter()
x509_csr
.requested_extensions()
.unwrap()
.find(|ext| { matches!(ext, ParsedExtension::AuthorityKeyIdentifier(_)) }),
None
);
Expand Down

0 comments on commit 2831ffc

Please sign in to comment.