Skip to content

Commit

Permalink
wip: test rcgen extensions w/ x509-parser, lib fixes
Browse files Browse the repository at this point in the history
  • Loading branch information
cpu committed Sep 10, 2023
1 parent 849ee7f commit 90f29c3
Show file tree
Hide file tree
Showing 2 changed files with 250 additions and 14 deletions.
21 changes: 7 additions & 14 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -826,14 +826,14 @@ impl CertificateParams {
serial_number,
subject_alt_names: _,
distinguished_name,
is_ca,
key_usages,
extended_key_usages,
name_constraints,
crl_distribution_points,
is_ca: _,
key_usages: _,
extended_key_usages: _,
name_constraints: _,
crl_distribution_points: _,
custom_extensions: _,
key_pair,
use_authority_key_identifier_extension,
use_authority_key_identifier_extension: _,
key_identifier_method,
} = self;
// - alg and key_pair will be used by the caller
Expand All @@ -843,14 +843,7 @@ impl CertificateParams {
// from the defaults so this is left for a later version if
// needed.
let _ = (alg, key_pair, not_before, not_after, key_identifier_method);
if serial_number.is_some()
|| *is_ca != IsCa::NoCa
|| !key_usages.is_empty()
|| !extended_key_usages.is_empty()
|| name_constraints.is_some()
|| !crl_distribution_points.is_empty()
|| *use_authority_key_identifier_extension
{
if serial_number.is_some() {
return Err(RcgenError::UnsupportedInCsr);
}
writer.write_sequence(|writer| {
Expand Down
243 changes: 243 additions & 0 deletions tests/generic.rs
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,249 @@ mod test_convert_x509_subject_alternative_name {
}
}

#[cfg(feature = "x509-parser")]
mod test_csr_exts {
use crate::util;
use rcgen::{
BasicConstraints, Certificate, CrlDistributionPoint, GeneralSubtree, KeyUsagePurpose,
SanType,
};
use x509_parser::prelude::{
FromDer, ParsedExtension, X509Certificate, X509CertificationRequest,
};

#[test]
fn test_rcgen_extensions() {
// Create a certificate that has several rcgen managed extensions (e.g. not custom extensions).
let mut params = util::default_params();
params.subject_alt_names = vec![SanType::DnsName("san.example.com".into())];
let path_len_constraint = 3;
params.is_ca = rcgen::IsCa::Ca(BasicConstraints::Constrained(path_len_constraint));
params.key_usages = vec![
KeyUsagePurpose::DigitalSignature,
KeyUsagePurpose::KeyEncipherment,
];
params.extended_key_usages = vec![
rcgen::ExtendedKeyUsagePurpose::ServerAuth,
rcgen::ExtendedKeyUsagePurpose::ClientAuth,
];
params.name_constraints = Some(rcgen::NameConstraints {
permitted_subtrees: vec![GeneralSubtree::DnsName("example.com".into())],
excluded_subtrees: vec![GeneralSubtree::DnsName("example.org".into())],
});
params.crl_distribution_points = vec![CrlDistributionPoint {
uris: vec!["http://example.com".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();

// Check SAN is present, not critical, and not empty.
let san_ext = x509_test_cert
.subject_alternative_name()
.expect("invalid SAN ext")
.expect("missing SAN ext");
assert!(!san_ext.critical);
assert!(!san_ext.value.general_names.is_empty());

// Check basic constraints are present, critical, and have the expected path len constraint.
let basic_constraints = x509_test_cert
.basic_constraints()
.expect("invalid basic constraints")
.expect("missing basic constraints");
assert!(basic_constraints.critical);
let assert_basic_constraints =
|basic_constraints: &x509_parser::prelude::BasicConstraints| {
assert!(basic_constraints.ca);
assert_eq!(
basic_constraints
.path_len_constraint
.expect("missing path len constraint"),
path_len_constraint as u32
);
};
assert_basic_constraints(basic_constraints.value);

// Check name constraints are present, critical and non-empty.
let name_constraints = x509_test_cert
.name_constraints()
.expect("invalid name constraints")
.expect("missing name constraints");
assert!(name_constraints.critical);
let assert_name_constraints = |name_constraints: &x509_parser::prelude::NameConstraints| {
assert!(!name_constraints
.permitted_subtrees
.clone()
.expect("missing permitted subtrees")
.is_empty());
assert!(!name_constraints
.excluded_subtrees
.clone()
.expect("missing excluded subtrees")
.is_empty());
};
assert_name_constraints(name_constraints.value);

// Check CRL distribution point is present and non-critical.
let crl_distrib_point = x509_test_cert
.get_extension_unique(&x509_parser::oid_registry::OID_X509_EXT_CRL_DISTRIBUTION_POINTS)
.expect("malformed CRL distribution points extension")
.expect("missing CRL distribution points extension");
assert!(!crl_distrib_point.critical);
// See parse_crl_dps for a test of the ext structure/value.

// Check key usage is present, and critical.
let key_usage = x509_test_cert
.key_usage()
.expect("invalid key usage")
.expect("missing key usage");
assert!(key_usage.critical);

// Check extended key usage is present, and non-critical.
let extended_key_usage = x509_test_cert
.extended_key_usage()
.expect("invalid extended key usage")
.expect("missing extended key usage");
assert!(!extended_key_usage.critical);

// Check subject key identifier is present, non-critical, and matches the expected value.
let ski = x509_test_cert
.get_extension_unique(&x509_parser::oid_registry::OID_X509_EXT_SUBJECT_KEY_IDENTIFIER)
.expect("invalid SKI extension")
.expect("missing SKI extension");
assert!(!ski.critical);
let ski = match ski.parsed_extension() {
ParsedExtension::SubjectKeyIdentifier(key_id) => key_id,
_ => panic!("unexpected parsed extension type"),
};
assert_eq!(ski.0, &test_cert.get_key_identifier());

let aki = x509_test_cert
.get_extension_unique(&x509_parser::oid_registry::OID_X509_EXT_AUTHORITY_KEY_IDENTIFIER)
.expect("invalid AKI extension")
.expect("missing AKI extension");
assert!(!aki.critical);
let aki = match aki.parsed_extension() {
ParsedExtension::AuthorityKeyIdentifier(key_id) => key_id,
_ => panic!("unexpected parsed extension type"),
};
assert_eq!(
aki.key_identifier
.clone()
.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<_>>();

// The basic constraints we specified should be there.
let basic_constraints = parsed_exts
.iter()
.find_map(|ext| {
if let ParsedExtension::BasicConstraints(bc) = ext {
Some(bc)
} else {
None
}
})
.expect("missing basic constraints");
assert_basic_constraints(basic_constraints);

let ski = parsed_exts
.iter()
.find_map(|ext| {
if let ParsedExtension::SubjectKeyIdentifier(ski) = ext {
Some(ski)
} else {
None
}
})
.expect("missing subject key identifier");
assert_eq!(ski.0, &test_cert.get_key_identifier());

parsed_exts
.iter()
.find_map(|ext| {
if let ParsedExtension::CRLDistributionPoints(ski) = ext {
Some(ski)
} else {
None
}
})
.expect("missing CRL distribution points");

let name_constraints = parsed_exts
.iter()
.find_map(|ext| {
if let ParsedExtension::NameConstraints(name_constraints) = ext {
Some(name_constraints)
} else {
None
}
})
.expect("missing name constraints");
assert_name_constraints(name_constraints);

parsed_exts
.iter()
.find_map(|ext| {
if let ParsedExtension::ExtendedKeyUsage(eku) = ext {
Some(eku)
} else {
None
}
})
.expect("missing extended key usage");

parsed_exts
.iter()
.find_map(|ext| {
if let ParsedExtension::KeyUsage(ku) = ext {
Some(ku)
} else {
None
}
})
.expect("missing key usage");

parsed_exts
.iter()
.find_map(|ext| {
if let ParsedExtension::SubjectAlternativeName(san) = ext {
Some(san)
} else {
None
}
})
.expect("missing subject alternative name");

// 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).
assert_eq!(
parsed_exts.iter().find_map(|ext| {
if let ParsedExtension::AuthorityKeyIdentifier(aki) = ext {
Some(aki)
} else {
None
}
}),
None
);
}
}

#[cfg(feature = "x509-parser")]
mod test_x509_custom_ext {
use crate::util;
Expand Down

0 comments on commit 90f29c3

Please sign in to comment.