use super::{Certificate, Error, Extension, Name, NameAttribute, Result, Role, ValidityCheck};
use crate::xdsa;
use const_oid::ObjectIdentifier;
use std::collections::HashSet;
use std::time::{Duration, SystemTime, UNIX_EPOCH};
use x509_parser::extensions::ParsedExtension;
pub(crate) fn verify_cert<const N: usize>(
der: &[u8],
issuer: &xdsa::PublicKey,
validity: ValidityCheck,
) -> Result<([u8; N], Certificate, u16)> {
let (rem, cert) = x509_parser::parse_x509_certificate(der).map_err(|e| Error::X509Parse {
details: e.to_string(),
})?;
if !rem.is_empty() {
return Err(Error::X509Parse {
details: "trailing data after DER certificate".into(),
});
}
let tbs = cert.tbs_certificate.as_ref();
let sig_bytes: [u8; xdsa::SIGNATURE_SIZE] = cert
.signature_value
.data
.as_ref()
.try_into()
.map_err(|_| Error::X509Parse {
details: "invalid signature length".into(),
})?;
let sig = xdsa::Signature::from_bytes(&sig_bytes);
issuer.verify(tbs, &sig)?;
let not_before = cert.tbs_certificate.validity.not_before.timestamp();
let not_after = cert.tbs_certificate.validity.not_after.timestamp();
if not_before < 0 || not_after < 0 {
return Err(Error::X509Parse {
details: "invalid timestamp".into(),
});
}
let not_before = not_before as u64;
let not_after = not_after as u64;
let check_time = match validity {
ValidityCheck::Now => Some(
SystemTime::now()
.duration_since(UNIX_EPOCH)
.unwrap_or(Duration::ZERO)
.as_secs(),
),
ValidityCheck::At(ts) => Some(ts),
ValidityCheck::Disabled => None,
};
if let Some(now) = check_time
&& (now < not_before || now > not_after)
{
return Err(Error::ExpiredCertificate);
}
let key_bytes = subject_public_key_bytes::<N>(&cert)?;
let (parsed, key_usage) = extract_cert(&cert)?;
Ok((key_bytes, parsed, key_usage))
}
fn subject_public_key_bytes<const N: usize>(
cert: &x509_parser::certificate::X509Certificate<'_>,
) -> Result<[u8; N]> {
if cert
.tbs_certificate
.subject_pki
.subject_public_key
.unused_bits
!= 0
{
return Err(Error::X509Parse {
details: "non-canonical BIT STRING: subjectPublicKey must have zero unused bits".into(),
});
}
cert.tbs_certificate
.subject_pki
.subject_public_key
.data
.as_ref()
.try_into()
.map_err(|_| Error::X509Parse {
details: "invalid subject public key length".into(),
})
}
fn extract_cert(cert: &x509_parser::certificate::X509Certificate) -> Result<(Certificate, u16)> {
let basic_constraints =
cert.tbs_certificate
.basic_constraints()
.map_err(|e| Error::X509Parse {
details: format!("basicConstraints: {e}"),
})?;
let role = match basic_constraints {
Some(ext) if ext.value.ca => {
let path_len = convert_path_len(ext.value.path_len_constraint)?;
Role::Authority { path_len }
}
Some(ext) if ext.value.path_len_constraint.is_some() => {
return Err(Error::InvalidPathLen {
details: "requires ca=true",
});
}
_ => Role::Leaf,
};
let key_usage = cert
.tbs_certificate
.key_usage()
.map_err(|e| Error::X509Parse {
details: format!("keyUsage: {e}"),
})?
.map(|ku| parse_key_usage_flags(ku.value.flags))
.transpose()?;
let mut extensions = Vec::new();
let mut extension_oids = HashSet::new();
for ext in cert.tbs_certificate.extensions() {
let oid = ext.oid.to_id_string();
if !extension_oids.insert(oid.clone()) {
return Err(Error::DuplicateExtensionOid { oid });
}
match ext.parsed_extension() {
ParsedExtension::SubjectKeyIdentifier(_)
| ParsedExtension::AuthorityKeyIdentifier(_)
| ParsedExtension::BasicConstraints(_)
| ParsedExtension::KeyUsage(_) => {}
_ => {
extensions.push(Extension {
oid: ObjectIdentifier::new(ext.oid.to_id_string().as_str())?,
critical: ext.critical,
value: ext.value.to_vec(),
});
}
}
}
let key_usage = key_usage.ok_or(Error::X509Parse {
details: "keyUsage extension is required".into(),
})?;
let parsed = Certificate {
subject: parse_name(&cert.tbs_certificate.subject)?,
issuer: parse_name(&cert.tbs_certificate.issuer)?,
not_before: cert.tbs_certificate.validity.not_before.timestamp() as u64, not_after: cert.tbs_certificate.validity.not_after.timestamp() as u64, role,
extensions,
};
Ok((parsed, key_usage))
}
pub(super) fn convert_path_len(path_len: Option<u32>) -> Result<Option<u8>> {
match path_len {
Some(v) if v > u8::MAX as u32 => Err(Error::InvalidPathLen {
details: "exceeds u8::MAX",
}),
Some(v) => Ok(Some(v as u8)),
None => Ok(None),
}
}
fn parse_key_usage_flags(flags: u16) -> Result<u16> {
const ALL_KNOWN: u16 = (1 << 9) - 1;
if flags & !ALL_KNOWN != 0 {
return Err(Error::X509Parse {
details: "keyUsage: unknown bits set".into(),
});
}
Ok(flags)
}
fn parse_name(name: &x509_parser::x509::X509Name<'_>) -> Result<Name> {
let mut attrs = Vec::new();
for attr in name.iter_attributes() {
let value = attr
.as_str()
.map_err(|_| Error::X509Parse {
details: "DN attribute value is not valid UTF-8".into(),
})?
.to_string();
attrs.push(NameAttribute {
oid: ObjectIdentifier::new(attr.attr_type().to_id_string().as_str())?,
value,
});
}
Ok(Name { attrs })
}
#[cfg(test)]
mod test {
use super::*;
use crate::x509::issue_cert;
use crate::x509::name;
use crate::xdsa;
#[cfg(feature = "xhpke")]
use crate::xhpke;
use const_oid::ObjectIdentifier;
use der::asn1::{Any, BitString, OctetString, SetOfVec};
use der::{Encode, Tag};
use std::time::{Duration, SystemTime, UNIX_EPOCH};
use x509_cert::attr::AttributeTypeAndValue;
use x509_cert::certificate::CertificateInner;
use x509_cert::ext::pkix::BasicConstraints;
use x509_cert::ext::pkix::{KeyUsage, KeyUsages};
use x509_cert::name::{RdnSequence, RelativeDistinguishedName};
fn build_xdsa_cert(
subject: &xdsa::PublicKey,
issuer: &xdsa::SecretKey,
template: &Certificate,
) -> Result<CertificateInner> {
let default_ku = match template.role {
Role::Authority { .. } => KeyUsage(KeyUsages::KeyCertSign | KeyUsages::CRLSign),
Role::Leaf => KeyUsage(KeyUsages::DigitalSignature.into()),
};
issue_cert(&subject.to_bytes(), xdsa::OID, default_ku, issuer, template)
}
#[cfg(feature = "xhpke")]
fn build_xhpke_cert(
subject: &xhpke::PublicKey,
issuer: &xdsa::SecretKey,
template: &Certificate,
) -> Result<CertificateInner> {
issue_cert(
&subject.to_bytes(),
xhpke::OID,
KeyUsage(KeyUsages::KeyAgreement.into()),
issuer,
template,
)
}
#[test]
fn test_verify_xdsa_rejects_wrong_signer() {
let subject = xdsa::SecretKey::generate();
let issuer = xdsa::SecretKey::generate();
let wrong = xdsa::SecretKey::generate();
let now = SystemTime::now()
.duration_since(UNIX_EPOCH)
.unwrap_or(Duration::ZERO)
.as_secs();
let template = Certificate {
subject: Name::new().cn("Alice Identity"),
issuer: Name::new().cn("Root"),
not_before: now,
not_after: now + 3600,
role: Role::Leaf,
..Default::default()
};
let pem = xdsa::issue_cert_pem(&subject.public_key(), &issuer, &template).unwrap();
let result = xdsa::verify_cert_pem(&pem, &wrong.public_key(), ValidityCheck::Now);
assert!(result.is_err());
}
#[test]
#[cfg(feature = "xhpke")]
fn test_verify_xhpke_rejects_wrong_signer() {
let subject = xhpke::SecretKey::generate();
let issuer = xdsa::SecretKey::generate();
let wrong = xdsa::SecretKey::generate();
let now = SystemTime::now()
.duration_since(UNIX_EPOCH)
.unwrap_or(Duration::ZERO)
.as_secs();
let template = Certificate {
subject: Name::new().cn("Alice Encryption"),
issuer: Name::new().cn("Alice Identity"),
not_before: now,
not_after: now + 3600,
role: Role::Leaf,
..Default::default()
};
let pem = xhpke::issue_cert_pem(&subject.public_key(), &issuer, &template).unwrap();
let result = xhpke::verify_cert_pem(&pem, &wrong.public_key(), ValidityCheck::Now);
assert!(result.is_err());
}
#[test]
#[cfg(feature = "xhpke")]
fn test_verify_xhpke_rejects_ca_certificate() {
let subject = xhpke::SecretKey::generate();
let issuer = xdsa::SecretKey::generate();
let now = SystemTime::now()
.duration_since(UNIX_EPOCH)
.unwrap_or(Duration::ZERO)
.as_secs();
let template = Certificate {
subject: Name::new().cn("Alice Encryption"),
issuer: Name::new().cn("Alice Identity"),
not_before: now,
not_after: now + 3600,
role: Role::Authority { path_len: Some(0) },
..Default::default()
};
let der = build_xhpke_cert(&subject.public_key(), &issuer, &template)
.unwrap()
.to_der()
.unwrap();
let result = xhpke::verify_cert_der(&der, &issuer.public_key(), ValidityCheck::Now);
assert!(result.is_err());
}
#[test]
fn test_verify_rejects_non_certificate_pem_label() {
let subject = xdsa::SecretKey::generate();
let issuer = xdsa::SecretKey::generate();
let now = SystemTime::now()
.duration_since(UNIX_EPOCH)
.unwrap_or(Duration::ZERO)
.as_secs();
let template = Certificate {
subject: Name::new().cn("Alice Identity"),
issuer: Name::new().cn("Root"),
not_before: now,
not_after: now + 3600,
role: Role::Leaf,
..Default::default()
};
let pem = xdsa::issue_cert_pem(&subject.public_key(), &issuer, &template).unwrap();
let pem = pem.replace("CERTIFICATE", "PRIVATE KEY");
let result = xdsa::verify_cert_pem(&pem, &issuer.public_key(), ValidityCheck::Now);
assert!(result.is_err());
}
#[test]
fn test_verify_rejects_trailing_der_data() {
let subject = xdsa::SecretKey::generate();
let issuer = xdsa::SecretKey::generate();
let now = SystemTime::now()
.duration_since(UNIX_EPOCH)
.unwrap_or(Duration::ZERO)
.as_secs();
let template = Certificate {
subject: Name::new().cn("Alice Identity"),
issuer: Name::new().cn("Root"),
not_before: now,
not_after: now + 3600,
role: Role::Leaf,
..Default::default()
};
let mut der = xdsa::issue_cert_der(&subject.public_key(), &issuer, &template).unwrap();
der.extend_from_slice(&[0xde, 0xad]);
let result = xdsa::verify_cert_der(&der, &issuer.public_key(), ValidityCheck::Now);
assert!(result.is_err());
}
#[test]
fn test_verify_rejects_trailing_pem_data() {
let subject = xdsa::SecretKey::generate();
let issuer = xdsa::SecretKey::generate();
let now = SystemTime::now()
.duration_since(UNIX_EPOCH)
.unwrap_or(Duration::ZERO)
.as_secs();
let template = Certificate {
subject: Name::new().cn("Alice Identity"),
issuer: Name::new().cn("Root"),
not_before: now,
not_after: now + 3600,
role: Role::Leaf,
..Default::default()
};
let mut pem = xdsa::issue_cert_pem(&subject.public_key(), &issuer, &template).unwrap();
pem.push_str("TRAILING");
let result = xdsa::verify_cert_pem(&pem, &issuer.public_key(), ValidityCheck::Now);
assert!(result.is_err());
}
#[test]
fn test_convert_path_len_rejects_large_values() {
assert!(convert_path_len(Some(256)).is_err());
assert_eq!(convert_path_len(Some(255)).unwrap(), Some(255));
assert_eq!(convert_path_len(None).unwrap(), None);
}
#[test]
fn test_verify_rejects_future_cert_by_time_policy() {
let subject = xdsa::SecretKey::generate();
let issuer = xdsa::SecretKey::generate();
let now = SystemTime::now()
.duration_since(UNIX_EPOCH)
.unwrap_or(Duration::ZERO)
.as_secs();
let template = Certificate {
subject: Name::new().cn("Alice Identity"),
issuer: Name::new().cn("Root"),
not_before: now + 3600,
not_after: now + 7200,
role: Role::Leaf,
..Default::default()
};
let der = xdsa::issue_cert_der(&subject.public_key(), &issuer, &template).unwrap();
let result = xdsa::verify_cert_der(&der, &issuer.public_key(), ValidityCheck::At(now));
assert!(result.is_err());
}
#[test]
fn test_verify_allows_valid_cert_without_time_policy() {
let subject = xdsa::SecretKey::generate();
let issuer = xdsa::SecretKey::generate();
let now = SystemTime::now()
.duration_since(UNIX_EPOCH)
.unwrap_or(Duration::ZERO)
.as_secs();
let template = Certificate {
subject: Name::new().cn("Alice Identity"),
issuer: Name::new().cn("Root"),
not_before: now,
not_after: now + 3600,
role: Role::Leaf,
..Default::default()
};
let der = xdsa::issue_cert_der(&subject.public_key(), &issuer, &template).unwrap();
let result = xdsa::verify_cert_der(&der, &issuer.public_key(), ValidityCheck::Disabled);
assert!(result.is_ok());
}
#[test]
fn test_verify_xdsa_ee_rejects_key_agreement_usage() {
let subject = xdsa::SecretKey::generate();
let issuer = xdsa::SecretKey::generate();
let now = SystemTime::now()
.duration_since(UNIX_EPOCH)
.unwrap_or(Duration::ZERO)
.as_secs();
let template = Certificate {
subject: Name::new().cn("Alice Identity"),
issuer: Name::new().cn("Root"),
not_before: now,
not_after: now + 3600,
role: Role::Leaf,
..Default::default()
};
let mut cert = build_xdsa_cert(&subject.public_key(), &issuer, &template).unwrap();
let wrong_ku = KeyUsage(KeyUsages::KeyAgreement.into());
for ext in cert.tbs_certificate.extensions.as_mut().unwrap() {
if ext.extn_id == ObjectIdentifier::new_unwrap("2.5.29.15") {
ext.extn_value = OctetString::new(wrong_ku.to_der().unwrap()).unwrap();
break;
}
}
let tbs_der = cert.tbs_certificate.to_der().unwrap();
cert.signature = BitString::from_bytes(&issuer.sign(&tbs_der).to_bytes()).unwrap();
let der = cert.to_der().unwrap();
let result = xdsa::verify_cert_der(&der, &issuer.public_key(), ValidityCheck::Now);
assert!(result.is_err());
}
#[test]
#[cfg(feature = "xhpke")]
fn test_verify_xhpke_rejects_digital_signature_usage() {
let subject = xhpke::SecretKey::generate();
let issuer = xdsa::SecretKey::generate();
let now = SystemTime::now()
.duration_since(UNIX_EPOCH)
.unwrap_or(Duration::ZERO)
.as_secs();
let template = Certificate {
subject: Name::new().cn("Alice Encryption"),
issuer: Name::new().cn("Alice Identity"),
not_before: now,
not_after: now + 3600,
role: Role::Leaf,
..Default::default()
};
let mut cert = build_xhpke_cert(&subject.public_key(), &issuer, &template).unwrap();
let wrong_ku = KeyUsage(KeyUsages::DigitalSignature.into());
for ext in cert.tbs_certificate.extensions.as_mut().unwrap() {
if ext.extn_id == ObjectIdentifier::new_unwrap("2.5.29.15") {
ext.extn_value = OctetString::new(wrong_ku.to_der().unwrap()).unwrap();
break;
}
}
let tbs_der = cert.tbs_certificate.to_der().unwrap();
cert.signature = BitString::from_bytes(&issuer.sign(&tbs_der).to_bytes()).unwrap();
let der = cert.to_der().unwrap();
let result = xhpke::verify_cert_der(&der, &issuer.public_key(), ValidityCheck::Now);
assert!(result.is_err());
}
#[test]
fn test_verify_xdsa_ee_accepts_extra_key_usage_flags() {
let subject = xdsa::SecretKey::generate();
let issuer = xdsa::SecretKey::generate();
let now = SystemTime::now()
.duration_since(UNIX_EPOCH)
.unwrap_or(Duration::ZERO)
.as_secs();
let template = Certificate {
subject: Name::new().cn("Alice Identity"),
issuer: Name::new().cn("Root"),
not_before: now,
not_after: now + 3600,
role: Role::Leaf,
..Default::default()
};
let mut cert = build_xdsa_cert(&subject.public_key(), &issuer, &template).unwrap();
let mixed_ku = KeyUsage(KeyUsages::DigitalSignature | KeyUsages::KeyCertSign);
for ext in cert.tbs_certificate.extensions.as_mut().unwrap() {
if ext.extn_id == ObjectIdentifier::new_unwrap("2.5.29.15") {
ext.extn_value = OctetString::new(mixed_ku.to_der().unwrap()).unwrap();
break;
}
}
let tbs_der = cert.tbs_certificate.to_der().unwrap();
cert.signature = BitString::from_bytes(&issuer.sign(&tbs_der).to_bytes()).unwrap();
let der = cert.to_der().unwrap();
let result = xdsa::verify_cert_der(&der, &issuer.public_key(), ValidityCheck::Now);
assert!(result.is_ok());
}
#[test]
fn test_verify_parses_missing_basic_constraints_as_end_entity() {
let subject = xdsa::SecretKey::generate();
let issuer = xdsa::SecretKey::generate();
let now = SystemTime::now()
.duration_since(UNIX_EPOCH)
.unwrap_or(Duration::ZERO)
.as_secs();
let template = Certificate {
subject: Name::new().cn("Alice Identity"),
issuer: Name::new().cn("Root"),
not_before: now,
not_after: now + 3600,
role: Role::Leaf,
..Default::default()
};
let mut cert = build_xdsa_cert(&subject.public_key(), &issuer, &template).unwrap();
cert.tbs_certificate
.extensions
.as_mut()
.unwrap()
.retain(|ext| ext.extn_id != ObjectIdentifier::new_unwrap("2.5.29.19"));
let tbs_der = cert.tbs_certificate.to_der().unwrap();
cert.signature = BitString::from_bytes(&issuer.sign(&tbs_der).to_bytes()).unwrap();
let der = cert.to_der().unwrap();
let parsed = xdsa::verify_cert_der(&der, &issuer.public_key(), ValidityCheck::Now).unwrap();
assert!(matches!(parsed.cert.role, Role::Leaf));
}
#[test]
fn test_verify_rejects_xdsa_ca_wrong_key_usage() {
let subject = xdsa::SecretKey::generate();
let issuer = xdsa::SecretKey::generate();
let now = SystemTime::now()
.duration_since(UNIX_EPOCH)
.unwrap_or(Duration::ZERO)
.as_secs();
let template = Certificate {
subject: Name::new().cn("Alice Identity"),
issuer: Name::new().cn("Root"),
not_before: now,
not_after: now + 3600,
role: Role::Authority { path_len: Some(0) },
..Default::default()
};
let mut cert = build_xdsa_cert(&subject.public_key(), &issuer, &template).unwrap();
let wrong_ku = KeyUsage(KeyUsages::DigitalSignature.into());
for ext in cert.tbs_certificate.extensions.as_mut().unwrap() {
if ext.extn_id == ObjectIdentifier::new_unwrap("2.5.29.15") {
ext.extn_value = OctetString::new(wrong_ku.to_der().unwrap()).unwrap();
break;
}
}
let tbs_der = cert.tbs_certificate.to_der().unwrap();
cert.signature = BitString::from_bytes(&issuer.sign(&tbs_der).to_bytes()).unwrap();
let der = cert.to_der().unwrap();
let result = xdsa::verify_cert_der(&der, &issuer.public_key(), ValidityCheck::Now);
assert!(result.is_err());
}
#[test]
fn test_verify_rejects_binary_subject_attribute_values() {
let subject = xdsa::SecretKey::generate();
let issuer = xdsa::SecretKey::generate();
let now = SystemTime::now()
.duration_since(UNIX_EPOCH)
.unwrap_or(Duration::ZERO)
.as_secs();
let template = Certificate {
subject: Name::new().cn("Alice Identity"),
issuer: Name::new().cn("Root"),
not_before: now,
not_after: now + 3600,
role: Role::Leaf,
..Default::default()
};
let mut cert = build_xdsa_cert(&subject.public_key(), &issuer, &template).unwrap();
let mut set = SetOfVec::new();
set.insert(AttributeTypeAndValue {
oid: name::OID_CN,
value: Any::new(Tag::OctetString, vec![1, 2, 3]).unwrap(),
})
.unwrap();
cert.tbs_certificate.subject = RdnSequence(vec![RelativeDistinguishedName::from(set)]);
let tbs_der = cert.tbs_certificate.to_der().unwrap();
cert.signature = BitString::from_bytes(&issuer.sign(&tbs_der).to_bytes()).unwrap();
let der = cert.to_der().unwrap();
let result = xdsa::verify_cert_der(&der, &issuer.public_key(), ValidityCheck::Now);
assert!(result.is_err());
}
#[test]
#[cfg(feature = "xhpke")]
fn test_verify_with_issuer_rejects_non_ca_issuer() {
let issuer_ee = xdsa::SecretKey::generate();
let subject_ee = xhpke::SecretKey::generate();
let now = SystemTime::now()
.duration_since(UNIX_EPOCH)
.unwrap_or(Duration::ZERO)
.as_secs();
let issuer_template = Certificate {
subject: Name::new().cn("Issuer EE"),
issuer: Name::new().cn("Issuer EE"),
not_before: now,
not_after: now + 3600,
role: Role::Leaf,
..Default::default()
};
let issuer_cert_pem =
xdsa::issue_cert_pem(&issuer_ee.public_key(), &issuer_ee, &issuer_template).unwrap();
let issuer_cert = xdsa::verify_cert_pem(
&issuer_cert_pem,
&issuer_ee.public_key(),
ValidityCheck::Now,
)
.unwrap();
let leaf_template = Certificate {
subject: Name::new().cn("Leaf HPKE"),
issuer: Name::new().cn("Issuer EE"),
not_before: now,
not_after: now + 3600,
role: Role::Leaf,
..Default::default()
};
let leaf_pem =
xhpke::issue_cert_pem(&subject_ee.public_key(), &issuer_ee, &leaf_template).unwrap();
let result =
xhpke::verify_cert_pem_with_issuer(&leaf_pem, &issuer_cert, ValidityCheck::Now);
assert!(result.is_err());
}
#[test]
fn test_verify_rejects_basic_constraints_pathlen_without_ca() {
let subject = xdsa::SecretKey::generate();
let issuer = xdsa::SecretKey::generate();
let now = SystemTime::now()
.duration_since(UNIX_EPOCH)
.unwrap_or(Duration::ZERO)
.as_secs();
let template = Certificate {
subject: Name::new().cn("Alice Identity"),
issuer: Name::new().cn("Root"),
not_before: now,
not_after: now + 3600,
role: Role::Leaf,
..Default::default()
};
let mut cert = build_xdsa_cert(&subject.public_key(), &issuer, &template).unwrap();
for ext in cert.tbs_certificate.extensions.as_mut().unwrap() {
if ext.extn_id == ObjectIdentifier::new_unwrap("2.5.29.19") {
let bc = BasicConstraints {
ca: false,
path_len_constraint: Some(0),
};
ext.extn_value = OctetString::new(bc.to_der().unwrap()).unwrap();
break;
}
}
let tbs_der = cert.tbs_certificate.to_der().unwrap();
cert.signature = BitString::from_bytes(&issuer.sign(&tbs_der).to_bytes()).unwrap();
let der = cert.to_der().unwrap();
let result = xdsa::verify_cert_der(&der, &issuer.public_key(), ValidityCheck::Now);
assert!(result.is_err());
}
#[test]
fn test_verify_with_issuer_enforces_path_len_for_ca_child() {
let issuer_sk = xdsa::SecretKey::generate();
let child_sk = xdsa::SecretKey::generate();
let now = SystemTime::now()
.duration_since(UNIX_EPOCH)
.unwrap_or(Duration::ZERO)
.as_secs();
let issuer_template = Certificate {
subject: Name::new().cn("Issuer"),
issuer: Name::new().cn("Issuer"),
not_before: now,
not_after: now + 3600,
role: Role::Authority { path_len: Some(0) },
..Default::default()
};
let issuer_pem =
xdsa::issue_cert_pem(&issuer_sk.public_key(), &issuer_sk, &issuer_template).unwrap();
let issuer_cert =
xdsa::verify_cert_pem(&issuer_pem, &issuer_sk.public_key(), ValidityCheck::Now)
.unwrap();
let child_template = Certificate {
subject: Name::new().cn("Child CA"),
issuer: Name::new().cn("Issuer"),
not_before: now,
not_after: now + 3600,
role: Role::Authority { path_len: Some(0) },
..Default::default()
};
let child_pem =
xdsa::issue_cert_pem(&child_sk.public_key(), &issuer_sk, &child_template).unwrap();
let result =
xdsa::verify_cert_pem_with_issuer(&child_pem, &issuer_cert, ValidityCheck::Now);
assert!(result.is_err());
}
#[test]
fn test_verify_with_issuer_allows_path_len_zero_for_ee_child() {
let issuer_sk = xdsa::SecretKey::generate();
let child_sk = xdsa::SecretKey::generate();
let now = SystemTime::now()
.duration_since(UNIX_EPOCH)
.unwrap_or(Duration::ZERO)
.as_secs();
let issuer_template = Certificate {
subject: Name::new().cn("Issuer"),
issuer: Name::new().cn("Issuer"),
not_before: now,
not_after: now + 3600,
role: Role::Authority { path_len: Some(0) },
..Default::default()
};
let issuer_pem =
xdsa::issue_cert_pem(&issuer_sk.public_key(), &issuer_sk, &issuer_template).unwrap();
let issuer_cert =
xdsa::verify_cert_pem(&issuer_pem, &issuer_sk.public_key(), ValidityCheck::Now)
.unwrap();
let child_template = Certificate {
subject: Name::new().cn("Child EE"),
issuer: Name::new().cn("Issuer"),
not_before: now,
not_after: now + 3600,
role: Role::Leaf,
..Default::default()
};
let child_pem =
xdsa::issue_cert_pem(&child_sk.public_key(), &issuer_sk, &child_template).unwrap();
let result =
xdsa::verify_cert_pem_with_issuer(&child_pem, &issuer_cert, ValidityCheck::Now);
assert!(result.is_ok());
}
#[test]
fn test_verify_with_issuer_rejects_dn_name_mismatch() {
let issuer_sk = xdsa::SecretKey::generate();
let child_sk = xdsa::SecretKey::generate();
let now = SystemTime::now()
.duration_since(UNIX_EPOCH)
.unwrap_or(Duration::ZERO)
.as_secs();
let issuer_template = Certificate {
subject: Name::new().cn("Issuer Subject"),
issuer: Name::new().cn("Issuer Subject"),
not_before: now,
not_after: now + 3600,
role: Role::Authority { path_len: Some(0) },
..Default::default()
};
let issuer_pem =
xdsa::issue_cert_pem(&issuer_sk.public_key(), &issuer_sk, &issuer_template).unwrap();
let issuer_cert =
xdsa::verify_cert_pem(&issuer_pem, &issuer_sk.public_key(), ValidityCheck::Now)
.unwrap();
let child_template = Certificate {
subject: Name::new().cn("Child EE"),
issuer: Name::new().cn("Fake Issuer Name"),
not_before: now,
not_after: now + 3600,
role: Role::Leaf,
..Default::default()
};
let child_pem =
xdsa::issue_cert_pem(&child_sk.public_key(), &issuer_sk, &child_template).unwrap();
let result =
xdsa::verify_cert_pem_with_issuer(&child_pem, &issuer_cert, ValidityCheck::Now);
assert!(result.is_err());
}
#[test]
fn test_verify_xdsa_der_with_issuer() {
let issuer_sk = xdsa::SecretKey::generate();
let child_sk = xdsa::SecretKey::generate();
let now = SystemTime::now()
.duration_since(UNIX_EPOCH)
.unwrap_or(Duration::ZERO)
.as_secs();
let issuer_template = Certificate {
subject: Name::new().cn("Issuer"),
issuer: Name::new().cn("Issuer"),
not_before: now,
not_after: now + 3600,
role: Role::Authority { path_len: None },
..Default::default()
};
let issuer_der =
xdsa::issue_cert_der(&issuer_sk.public_key(), &issuer_sk, &issuer_template).unwrap();
let issuer_cert =
xdsa::verify_cert_der(&issuer_der, &issuer_sk.public_key(), ValidityCheck::Now)
.unwrap();
let child_template = Certificate {
subject: Name::new().cn("Child EE"),
issuer: Name::new().cn("Issuer"),
not_before: now,
not_after: now + 3600,
role: Role::Leaf,
..Default::default()
};
let child_der =
xdsa::issue_cert_der(&child_sk.public_key(), &issuer_sk, &child_template).unwrap();
let result =
xdsa::verify_cert_der_with_issuer(&child_der, &issuer_cert, ValidityCheck::Now);
assert!(result.is_ok());
}
#[test]
#[cfg(feature = "xhpke")]
fn test_verify_xhpke_der_with_issuer() {
let issuer_sk = xdsa::SecretKey::generate();
let child_sk = xhpke::SecretKey::generate();
let now = SystemTime::now()
.duration_since(UNIX_EPOCH)
.unwrap_or(Duration::ZERO)
.as_secs();
let issuer_template = Certificate {
subject: Name::new().cn("Issuer"),
issuer: Name::new().cn("Issuer"),
not_before: now,
not_after: now + 3600,
role: Role::Authority { path_len: None },
..Default::default()
};
let issuer_der =
xdsa::issue_cert_der(&issuer_sk.public_key(), &issuer_sk, &issuer_template).unwrap();
let issuer_cert =
xdsa::verify_cert_der(&issuer_der, &issuer_sk.public_key(), ValidityCheck::Now)
.unwrap();
let child_template = Certificate {
subject: Name::new().cn("Child HPKE"),
issuer: Name::new().cn("Issuer"),
not_before: now,
not_after: now + 3600,
role: Role::Leaf,
..Default::default()
};
let child_der =
xhpke::issue_cert_der(&child_sk.public_key(), &issuer_sk, &child_template).unwrap();
let result =
xhpke::verify_cert_der_with_issuer(&child_der, &issuer_cert, ValidityCheck::Now);
assert!(result.is_ok());
}
#[test]
fn test_verify_rejects_unknown_key_usage_bits() {
let subject = xdsa::SecretKey::generate();
let issuer = xdsa::SecretKey::generate();
let now = SystemTime::now()
.duration_since(UNIX_EPOCH)
.unwrap_or(Duration::ZERO)
.as_secs();
let template = Certificate {
subject: Name::new().cn("Alice Identity"),
issuer: Name::new().cn("Root"),
not_before: now,
not_after: now + 3600,
role: Role::Leaf,
..Default::default()
};
let mut cert = build_xdsa_cert(&subject.public_key(), &issuer, &template).unwrap();
let raw_ku = vec![0x03, 0x04, 0x06, 0x00, 0x02, 0x00];
for ext in cert.tbs_certificate.extensions.as_mut().unwrap() {
if ext.extn_id == ObjectIdentifier::new_unwrap("2.5.29.15") {
ext.extn_value = OctetString::new(raw_ku).unwrap();
break;
}
}
let tbs_der = cert.tbs_certificate.to_der().unwrap();
cert.signature = BitString::from_bytes(&issuer.sign(&tbs_der).to_bytes()).unwrap();
let der = cert.to_der().unwrap();
let result = xdsa::verify_cert_der(&der, &issuer.public_key(), ValidityCheck::Now);
assert!(result.is_err());
}
#[test]
fn test_verify_rejects_duplicate_standard_extension_oid() {
let subject = xdsa::SecretKey::generate();
let issuer = xdsa::SecretKey::generate();
let now = SystemTime::now()
.duration_since(UNIX_EPOCH)
.unwrap_or(Duration::ZERO)
.as_secs();
let template = Certificate {
subject: Name::new().cn("Alice Identity"),
issuer: Name::new().cn("Root"),
not_before: now,
not_after: now + 3600,
role: Role::Leaf,
..Default::default()
};
let mut cert = build_xdsa_cert(&subject.public_key(), &issuer, &template).unwrap();
let duplicate = cert.tbs_certificate.extensions.as_ref().unwrap()[0].clone();
cert.tbs_certificate
.extensions
.as_mut()
.unwrap()
.push(duplicate);
let tbs_der = cert.tbs_certificate.to_der().unwrap();
cert.signature = BitString::from_bytes(&issuer.sign(&tbs_der).to_bytes()).unwrap();
let der = cert.to_der().unwrap();
let result = xdsa::verify_cert_der(&der, &issuer.public_key(), ValidityCheck::Now);
assert!(result.is_err());
}
#[test]
fn test_verify_rejects_duplicate_custom_extension_oid() {
let subject = xdsa::SecretKey::generate();
let issuer = xdsa::SecretKey::generate();
let now = SystemTime::now()
.duration_since(UNIX_EPOCH)
.unwrap_or(Duration::ZERO)
.as_secs();
let custom_oid = ObjectIdentifier::new("1.3.6.1.4.1.62253.7.7").unwrap();
let template = Certificate {
subject: Name::new().cn("Alice Identity"),
issuer: Name::new().cn("Root"),
not_before: now,
not_after: now + 3600,
role: Role::Leaf,
extensions: vec![Extension {
oid: custom_oid,
critical: false,
value: vec![0x05, 0x00],
}],
..Default::default()
};
let mut cert = build_xdsa_cert(&subject.public_key(), &issuer, &template).unwrap();
let duplicate = cert
.tbs_certificate
.extensions
.as_ref()
.unwrap()
.last()
.unwrap()
.clone();
cert.tbs_certificate
.extensions
.as_mut()
.unwrap()
.push(duplicate);
let tbs_der = cert.tbs_certificate.to_der().unwrap();
cert.signature = BitString::from_bytes(&issuer.sign(&tbs_der).to_bytes()).unwrap();
let der = cert.to_der().unwrap();
let result = xdsa::verify_cert_der(&der, &issuer.public_key(), ValidityCheck::Now);
assert!(result.is_err());
}
#[test]
fn test_verify_rejects_spki_nonzero_unused_bits() {
let subject = xdsa::SecretKey::generate();
let issuer = xdsa::SecretKey::generate();
let now = SystemTime::now()
.duration_since(UNIX_EPOCH)
.unwrap_or(Duration::ZERO)
.as_secs();
let template = Certificate {
subject: Name::new().cn("Alice Identity"),
issuer: Name::new().cn("Root"),
not_before: now,
not_after: now + 3600,
role: Role::Leaf,
..Default::default()
};
let mut cert = build_xdsa_cert(&subject.public_key(), &issuer, &template).unwrap();
let pk_bytes = subject.public_key().to_bytes();
cert.tbs_certificate
.subject_public_key_info
.subject_public_key = BitString::new(1, &pk_bytes).unwrap();
let tbs_der = cert.tbs_certificate.to_der().unwrap();
cert.signature = BitString::from_bytes(&issuer.sign(&tbs_der).to_bytes()).unwrap();
let der = cert.to_der().unwrap();
let result = xdsa::verify_cert_der(&der, &issuer.public_key(), ValidityCheck::Now);
assert!(result.is_err());
}
}