← Week 1: Certificate Issuance Pipeline

Day 6: Chain Parsing and Validation

Phase 2 · June 22, 2026

← Week 1: Certificate Issuance Pipeline

Agenda (2–3 hours)

  • Read (20 min): x509-parser verify_signature() docs; RFC 5280 §6.1 (review)
  • Build (120 min): Implement validate_chain() in validate.rs
  • Verify (30 min): Test with valid and deliberately broken chains
← Week 1: Certificate Issuance Pipeline

What Chain Validation Needs to Check

Recall RFC 5280 §6 (Phase 1, Day 26). For each cert in the chain:

  1. Signature: verify cert[i]'s signature using cert[i-1]'s public key
  2. Validity period: NotBefore ≤ now ≤ NotAfter
  3. Issuer/Subject linkage: cert[i].issuer == cert[i-1].subject
  4. BasicConstraints: cert[i-1] must have cA=TRUE if it signed cert[i]
  5. KeyUsage: cert[i-1] must have keyCertSign if it signed cert[i]
  6. AKI/SKI linkage: cert[i].AKI == cert[i-1].SKI
← Week 1: Certificate Issuance Pipeline

x509-parser: Signature Verification

use x509_parser::prelude::*;

pub fn verify_cert_signature(
    cert: &X509Certificate,
    issuer: &X509Certificate,
) -> anyhow::Result<()> {
    // x509-parser can verify a cert's signature against an issuer cert
    cert.verify_signature(Some(issuer.public_key()))
        .map_err(|e| anyhow::anyhow!("Signature verification failed: {e:?}"))
}

For the trust anchor (root CA): verify signature against itself (self-signed).

← Week 1: Certificate Issuance Pipeline

Implementing validate_chain

/// certs: [leaf, intermediate, ..., root]
/// trust_anchor: the root CA cert you explicitly trust
pub fn validate_chain(
    certs: &[X509Certificate],
    trust_anchor: &X509Certificate,
    now: OffsetDateTime,
) -> anyhow::Result<()> {
    // 1. Verify trust anchor is self-signed
    // 2. For each cert from root down to leaf:
    //    a. Signature
    //    b. Validity period
    //    c. Issuer/Subject linkage
    //    d. BasicConstraints on signer
    //    e. KeyUsage on signer
    //    f. AKI/SKI linkage
    // 3. Return Ok(()) only if all checks pass
}
← Week 1: Certificate Issuance Pipeline

Parsing a PEM Chain

use x509_parser::pem::Pem;

pub fn parse_pem_chain(pem_data: &str) -> anyhow::Result<Vec<X509Certificate>> {
    let mut certs = Vec::new();
    for pem in Pem::iter_from_buffer(pem_data.as_bytes()) {
        let pem = pem?;
        if pem.label == "CERTIFICATE" {
            let (_, cert) = X509Certificate::from_der(&pem.contents)?;
            certs.push(cert);
        }
    }
    Ok(certs)
}

Chain order convention: leaf first, root last (TLS wire format).

← Week 1: Certificate Issuance Pipeline

Challenge Assignment

Implement validate_chain() in validate.rs — all 6 checks listed above.

Write three tests:

  1. test_valid_chain: root → intermediate → leaf — should pass
  2. test_expired_cert: use time::OffsetDateTime in the past as now — should fail with a clear error message naming which cert expired
  3. test_tampered_chain: swap the intermediate from a different CA hierarchy — should fail on signature check

For each failure, the error message should tell you which cert and which check failed.
Vague errors are not acceptable — you'll thank yourself in production.

← Week 1: Certificate Issuance Pipeline

Resources

  • x509-parser X509Certificate::verify_signature()
  • x509-parser validity(), basic_constraints(), key_usage(), authority_key_identifier()
  • RFC 5280 §6.1.3: the path processing algorithm (review)