use crate::error::TrustError;
use der::{Decode, Encode};
use std::path::Path;
use x509_cert::Certificate;
fn basic_constraints(cert: &Certificate) -> Result<(bool, Option<u64>), TrustError> {
let bc_oid = const_oid::ObjectIdentifier::new_unwrap("2.5.29.19");
let Some(extensions) = &cert.tbs_certificate.extensions else {
return Ok((false, None));
};
for ext in extensions.iter() {
if ext.extn_id == bc_oid {
return crate::der_utils::parse_basic_constraints(ext.extn_value.as_bytes())
.map_err(TrustError::CertificateParse);
}
}
Ok((false, None))
}
fn is_self_issued(cert: &Certificate) -> bool {
cert.tbs_certificate.subject == cert.tbs_certificate.issuer
}
fn key_cert_sign(cert: &Certificate) -> Result<Option<bool>, TrustError> {
let ku_oid = const_oid::ObjectIdentifier::new_unwrap("2.5.29.15");
let Some(extensions) = &cert.tbs_certificate.extensions else {
return Ok(None);
};
let Some(ext) = extensions.iter().find(|e| e.extn_id == ku_oid) else {
return Ok(None);
};
parse_key_cert_sign_bit(ext.extn_value.as_bytes()).map(Some)
}
fn parse_key_cert_sign_bit(extn_value: &[u8]) -> Result<bool, TrustError> {
let (tag, body, rest) = crate::der_utils::parse_tlv_with_rest(extn_value)
.map_err(|e| TrustError::CertificateParse(format!("keyUsage: {e}")))?;
if !rest.is_empty() {
return Err(TrustError::CertificateParse(
"keyUsage: trailing data after BIT STRING".into(),
));
}
if tag != 0x03 {
return Err(TrustError::CertificateParse(format!(
"keyUsage: expected BIT STRING (0x03), got 0x{tag:02x}"
)));
}
if body.is_empty() {
return Err(TrustError::CertificateParse(
"keyUsage: BIT STRING missing the unused-bits octet".into(),
));
}
let unused_bits = body[0];
if unused_bits > 7 {
return Err(TrustError::CertificateParse(format!(
"keyUsage: invalid unused-bits count {unused_bits} (must be 0..=7)"
)));
}
let bit_bytes = &body[1..];
if bit_bytes.is_empty() {
return Err(TrustError::CertificateParse(
"keyUsage: BIT STRING has no content octets (no key-usage bits set)".into(),
));
}
if unused_bits > 0 {
let last = bit_bytes[bit_bytes.len() - 1];
let pad_mask = (1u8 << unused_bits) - 1;
if last & pad_mask != 0 {
return Err(TrustError::CertificateParse(format!(
"keyUsage: BIT STRING has {unused_bits} non-zero unused bit(s) (not valid DER)"
)));
}
}
Ok((bit_bytes[0] >> 2) & 1 == 1)
}
fn validate_intermediate_ca_extensions(cert: &Certificate, label: &str) -> Result<(), TrustError> {
let (is_ca, _) = basic_constraints(cert)?;
if !is_ca {
return Err(TrustError::SignatureVerification(format!(
"{label} is not a CA (basicConstraints cA is not TRUE)"
)));
}
match key_cert_sign(cert)? {
Some(true) | None => Ok(()),
Some(false) => Err(TrustError::SignatureVerification(format!(
"{label} keyUsage is present but does not assert keyCertSign"
))),
}
}
fn enforce_path_len(
issuer: &Certificate,
below: &[Certificate],
label: &str,
) -> Result<(), TrustError> {
let (_is_ca, path_len) = basic_constraints(issuer).map_err(|e| {
TrustError::SignatureVerification(format!(
"failed to parse basicConstraints for {label}: {e}"
))
})?;
let Some(max_depth) = path_len else {
return Ok(());
};
let mut subordinate_ca_count = 0u64;
for cert in below {
let (is_ca, _) = basic_constraints(cert).map_err(|e| {
TrustError::SignatureVerification(format!(
"failed to parse basicConstraints below {label}: {e}"
))
})?;
if is_ca && !is_self_issued(cert) {
subordinate_ca_count += 1;
}
}
if subordinate_ca_count > max_depth {
return Err(TrustError::SignatureVerification(format!(
"pathLenConstraint ({max_depth}) exceeded for {label}: {subordinate_ca_count} non-self-issued subordinate CA certs below"
)));
}
Ok(())
}
#[derive(Clone)]
struct TrustAnchor {
cert: Certificate,
der: Vec<u8>,
}
#[derive(Clone)]
pub struct TrustStore {
anchors: Vec<TrustAnchor>,
label: Option<String>,
signature_policy: crate::crypto::verify::SignaturePolicy,
}
impl TrustStore {
pub fn new() -> Self {
Self {
anchors: Vec::new(),
label: None,
signature_policy: crate::crypto::verify::SignaturePolicy::default(),
}
}
pub fn with_label(mut self, label: impl Into<String>) -> Self {
self.label = Some(label.into());
self
}
pub fn with_signature_policy(mut self, policy: crate::crypto::verify::SignaturePolicy) -> Self {
self.signature_policy = policy;
self
}
pub fn allow_legacy_signatures(mut self) -> Self {
self.signature_policy = crate::crypto::verify::SignaturePolicy::allow_legacy();
self
}
pub fn signature_policy(&self) -> crate::crypto::verify::SignaturePolicy {
self.signature_policy
}
pub fn label(&self) -> Option<&str> {
self.label.as_deref()
}
pub fn len(&self) -> usize {
self.anchors.len()
}
pub fn is_empty(&self) -> bool {
self.anchors.is_empty()
}
pub fn from_pem_file(path: impl AsRef<Path>) -> Result<Self, TrustError> {
let data = std::fs::read(path.as_ref()).map_err(TrustError::Io)?;
let mut store = Self::new();
store.add_pem_data(&data)?;
Ok(store)
}
pub fn from_pem_directory(dir: impl AsRef<Path>) -> Result<Self, TrustError> {
let dir = dir.as_ref();
if !dir.is_dir() {
return Err(TrustError::NotADirectory(dir.display().to_string()));
}
let mut store = Self::new();
let mut entries: Vec<_> = std::fs::read_dir(dir)
.map_err(TrustError::Io)?
.filter_map(|e| e.ok())
.collect();
entries.sort_by_key(|e| e.file_name());
for entry in entries {
let path = entry.path();
if let Some(ext) = path.extension() {
let ext = ext.to_string_lossy().to_lowercase();
if ext == "pem" || ext == "crt" || ext == "cer" {
if let Ok(data) = std::fs::read(&path) {
let _ = store.add_pem_data(&data);
}
}
}
}
Ok(store)
}
pub fn add_der_certificate(&mut self, der: &[u8]) -> Result<(), TrustError> {
let cert = Certificate::from_der(der)
.map_err(|e| TrustError::CertificateParse(format!("DER decode failed: {e}")))?;
self.anchors.push(TrustAnchor {
cert,
der: der.to_vec(),
});
Ok(())
}
pub fn add_certificate(&mut self, cert: Certificate) -> Result<(), TrustError> {
let der = cert
.to_der()
.map_err(|e| TrustError::CertificateParse(format!("DER encode failed: {e}")))?;
self.anchors.push(TrustAnchor { cert, der });
Ok(())
}
pub fn add_pem_data(&mut self, pem_data: &[u8]) -> Result<(), TrustError> {
let pem_str = std::str::from_utf8(pem_data)
.map_err(|e| TrustError::CertificateParse(format!("invalid UTF-8 in PEM: {e}")))?;
let mut found_any = false;
let mut remaining = pem_str;
while let Some(begin_pos) = remaining.find("-----BEGIN CERTIFICATE-----") {
let block_start = &remaining[begin_pos..];
if let Some(end_pos) = block_start.find("-----END CERTIFICATE-----") {
let end = end_pos + "-----END CERTIFICATE-----".len();
let pem_block = &block_start[..end];
let b64: String = pem_block
.lines()
.filter(|line| !line.starts_with("-----"))
.collect();
use base64::Engine;
let der_bytes = base64::engine::general_purpose::STANDARD
.decode(&b64)
.map_err(|e| {
TrustError::CertificateParse(format!("base64 decode error: {e}"))
})?;
self.add_der_certificate(&der_bytes)?;
found_any = true;
remaining = &block_start[end..];
} else {
break;
}
}
if !found_any {
return Err(TrustError::CertificateParse(
"no CERTIFICATE blocks found in PEM data".into(),
));
}
Ok(())
}
pub fn contains_der(&self, cert_der: &[u8]) -> bool {
self.anchors.iter().any(|a| a.der == cert_der)
}
pub fn find_issuer(&self, cert: &Certificate) -> Option<&Certificate> {
let issuer = &cert.tbs_certificate.issuer;
self.anchors.iter().find_map(|anchor| {
if &anchor.cert.tbs_certificate.subject == issuer {
Some(&anchor.cert)
} else {
None
}
})
}
fn find_all_issuers(&self, cert: &Certificate) -> Vec<&Certificate> {
let issuer = &cert.tbs_certificate.issuer;
self.anchors
.iter()
.filter_map(|anchor| {
if &anchor.cert.tbs_certificate.subject == issuer {
Some(&anchor.cert)
} else {
None
}
})
.collect()
}
pub fn find_issuer_for_der(&self, cert_der: &[u8]) -> Option<&Certificate> {
let cert = Certificate::from_der(cert_der).ok()?;
self.find_issuer(&cert)
}
pub fn certificates(&self) -> impl Iterator<Item = &Certificate> {
self.anchors.iter().map(|a| &a.cert)
}
pub fn certificates_der(&self) -> impl Iterator<Item = &[u8]> {
self.anchors.iter().map(|a| a.der.as_slice())
}
pub fn verify_chain(
&self,
chain: &[Certificate],
validation_time: Option<der::DateTime>,
) -> Result<&Certificate, TrustError> {
let policy = &self.signature_policy;
if chain.is_empty() {
return Err(TrustError::EmptyChain);
}
if let Some(time) = validation_time {
for (i, cert) in chain.iter().enumerate() {
let validity = &cert.tbs_certificate.validity;
if time < validity.not_before.to_date_time() {
return Err(TrustError::NotYetValid {
index: i,
not_before: validity.not_before.to_date_time(),
});
}
if time > validity.not_after.to_date_time() {
return Err(TrustError::Expired {
index: i,
not_after: validity.not_after.to_date_time(),
});
}
}
}
for i in 0..chain.len().saturating_sub(1) {
let cert = &chain[i];
let issuer_cert = &chain[i + 1];
if cert.tbs_certificate.issuer != issuer_cert.tbs_certificate.subject {
return Err(TrustError::ChainBroken {
index: i,
expected_issuer: format!("{}", cert.tbs_certificate.issuer),
found_subject: format!("{}", issuer_cert.tbs_certificate.subject),
});
}
crate::crypto::verify::verify_certificate_signature_with_policy(
cert,
issuer_cert,
policy,
)?;
validate_intermediate_ca_extensions(
issuer_cert,
&format!("issuer at index {}", i + 1),
)?;
enforce_path_len(
issuer_cert,
&chain[0..=i],
&format!("intermediate index {}", i + 1),
)?;
}
let last = chain.last().unwrap();
if last.tbs_certificate.issuer == last.tbs_certificate.subject {
let last_der = last.to_der().map_err(|e| {
TrustError::CertificateParse(format!(
"failed to encode certificate for anchor lookup: {e}"
))
})?;
if let Some(anchor) = self.anchors.iter().find(|a| a.der == last_der) {
crate::crypto::verify::verify_certificate_signature_with_policy(
last, last, policy,
)?;
return Ok(&anchor.cert);
}
}
let candidates = self.find_all_issuers(last);
if candidates.is_empty() {
return Err(TrustError::UntrustedRoot {
issuer: format!("{}", last.tbs_certificate.issuer),
});
}
let mut last_err = None;
for anchor in &candidates {
match crate::crypto::verify::verify_certificate_signature_with_policy(
last, anchor, policy,
) {
Ok(()) => {
enforce_path_len(anchor, chain, "trust anchor")?;
if let Some(time) = validation_time {
let validity = &anchor.tbs_certificate.validity;
if time < validity.not_before.to_date_time() {
return Err(TrustError::NotYetValid {
index: chain.len(),
not_before: validity.not_before.to_date_time(),
});
}
if time > validity.not_after.to_date_time() {
return Err(TrustError::Expired {
index: chain.len(),
not_after: validity.not_after.to_date_time(),
});
}
}
return Ok(anchor);
}
Err(e) => {
last_err = Some(e);
}
}
}
Err(last_err.unwrap())
}
}
impl Default for TrustStore {
fn default() -> Self {
Self::new()
}
}
impl std::fmt::Debug for TrustStore {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("TrustStore")
.field("label", &self.label)
.field("anchors", &self.anchors.len())
.finish()
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::crypto::verify::SignaturePolicy;
fn sha1_self_signed_root() -> Certificate {
use der::asn1::BitString;
use der::{Any, Encode};
use rsa::pkcs1v15::SigningKey;
use rsa::signature::Keypair;
use rsa::{Pkcs1v15Sign, RsaPrivateKey};
use sha1::{Digest, Sha1};
use sha2::Sha256;
use spki::AlgorithmIdentifierOwned;
use x509_cert::builder::{Builder, CertificateBuilder, Profile};
use x509_cert::name::Name;
use x509_cert::serial_number::SerialNumber;
use x509_cert::spki::SubjectPublicKeyInfoOwned;
use x509_cert::time::Validity;
let mut rng = rand::thread_rng();
let key = RsaPrivateKey::new(&mut rng, 2048).expect("RSA keygen");
let signer = SigningKey::<Sha256>::new(key.clone());
let spki =
SubjectPublicKeyInfoOwned::from_key(signer.verifying_key()).expect("SPKI from key");
let serial = SerialNumber::new(&[0x01]).unwrap();
let validity =
Validity::from_now(std::time::Duration::from_secs(3650 * 24 * 3600)).unwrap();
let subject: Name = "CN=Legacy SHA-1 Root,O=tsp-ltv tests".parse().unwrap();
let base = CertificateBuilder::new(Profile::Root, serial, validity, subject, spki, &signer)
.expect("cert builder")
.build()
.expect("build base root");
let sha1_algid = AlgorithmIdentifierOwned {
oid: crate::crypto::algorithm::OID_SHA1_WITH_RSA,
parameters: Some(Any::from_der(&[0x05, 0x00]).unwrap()),
};
let mut tbs = base.tbs_certificate.clone();
tbs.signature = sha1_algid.clone();
let tbs_der = tbs.to_der().unwrap();
let hash = Sha1::digest(&tbs_der);
let sig = key
.sign(Pkcs1v15Sign::new::<Sha1>(), &hash)
.expect("SHA-1 RSA sign");
Certificate {
tbs_certificate: tbs,
signature_algorithm: sha1_algid,
signature: BitString::from_bytes(&sig).unwrap(),
}
}
#[test]
fn test_verify_chain_rejects_sha1_by_default_and_accepts_with_legacy() {
let root = sha1_self_signed_root();
assert!(crate::crypto::verify::is_weak_signature_oid(
&root.signature_algorithm.oid
));
let mut store = TrustStore::new();
store.add_certificate(root.clone()).unwrap();
let chain = [root];
let err = store.verify_chain(&chain, None).unwrap_err();
assert!(
matches!(err, TrustError::WeakAlgorithm(_)),
"strict store must reject a SHA-1-signed chain, got {err:?}"
);
let legacy_store = TrustStore::new()
.allow_legacy_signatures()
.with_label("legacy");
let mut legacy_store = legacy_store;
legacy_store.add_certificate(chain[0].clone()).unwrap();
legacy_store
.verify_chain(&chain, None)
.expect("legacy store must accept a SHA-1-signed chain");
assert_eq!(
legacy_store.signature_policy(),
SignaturePolicy::allow_legacy()
);
assert_eq!(store.signature_policy(), SignaturePolicy::strict());
}
fn issue_cert(
profile: x509_cert::builder::Profile,
subject: &str,
subject_key: &rsa::RsaPrivateKey,
issuer_signer: &rsa::pkcs1v15::SigningKey<sha2::Sha256>,
) -> Certificate {
use rsa::pkcs1v15::SigningKey;
use rsa::signature::Keypair;
use sha2::Sha256;
use x509_cert::builder::{Builder, CertificateBuilder};
use x509_cert::name::Name;
use x509_cert::serial_number::SerialNumber;
use x509_cert::spki::SubjectPublicKeyInfoOwned;
use x509_cert::time::Validity;
let subject_signer = SigningKey::<Sha256>::new(subject_key.clone());
let spki = SubjectPublicKeyInfoOwned::from_key(subject_signer.verifying_key())
.expect("SPKI from key");
let serial = SerialNumber::new(&[0x01]).unwrap();
let validity =
Validity::from_now(std::time::Duration::from_secs(3650 * 24 * 3600)).unwrap();
let subject: Name = subject.parse().unwrap();
CertificateBuilder::new(profile, serial, validity, subject, spki, issuer_signer)
.expect("cert builder")
.build()
.expect("build cert")
}
fn ca_leaf_chain(path_len: Option<u8>) -> (TrustStore, Vec<Certificate>) {
use rsa::pkcs1v15::SigningKey;
use rsa::RsaPrivateKey;
use sha2::Sha256;
use x509_cert::builder::Profile;
use x509_cert::name::Name;
let mut rng = rand::thread_rng();
let root_key = RsaPrivateKey::new(&mut rng, 2048).expect("root key");
let mid_key = RsaPrivateKey::new(&mut rng, 2048).expect("mid key");
let leaf_key = RsaPrivateKey::new(&mut rng, 2048).expect("leaf key");
let root_signer = SigningKey::<Sha256>::new(root_key.clone());
let mid_signer = SigningKey::<Sha256>::new(mid_key.clone());
let root_name = "CN=PathLen Root,O=tsp-ltv tests";
let mid_name = "CN=PathLen Mid,O=tsp-ltv tests";
let leaf_name = "CN=PathLen Leaf CA,O=tsp-ltv tests";
let root_issuer: Name = root_name.parse().unwrap();
let mid_issuer: Name = mid_name.parse().unwrap();
let root = issue_cert(Profile::Root, root_name, &root_key, &root_signer);
let mid = issue_cert(
Profile::SubCA {
issuer: root_issuer,
path_len_constraint: path_len,
},
mid_name,
&mid_key,
&root_signer,
);
let leaf = issue_cert(
Profile::SubCA {
issuer: mid_issuer,
path_len_constraint: None,
},
leaf_name,
&leaf_key,
&mid_signer,
);
let mut store = TrustStore::new();
store.add_certificate(root.clone()).unwrap();
(store, vec![leaf, mid, root])
}
#[test]
fn test_verify_chain_pathlen_rejects_ca_leaf_below_zero_constraint() {
let (store, chain) = ca_leaf_chain(Some(0));
let err = store
.verify_chain(&chain, None)
.expect_err("pathLen=0 with a CA leaf below must be rejected");
assert!(
matches!(err, TrustError::SignatureVerification(ref m) if m.contains("pathLenConstraint")),
"expected pathLenConstraint rejection, got: {err:?}"
);
}
#[test]
fn test_verify_chain_pathlen_allows_ca_leaf_within_constraint() {
let (store, chain) = ca_leaf_chain(Some(1));
store
.verify_chain(&chain, None)
.expect("pathLen=1 with one CA leaf below must be accepted");
}
fn anchor_only_chain(root_plen: Option<u8>) -> (TrustStore, Vec<Certificate>) {
use rsa::pkcs1v15::SigningKey;
use rsa::RsaPrivateKey;
use sha2::Sha256;
use x509_cert::builder::Profile;
use x509_cert::name::Name;
let mut rng = rand::thread_rng();
let root_key = RsaPrivateKey::new(&mut rng, 2048).expect("root key");
let mid_key = RsaPrivateKey::new(&mut rng, 2048).expect("mid key");
let leaf_key = RsaPrivateKey::new(&mut rng, 2048).expect("leaf key");
let root_signer = SigningKey::<Sha256>::new(root_key.clone());
let mid_signer = SigningKey::<Sha256>::new(mid_key.clone());
let root_name = "CN=Anchor Root,O=tsp-ltv tests";
let mid_name = "CN=Anchor Mid,O=tsp-ltv tests";
let leaf_name = "CN=Anchor Leaf CA,O=tsp-ltv tests";
let root_issuer: Name = root_name.parse().unwrap();
let mid_issuer: Name = mid_name.parse().unwrap();
let root = issue_cert(
Profile::SubCA {
issuer: root_issuer.clone(),
path_len_constraint: root_plen,
},
root_name,
&root_key,
&root_signer,
);
let mid = issue_cert(
Profile::SubCA {
issuer: root_issuer,
path_len_constraint: None,
},
mid_name,
&mid_key,
&root_signer,
);
let leaf = issue_cert(
Profile::SubCA {
issuer: mid_issuer,
path_len_constraint: None,
},
leaf_name,
&leaf_key,
&mid_signer,
);
let mut store = TrustStore::new();
store.add_certificate(root).unwrap();
(store, vec![leaf, mid])
}
#[test]
fn test_verify_chain_anchor_pathlen_enforced() {
let (store, chain) = anchor_only_chain(Some(0));
let err = store
.verify_chain(&chain, None)
.expect_err("anchor pathLen=0 with CAs below must be rejected");
assert!(
matches!(err, TrustError::SignatureVerification(ref m) if m.contains("pathLenConstraint") && m.contains("trust anchor")),
"expected trust-anchor pathLenConstraint rejection, got: {err:?}"
);
}
#[test]
fn test_verify_chain_anchor_pathlen_within_constraint() {
let (store, chain) = anchor_only_chain(Some(2));
store
.verify_chain(&chain, None)
.expect("anchor pathLen=2 with two CAs below must be accepted");
}
fn self_issued_ca_below_anchor(root_plen: Option<u8>) -> (TrustStore, Vec<Certificate>) {
use rsa::pkcs1v15::SigningKey;
use rsa::RsaPrivateKey;
use sha2::Sha256;
use x509_cert::builder::Profile;
use x509_cert::name::Name;
let mut rng = rand::thread_rng();
let root_key = RsaPrivateKey::new(&mut rng, 2048).expect("root key");
let rollover_key = RsaPrivateKey::new(&mut rng, 2048).expect("rollover key");
let root_signer = SigningKey::<Sha256>::new(root_key.clone());
let root_name = "CN=Rollover Root,O=tsp-ltv tests";
let root_issuer: Name = root_name.parse().unwrap();
let root = issue_cert(
Profile::SubCA {
issuer: root_issuer.clone(),
path_len_constraint: root_plen,
},
root_name,
&root_key,
&root_signer,
);
let rollover = issue_cert(
Profile::SubCA {
issuer: root_issuer,
path_len_constraint: None,
},
root_name,
&rollover_key,
&root_signer,
);
let mut store = TrustStore::new();
store.add_certificate(root).unwrap();
(store, vec![rollover])
}
#[test]
fn test_verify_chain_pathlen_ignores_self_issued_ca_below_anchor() {
let (store, chain) = self_issued_ca_below_anchor(Some(0));
store
.verify_chain(&chain, None)
.expect("self-issued CA below anchor must not consume pathLenConstraint");
}
#[test]
fn test_parse_key_cert_sign_bit() {
use crate::der_utils::encode_tlv;
assert!(parse_key_cert_sign_bit(&encode_tlv(0x03, &[0x01, 0x04])).unwrap());
assert!(!parse_key_cert_sign_bit(&encode_tlv(0x03, &[0x07, 0x80])).unwrap());
assert!(parse_key_cert_sign_bit(&encode_tlv(0x04, &[0x01, 0x04])).is_err());
let mut trailing = encode_tlv(0x03, &[0x01, 0x04]);
trailing.extend_from_slice(&[0x05, 0x00]); assert!(parse_key_cert_sign_bit(&trailing).is_err());
assert!(parse_key_cert_sign_bit(&encode_tlv(0x03, &[])).is_err());
assert!(parse_key_cert_sign_bit(&encode_tlv(0x03, &[0x00])).is_err());
assert!(parse_key_cert_sign_bit(&encode_tlv(0x03, &[0x08, 0x04])).is_err());
assert!(parse_key_cert_sign_bit(&encode_tlv(0x03, &[0x01, 0x05])).is_err());
assert!(parse_key_cert_sign_bit(&encode_tlv(0x03, &[0x01, 0x06])).unwrap());
}
#[test]
fn test_verify_chain_rejects_non_ca_intermediate() {
use rsa::pkcs1v15::SigningKey;
use rsa::RsaPrivateKey;
use sha2::Sha256;
use x509_cert::builder::Profile;
use x509_cert::name::Name;
let mut rng = rand::thread_rng();
let root_key = RsaPrivateKey::new(&mut rng, 2048).expect("root key");
let mid_key = RsaPrivateKey::new(&mut rng, 2048).expect("mid key");
let leaf_key = RsaPrivateKey::new(&mut rng, 2048).expect("leaf key");
let root_signer = SigningKey::<Sha256>::new(root_key.clone());
let mid_signer = SigningKey::<Sha256>::new(mid_key.clone());
let root_name = "CN=NonCA Root,O=tsp-ltv tests";
let mid_name = "CN=NonCA Mid,O=tsp-ltv tests";
let leaf_name = "CN=NonCA Leaf,O=tsp-ltv tests";
let root_issuer: Name = root_name.parse().unwrap();
let mid_issuer: Name = mid_name.parse().unwrap();
let root = issue_cert(Profile::Root, root_name, &root_key, &root_signer);
let mid = issue_cert(
Profile::Leaf {
issuer: root_issuer,
enable_key_agreement: false,
enable_key_encipherment: false,
},
mid_name,
&mid_key,
&root_signer,
);
let leaf = issue_cert(
Profile::Leaf {
issuer: mid_issuer,
enable_key_agreement: false,
enable_key_encipherment: false,
},
leaf_name,
&leaf_key,
&mid_signer,
);
let mut store = TrustStore::new();
store.add_certificate(root.clone()).unwrap();
let chain = vec![leaf, mid, root];
let err = store
.verify_chain(&chain, None)
.expect_err("a non-CA intermediate must be rejected");
assert!(
matches!(err, TrustError::SignatureVerification(ref m) if m.contains("not a CA")),
"expected non-CA intermediate rejection, got: {err:?}"
);
}
}