use asn1_rs::{Any, Class, FromDer, Header, Tag};
use chrono::{DateTime, Utc};
use thiserror::Error;
use web_time::SystemTime;
use x509_parser::{
certificate::{BasicExtension, X509Certificate},
der_parser::{ber::parse_ber_sequence, oid},
extensions::ParsedExtension,
num_bigint::BigInt,
oid_registry::Oid,
x509::{AlgorithmIdentifier, X509Version},
};
use crate::{
crypto::{asn1::rfc3161::TstInfo, cose::CertificateTrustPolicy},
log_item,
status_tracker::StatusTracker,
validation_results::validation_codes::*,
};
pub fn check_end_entity_certificate_profile(
certificate_der: &[u8],
ctp: &CertificateTrustPolicy,
validation_log: &mut StatusTracker,
tst_info_opt: Option<&TstInfo>,
) -> Result<(), CertificateProfileError> {
check_certificate_profile(certificate_der, ctp, validation_log, tst_info_opt)?;
let (_rem, signcert) = X509Certificate::from_der(certificate_der).map_err(|_err| {
log_item!(
"",
"certificate could not be parsed",
"check_certificate_profile"
)
.validation_status(SIGNING_CREDENTIAL_INVALID)
.failure_no_throw(validation_log, CertificateProfileError::InvalidCertificate);
CertificateProfileError::InvalidCertificate
})?;
if ctp.has_mandatory_ekus() {
if let Ok(Some(BasicExtension { value: eku, .. })) = signcert.extended_key_usage() {
if !ctp.verify_mandatory_ekus(eku) {
log_item!(
"",
"certificate missing mandatory EKU",
"check_certificate_profile"
)
.validation_status(SIGNING_CREDENTIAL_INVALID)
.failure_no_throw(validation_log, CertificateProfileError::InvalidEku);
}
} else {
log_item!(
"",
"certificate missing mandatory EKU",
"check_certificate_profile"
)
.validation_status(SIGNING_CREDENTIAL_INVALID)
.failure_no_throw(validation_log, CertificateProfileError::InvalidEku);
}
}
let tbscert = &signcert.tbs_certificate;
if tbscert.is_ca() {
log_item!(
"",
"expected end-entity certificate",
"check_certificate_profile"
)
.validation_status(SIGNING_CREDENTIAL_INVALID)
.failure_no_throw(validation_log, CertificateProfileError::InvalidCertificate);
return Err(CertificateProfileError::InvalidCertificate);
}
Ok(())
}
pub fn check_certificate_profile(
certificate_der: &[u8],
ctp: &CertificateTrustPolicy,
validation_log: &mut StatusTracker,
_tst_info_opt: Option<&TstInfo>,
) -> Result<(), CertificateProfileError> {
let (_rem, signcert) = X509Certificate::from_der(certificate_der).map_err(|_err| {
log_item!(
"",
"certificate could not be parsed",
"check_certificate_profile"
)
.validation_status(SIGNING_CREDENTIAL_INVALID)
.failure_no_throw(validation_log, CertificateProfileError::InvalidCertificate);
CertificateProfileError::InvalidCertificate
})?;
if signcert.version() != X509Version::V3 {
log_item!(
"",
"certificate version incorrect",
"check_certificate_profile"
)
.validation_status(SIGNING_CREDENTIAL_INVALID)
.failure_no_throw(
validation_log,
CertificateProfileError::InvalidCertificateVersion,
);
return Err(CertificateProfileError::InvalidCertificateVersion);
}
if let Some(tst_info) = _tst_info_opt {
let signing_time: DateTime<Utc> = tst_info.gen_time.clone().into();
if !signcert.validity().is_valid_at(
x509_parser::time::ASN1Time::from_timestamp(signing_time.timestamp())
.map_err(|_| CertificateProfileError::InvalidCertificate)?,
) {
log_item!("", "certificate expired", "check_certificate_profile")
.validation_status(SIGNING_CREDENTIAL_EXPIRED)
.failure_no_throw(
validation_log,
CertificateProfileError::CertificateNotValidAtTime,
);
return Err(CertificateProfileError::CertificateNotValidAtTime);
}
} else {
let Ok(now) = SystemTime::now().duration_since(web_time::UNIX_EPOCH) else {
return Err(CertificateProfileError::InternalError(
"system time invalid".to_string(),
));
};
if !signcert.validity().is_valid_at(
x509_parser::time::ASN1Time::from_timestamp(now.as_secs() as i64)
.map_err(|_| CertificateProfileError::InvalidCertificate)?,
) {
log_item!("", "certificate expired", "check_certificate_profile")
.validation_status(SIGNING_CREDENTIAL_EXPIRED)
.failure_no_throw(
validation_log,
CertificateProfileError::CertificateNotValidAtTime,
);
return Err(CertificateProfileError::CertificateNotValidAtTime);
}
}
if signcert.tbs_certificate.signature != signcert.signature_algorithm {
log_item!(
"",
"certificate signature algorithm does not match tbsCertificate signature algorithm",
"check_certificate_profile"
)
.validation_status(SIGNING_CREDENTIAL_INVALID)
.failure_no_throw(validation_log, CertificateProfileError::InvalidCertificate);
return Err(CertificateProfileError::InvalidCertificate);
}
let cert_alg = &signcert.signature_algorithm.algorithm;
if !(*cert_alg == SHA256_WITH_RSAENCRYPTION_OID
|| *cert_alg == SHA384_WITH_RSAENCRYPTION_OID
|| *cert_alg == SHA512_WITH_RSAENCRYPTION_OID
|| *cert_alg == ECDSA_WITH_SHA256_OID
|| *cert_alg == ECDSA_WITH_SHA384_OID
|| *cert_alg == ECDSA_WITH_SHA512_OID
|| *cert_alg == RSASSA_PSS_OID
|| *cert_alg == ED25519_OID)
{
log_item!(
"",
"certificate algorithm not supported",
"check_certificate_profile"
)
.validation_status(SIGNING_CREDENTIAL_INVALID)
.failure_no_throw(
validation_log,
CertificateProfileError::UnsupportedAlgorithm,
);
return Err(CertificateProfileError::UnsupportedAlgorithm);
}
if *cert_alg == ED25519_OID && signcert.signature_algorithm.parameters.is_some() {
log_item!(
"",
"certificate Ed25519 signature algorithm parameters must be absent",
"check_certificate_profile"
)
.validation_status(SIGNING_CREDENTIAL_INVALID)
.failure_no_throw(validation_log, CertificateProfileError::InvalidCertificate);
return Err(CertificateProfileError::InvalidCertificate);
}
if *cert_alg == RSASSA_PSS_OID {
if let Some(parameters) = &signcert.signature_algorithm.parameters {
let seq = parameters
.as_sequence()
.map_err(|_err| CertificateProfileError::InvalidCertificate)?;
let (_i, (ha_alg, mgf_ai)) = seq
.parse(|i| {
let (i, h) = <Header as asn1_rs::FromDer>::from_der(i)?;
if h.class() != Class::ContextSpecific || h.tag() != Tag(0) {
return Err(nom::Err::Error(asn1_rs::Error::BerValueError));
}
let (i, ha_alg) = AlgorithmIdentifier::from_der(i)
.map_err(|_| nom::Err::Error(asn1_rs::Error::BerValueError))?;
let (i, h) = <Header as asn1_rs::FromDer>::from_der(i)?;
if h.class() != Class::ContextSpecific || h.tag() != Tag(1) {
return Err(nom::Err::Error(asn1_rs::Error::BerValueError));
}
let (i, mgf_ai) = AlgorithmIdentifier::from_der(i)
.map_err(|_| nom::Err::Error(asn1_rs::Error::BerValueError))?;
Ok((i, (ha_alg, mgf_ai)))
})
.map_err(|_| CertificateProfileError::InvalidCertificate)?;
let mgf_ai_parameters = mgf_ai
.parameters
.ok_or(CertificateProfileError::InvalidCertificate)?;
let mgf_ai_parameters = mgf_ai_parameters
.as_sequence()
.map_err(|_| CertificateProfileError::InvalidCertificate)?;
let (_i, mgf_ai_params_algorithm) =
<Any as asn1_rs::FromDer>::from_der(&mgf_ai_parameters.content)
.map_err(|_| CertificateProfileError::InvalidCertificate)?;
let mgf_ai_params_algorithm = mgf_ai_params_algorithm
.as_oid()
.map_err(|_| CertificateProfileError::InvalidCertificate)?;
if ha_alg.algorithm.to_id_string() != mgf_ai_params_algorithm.to_id_string() {
log_item!(
"",
"certificate algorithm error",
"check_certificate_profile"
)
.validation_status(SIGNING_CREDENTIAL_INVALID)
.failure_no_throw(validation_log, CertificateProfileError::InvalidCertificate);
return Err(CertificateProfileError::InvalidCertificate);
}
if !(ha_alg.algorithm == SHA256_OID
|| ha_alg.algorithm == SHA384_OID
|| ha_alg.algorithm == SHA512_OID)
{
log_item!(
"",
"certificate hash algorithm not supported",
"check_certificate_profile"
)
.validation_status(SIGNING_CREDENTIAL_INVALID)
.failure_no_throw(validation_log, CertificateProfileError::InvalidCertificate);
return Err(CertificateProfileError::InvalidCertificate);
}
} else {
log_item!(
"",
"certificate missing algorithm parameters",
"check_certificate_profile"
)
.validation_status(SIGNING_CREDENTIAL_INVALID)
.failure_no_throw(validation_log, CertificateProfileError::InvalidCertificate);
return Err(CertificateProfileError::InvalidCertificate);
}
}
let pk = signcert.public_key();
let skpi_alg = &pk.algorithm;
if skpi_alg.algorithm == EC_PUBLICKEY_OID {
if let Some(parameters) = &skpi_alg.parameters {
let named_curve_oid = parameters
.as_oid()
.map_err(|_err| CertificateProfileError::InvalidCertificate)?;
if !(named_curve_oid == PRIME256V1_OID
|| named_curve_oid == SECP384R1_OID
|| named_curve_oid == SECP521R1_OID)
{
log_item!(
"",
"certificate unsupported EC curve",
"check_certificate_profile"
)
.validation_status(SIGNING_CREDENTIAL_INVALID)
.failure_no_throw(validation_log, CertificateProfileError::InvalidCertificate);
return Err(CertificateProfileError::InvalidCertificate);
}
} else {
return Err(CertificateProfileError::InvalidCertificate);
}
}
if skpi_alg.algorithm == ED25519_OID && skpi_alg.parameters.is_some() {
log_item!(
"",
"certificate Ed25519 public key parameters must be absent",
"check_certificate_profile"
)
.validation_status(SIGNING_CREDENTIAL_INVALID)
.failure_no_throw(validation_log, CertificateProfileError::InvalidCertificate);
return Err(CertificateProfileError::InvalidCertificate);
}
if skpi_alg.algorithm == RSA_OID || skpi_alg.algorithm == RSASSA_PSS_OID {
let (_, skpi_ber) = parse_ber_sequence(&pk.subject_public_key.data)
.map_err(|_err| CertificateProfileError::InvalidCertificate)?;
let seq = skpi_ber
.as_sequence()
.map_err(|_err| CertificateProfileError::InvalidCertificate)?;
if seq.len() < 2 {
return Err(CertificateProfileError::InvalidCertificate);
}
let modulus = seq[0]
.as_bigint()
.map_err(|_| CertificateProfileError::InvalidCertificate)?;
let exponent = seq[1]
.as_bigint()
.map_err(|_| CertificateProfileError::InvalidCertificate)?;
if modulus.bits() < 2048 {
log_item!(
"",
"certificate key length too short",
"check_certificate_profile"
)
.validation_status(SIGNING_CREDENTIAL_INVALID)
.failure_no_throw(validation_log, CertificateProfileError::InvalidCertificate);
return Err(CertificateProfileError::InvalidCertificate);
}
if !is_valid_rsa_public_exponent(&exponent) {
log_item!(
"",
"certificate RSA public exponent is invalid",
"check_certificate_profile"
)
.validation_status(SIGNING_CREDENTIAL_INVALID)
.failure_no_throw(validation_log, CertificateProfileError::InvalidCertificate);
return Err(CertificateProfileError::InvalidCertificate);
}
}
let tbscert = &signcert.tbs_certificate;
if tbscert.is_ca() && tbscert.issuer() == tbscert.subject() {
log_item!(
"",
"certificate issuer and subject cannot be the same (self-signed disallowed)",
"check_certificate_profile"
)
.validation_status(SIGNING_CREDENTIAL_INVALID)
.failure_no_throw(
validation_log,
CertificateProfileError::SelfSignedCertificate,
);
return Err(CertificateProfileError::SelfSignedCertificate);
}
if signcert.issuer_uid.is_some() || signcert.subject_uid.is_some() {
log_item!(
"",
"certificate issuer/subject unique ids are not allowed",
"check_certificate_profile"
)
.validation_status(SIGNING_CREDENTIAL_INVALID)
.failure_no_throw(validation_log, CertificateProfileError::InvalidCertificate);
return Err(CertificateProfileError::InvalidCertificate);
}
let mut aki_good = false;
let mut ski_good = false;
let mut key_usage_good = false;
let mut handled_all_critical = true;
let extended_key_usage_good = match tbscert
.extended_key_usage()
.map_err(|_| CertificateProfileError::InvalidCertificate)?
{
Some(BasicExtension { value: eku, .. }) => {
if eku.any {
log_item!(
"",
"certificate 'any' EKU not allowed",
"check_certificate_profile"
)
.validation_status(SIGNING_CREDENTIAL_INVALID)
.failure_no_throw(validation_log, CertificateProfileError::InvalidCertificate);
return Err(CertificateProfileError::InvalidCertificate);
}
if ctp.has_allowed_eku(eku).is_none() {
log_item!(
"",
"certificate missing required EKU",
"check_certificate_profile"
)
.validation_status(SIGNING_CREDENTIAL_INVALID)
.failure_no_throw(validation_log, CertificateProfileError::InvalidCertificate);
return Err(CertificateProfileError::InvalidCertificate);
}
if (eku.ocsp_signing && eku.time_stamping)
|| ((eku.ocsp_signing ^ eku.time_stamping)
&& (eku.client_auth
| eku.code_signing
| eku.email_protection
| eku.server_auth
| !eku.other.is_empty()))
{
log_item!(
"",
"certificate invalid set of EKUs",
"check_certificate_profile"
)
.validation_status(SIGNING_CREDENTIAL_INVALID)
.failure_no_throw(validation_log, CertificateProfileError::InvalidCertificate);
return Err(CertificateProfileError::InvalidCertificate);
}
true
}
None => tbscert.is_ca(), };
let signs_time_stamps_or_ocsp = matches!(
tbscert.extended_key_usage(),
Ok(Some(BasicExtension { value: eku, .. })) if eku.time_stamping || eku.ocsp_signing
);
for e in signcert.extensions() {
match e.parsed_extension() {
ParsedExtension::AuthorityKeyIdentifier(_aki) => {
aki_good = true;
}
ParsedExtension::SubjectKeyIdentifier(_spki) => {
ski_good = true;
}
ParsedExtension::KeyUsage(ku) => {
if ku.key_cert_sign() && !tbscert.is_ca() {
log_item!(
"",
"certificate asserts keyCertSign key usage but is not a CA",
"check_certificate_profile"
)
.validation_status(SIGNING_CREDENTIAL_INVALID)
.failure_no_throw(validation_log, CertificateProfileError::InvalidCertificate);
return Err(CertificateProfileError::InvalidCertificate);
}
if !tbscert.is_ca() && !signs_time_stamps_or_ocsp && !ku.digital_signature() {
log_item!(
"",
"certificate missing digitalSignature key usage",
"check_certificate_profile"
)
.validation_status(SIGNING_CREDENTIAL_INVALID)
.failure_no_throw(validation_log, CertificateProfileError::InvalidCertificate);
return Err(CertificateProfileError::InvalidCertificate);
}
if ku.digital_signature() || ku.key_cert_sign() || ku.non_repudiation() {
key_usage_good = true;
}
}
ParsedExtension::CertificatePolicies(_) => (),
ParsedExtension::PolicyMappings(_) => (),
ParsedExtension::SubjectAlternativeName(_) => (),
ParsedExtension::BasicConstraints(_) => (),
ParsedExtension::NameConstraints(_) => (),
ParsedExtension::PolicyConstraints(_) => (),
ParsedExtension::ExtendedKeyUsage(_) => (),
ParsedExtension::CRLDistributionPoints(_) => (),
ParsedExtension::InhibitAnyPolicy(_) => (),
ParsedExtension::AuthorityInfoAccess(_) => (),
ParsedExtension::NSCertType(_) => (),
ParsedExtension::CRLNumber(_) => (),
ParsedExtension::ReasonCode(_) => (),
ParsedExtension::InvalidityDate(_) => (),
ParsedExtension::Unparsed => {
if e.critical {
handled_all_critical = false;
}
}
_ => {
if e.critical {
handled_all_critical = false;
}
}
}
}
ski_good = if tbscert.is_ca() { ski_good } else { true };
if aki_good && ski_good && key_usage_good && extended_key_usage_good && handled_all_critical {
Ok(())
} else {
log_item!(
"",
"certificate params incorrect",
"check_certificate_profile"
)
.validation_status(SIGNING_CREDENTIAL_INVALID)
.failure_no_throw(validation_log, CertificateProfileError::InvalidCertificate);
Err(CertificateProfileError::InvalidCertificate)
}
}
#[derive(Debug, Eq, Error, PartialEq)]
#[non_exhaustive]
pub enum CertificateProfileError {
#[error("the certificate is invalid")]
InvalidCertificate,
#[error("the certificate must be a `v3` certificate")]
InvalidCertificateVersion,
#[error("the certificate was not valid at time of signing")]
CertificateNotValidAtTime,
#[error("the certificate was signed with an unsupported algorithm")]
UnsupportedAlgorithm,
#[error("the certificate contains an invalid extended key usage (EKU) value")]
InvalidEku,
#[error("the certificate was self-signed")]
SelfSignedCertificate,
#[error("internal error ({0})")]
InternalError(String),
}
const RSA_OID: Oid<'static> = oid!(1.2.840 .113549 .1 .1 .1);
const EC_PUBLICKEY_OID: Oid<'static> = oid!(1.2.840 .10045 .2 .1);
const RSASSA_PSS_OID: Oid<'static> = oid!(1.2.840 .113549 .1 .1 .10);
const ECDSA_WITH_SHA256_OID: Oid<'static> = oid!(1.2.840 .10045 .4 .3 .2);
const ECDSA_WITH_SHA384_OID: Oid<'static> = oid!(1.2.840 .10045 .4 .3 .3);
const ECDSA_WITH_SHA512_OID: Oid<'static> = oid!(1.2.840 .10045 .4 .3 .4);
const SHA256_WITH_RSAENCRYPTION_OID: Oid<'static> = oid!(1.2.840 .113549 .1 .1 .11);
const SHA384_WITH_RSAENCRYPTION_OID: Oid<'static> = oid!(1.2.840 .113549 .1 .1 .12);
const SHA512_WITH_RSAENCRYPTION_OID: Oid<'static> = oid!(1.2.840 .113549 .1 .1 .13);
const ED25519_OID: Oid<'static> = oid!(1.3.101 .112);
const SHA256_OID: Oid<'static> = oid!(2.16.840 .1 .101 .3 .4 .2 .1);
const SHA384_OID: Oid<'static> = oid!(2.16.840 .1 .101 .3 .4 .2 .2);
const SHA512_OID: Oid<'static> = oid!(2.16.840 .1 .101 .3 .4 .2 .3);
const SECP521R1_OID: Oid<'static> = oid!(1.3.132 .0 .35);
const SECP384R1_OID: Oid<'static> = oid!(1.3.132 .0 .34);
const PRIME256V1_OID: Oid<'static> = oid!(1.2.840 .10045 .3 .1 .7);
fn is_valid_rsa_public_exponent(exponent: &BigInt) -> bool {
exponent >= &BigInt::from(3u32)
&& exponent
.magnitude()
.to_u32_digits()
.first()
.is_some_and(|least_significant| least_significant % 2 == 1)
}
#[cfg(test)]
mod tests {
#![allow(clippy::expect_used)]
#![allow(clippy::panic)]
#![allow(clippy::unwrap_used)]
use rasn::types::Any;
use rasn_pkix::Certificate;
#[cfg(all(target_arch = "wasm32", not(target_os = "wasi")))]
use wasm_bindgen_test::wasm_bindgen_test;
use x509_parser::{num_bigint::BigInt, pem::Pem};
use super::is_valid_rsa_public_exponent;
use crate::{
crypto::cose::{check_end_entity_certificate_profile, CertificateTrustPolicy},
status_tracker::StatusTracker,
utils::ephemeral_cert::generate_ephemeral_chain,
validation_results::validation_codes::{
SIGNING_CREDENTIAL_EXPIRED, SIGNING_CREDENTIAL_INVALID,
},
};
#[test]
fn rsa_public_exponent_profile() {
assert!(!is_valid_rsa_public_exponent(&BigInt::from(1u32)));
assert!(!is_valid_rsa_public_exponent(&BigInt::from(2u32)));
assert!(!is_valid_rsa_public_exponent(&BigInt::from(4u32)));
assert!(!is_valid_rsa_public_exponent(&BigInt::from(-3)));
assert!(is_valid_rsa_public_exponent(&BigInt::from(3u32)));
assert!(is_valid_rsa_public_exponent(&BigInt::from(65537u32)));
}
#[test]
#[cfg_attr(
all(target_arch = "wasm32", not(target_os = "wasi")),
wasm_bindgen_test
)]
fn expired_cert() {
let ctp = CertificateTrustPolicy::default();
let mut validation_log = StatusTracker::default();
let cert_der = x509_der_from_pem(include_bytes!(
"../../../tests/fixtures/crypto/cose/rsa-pss256_key-expired.pub"
));
assert!(
check_end_entity_certificate_profile(&cert_der, &ctp, &mut validation_log, None)
.is_err()
);
assert!(!validation_log.logged_items().is_empty());
assert_eq!(
validation_log.logged_items()[0].validation_status,
Some(SIGNING_CREDENTIAL_EXPIRED.into())
);
}
#[test]
#[cfg_attr(
all(target_arch = "wasm32", not(target_os = "wasi")),
wasm_bindgen_test
)]
fn cert_algorithms() {
let ctp = CertificateTrustPolicy::default();
let mut validation_log = StatusTracker::default();
let es256_cert = x509_der_from_pem(include_bytes!(
"../../../tests/fixtures/crypto/raw_signature/es256.pub"
));
let es384_cert = x509_der_from_pem(include_bytes!(
"../../../tests/fixtures/crypto/raw_signature/es384.pub"
));
let es512_cert = x509_der_from_pem(include_bytes!(
"../../../tests/fixtures/crypto/raw_signature/es512.pub"
));
let ps256_cert = x509_der_from_pem(include_bytes!(
"../../../tests/fixtures/crypto/raw_signature/ps256.pub"
));
check_end_entity_certificate_profile(&es256_cert, &ctp, &mut validation_log, None).unwrap();
check_end_entity_certificate_profile(&es384_cert, &ctp, &mut validation_log, None).unwrap();
check_end_entity_certificate_profile(&es512_cert, &ctp, &mut validation_log, None).unwrap();
check_end_entity_certificate_profile(&ps256_cert, &ctp, &mut validation_log, None).unwrap();
}
#[test]
#[cfg_attr(
all(target_arch = "wasm32", not(target_os = "wasi")),
wasm_bindgen_test
)]
fn ed25519_algorithm_parameters_must_be_absent() {
let der_null = Any::new(vec![0x05, 0x00]);
let ctp = CertificateTrustPolicy::default();
let chain = generate_ephemeral_chain("ed25519-profile.local").unwrap();
let mut validation_log = StatusTracker::default();
check_end_entity_certificate_profile(&chain.ee_der, &ctp, &mut validation_log, None)
.unwrap();
let assert_rejected = |cert: &Certificate, expected_description: &str| {
let der = rasn::der::encode(cert).unwrap();
let mut validation_log = StatusTracker::default();
assert!(
check_end_entity_certificate_profile(&der, &ctp, &mut validation_log, None)
.is_err()
);
let item = &validation_log.logged_items()[0];
assert_eq!(
item.validation_status,
Some(SIGNING_CREDENTIAL_INVALID.into())
);
assert_eq!(item.description, expected_description);
};
let mut cert: Certificate = rasn::der::decode(&chain.ee_der).unwrap();
cert.tbs_certificate
.subject_public_key_info
.algorithm
.parameters = Some(der_null.clone());
assert_rejected(
&cert,
"certificate Ed25519 public key parameters must be absent",
);
let mut cert: Certificate = rasn::der::decode(&chain.ee_der).unwrap();
cert.signature_algorithm.parameters = Some(der_null.clone());
cert.tbs_certificate.signature.parameters = Some(der_null.clone());
assert_rejected(
&cert,
"certificate Ed25519 signature algorithm parameters must be absent",
);
}
#[test]
#[cfg_attr(
all(target_arch = "wasm32", not(target_os = "wasi")),
wasm_bindgen_test
)]
fn signature_algorithm_must_match_tbs_signature() {
let der_null = Any::new(vec![0x05, 0x00]);
let ctp = CertificateTrustPolicy::default();
let chain = generate_ephemeral_chain("ed25519-profile.local").unwrap();
let expected =
"certificate signature algorithm does not match tbsCertificate signature algorithm";
let mut cert: Certificate = rasn::der::decode(&chain.ee_der).unwrap();
cert.signature_algorithm.parameters = Some(der_null.clone());
let mut cert_inner: Certificate = rasn::der::decode(&chain.ee_der).unwrap();
cert_inner.tbs_certificate.signature.parameters = Some(der_null);
for cert in [cert, cert_inner] {
let der = rasn::der::encode(&cert).unwrap();
let mut validation_log = StatusTracker::default();
assert!(
check_end_entity_certificate_profile(&der, &ctp, &mut validation_log, None)
.is_err()
);
let item = &validation_log.logged_items()[0];
assert_eq!(
item.validation_status,
Some(SIGNING_CREDENTIAL_INVALID.into())
);
assert_eq!(item.description, expected);
}
}
#[test]
#[cfg_attr(
all(target_arch = "wasm32", not(target_os = "wasi")),
wasm_bindgen_test
)]
fn end_entity_key_usage() {
let ctp = CertificateTrustPolicy::default();
for (cert_pem, expected_description) in [
(
include_bytes!("../../../tests/fixtures/crypto/cose/ku_keycertsign_not_ca.pub")
.as_slice(),
"certificate asserts keyCertSign key usage but is not a CA",
),
(
include_bytes!("../../../tests/fixtures/crypto/cose/ku_nonrepudiation_only.pub")
.as_slice(),
"certificate missing digitalSignature key usage",
),
] {
let mut validation_log = StatusTracker::default();
assert!(check_end_entity_certificate_profile(
&x509_der_from_pem(cert_pem),
&ctp,
&mut validation_log,
None
)
.is_err());
let item = &validation_log.logged_items()[0];
assert_eq!(
item.validation_status,
Some(SIGNING_CREDENTIAL_INVALID.into())
);
assert_eq!(item.description, expected_description);
}
let mut validation_log = StatusTracker::default();
check_end_entity_certificate_profile(
&x509_der_from_pem(include_bytes!(
"../../../tests/fixtures/crypto/cose/ku_nonrepudiation_tsa.pub"
)),
&ctp,
&mut validation_log,
None,
)
.unwrap();
}
#[test]
#[cfg_attr(
all(target_arch = "wasm32", not(target_os = "wasi")),
wasm_bindgen_test
)]
fn ocsp_signing_allows_nonrepudiation_without_digital_signature() {
fn oid(components: &[u32]) -> rasn::types::ObjectIdentifier {
rasn::types::ObjectIdentifier::new(components.to_vec()).unwrap()
}
fn extension(cert: &mut Certificate, components: &[u32], value: Vec<u8>) {
let id = oid(components);
cert.tbs_certificate
.extensions
.as_mut()
.unwrap()
.iter_mut()
.find(|extension| extension.extn_id == id)
.unwrap()
.extn_value = value.into();
}
let chain = generate_ephemeral_chain("ocsp-key-usage.local").unwrap();
let mut cert: Certificate = rasn::der::decode(&chain.ee_der).unwrap();
extension(&mut cert, &[2, 5, 29, 15], vec![0x03, 0x02, 0x06, 0x40]);
extension(
&mut cert,
&[2, 5, 29, 37],
rasn::der::encode(&vec![oid(&[1, 3, 6, 1, 5, 5, 7, 3, 9])]).unwrap(),
);
let ctp = CertificateTrustPolicy::default();
let mut validation_log = StatusTracker::default();
assert_eq!(
check_end_entity_certificate_profile(
&rasn::der::encode(&cert).unwrap(),
&ctp,
&mut validation_log,
None,
),
Ok(())
);
assert!(validation_log.logged_items().is_empty());
extension(
&mut cert,
&[2, 5, 29, 37],
rasn::der::encode(&vec![oid(&[1, 3, 6, 1, 5, 5, 7, 3, 4])]).unwrap(),
);
let mut validation_log = StatusTracker::default();
assert_eq!(
check_end_entity_certificate_profile(
&rasn::der::encode(&cert).unwrap(),
&ctp,
&mut validation_log,
None,
),
Err(super::CertificateProfileError::InvalidCertificate)
);
let item = &validation_log.logged_items()[0];
assert_eq!(
item.validation_status,
Some(SIGNING_CREDENTIAL_INVALID.into())
);
assert_eq!(
item.description,
"certificate missing digitalSignature key usage"
);
}
fn x509_der_from_pem(cert_pem: &[u8]) -> Vec<u8> {
let mut pems = Pem::iter_from_buffer(cert_pem);
let pem = pems.next().unwrap().unwrap();
pem.contents
}
}