← Week 1: Certificate Issuance Pipeline

Day 7: Challenge — Full Issuance Pipeline

Phase 2 · June 23, 2026 · Week 1 Review

← Week 1: Certificate Issuance Pipeline

Agenda (2–3 hours)

  • Review (20 min): Week 1 module implementations — any gaps?
  • Integrate (30 min): Wire ca.rs + issue.rs + validate.rs together
  • Test (90 min): Write comprehensive integration tests
  • Reflect (20 min): What still feels shaky? Note it.
← Week 1: Certificate Issuance Pipeline

Week 1 State Check

Your toy-pki project should now have working implementations of:

  • [ ] Ca::new_root() — generates a self-signed root CA
  • [ ] Ca::new_intermediate() — issues an intermediate CA from a root
  • [ ] Ca::save() / Ca::load() — PEM serialization round-trip
  • [ ] issue_server_cert() — issues a TLS server cert from an intermediate
  • [ ] issue_client_cert() — issues a client cert from an intermediate
  • [ ] validate_chain() — verifies a 3-cert chain with 6 checks

If any of these are incomplete, finish them before the integration test.

← Week 1: Certificate Issuance Pipeline

Challenge Assignment: Integration Test Suite

Add tests/integration.rs and implement test_full_issuance_pipeline:

#[test]
fn test_full_issuance_pipeline() {
    // 1. Create root CA
    // 2. Issue intermediate from root
    // 3. Issue server cert for "localhost" from intermediate
    // 4. Issue client cert for "test-device-001" from intermediate
    // 5. validate_chain([server_leaf, intermediate, root], root, now) → Ok
    // 6. validate_chain([client_leaf, intermediate, root], root, now) → Ok
    // 7. Verify server cert has serverAuth EKU, client cert has clientAuth EKU
    // 8. Verify AKI/SKI linkage throughout the chain
    // 9. Save entire hierarchy to a temp dir
    // 10. Load it back and validate again (round-trip test)
}

All assertions must have descriptive failure messages.

← Week 1: Certificate Issuance Pipeline

Bonus: Cross-CA Rejection Test

#[test]
fn test_rejects_cert_from_different_ca() {
    let root_a = Ca::new_root("Root A").unwrap();
    let root_b = Ca::new_root("Root B").unwrap();
    let inter_a = Ca::new_intermediate("Intermediate A", &root_a).unwrap();

    // Issue a cert from intermediate_a, but validate against root_b
    let (server_cert, _) = issue_server_cert(&["localhost"], &inter_a).unwrap();

    // Build chain: [server_cert, inter_a, root_b] (wrong root)
    // Should fail at signature check between inter_a and root_b
    let result = validate_chain(&[server_cert, inter_a.cert, root_b.cert], &root_b, now);
    assert!(result.is_err());
    // Error message should say: "signature verification failed" on inter_a
}
← Week 1: Certificate Issuance Pipeline

openssl Cross-Check

Run every cert through openssl to confirm your Rust validator and openssl agree:

cargo test -- --nocapture 2>&1 | grep "saved to"
# Then for each test hierarchy written to disk:
openssl verify -CAfile root/ca.crt -untrusted intermediate/ca.crt leaf/server.crt
openssl verify -CAfile root/ca.crt -untrusted intermediate/ca.crt leaf/client.crt

If your Rust validator says "valid" but openssl says "error" — investigate and fix.
These two should always agree.

← Week 1: Certificate Issuance Pipeline

Resources

  • Rust integration test conventions: tests/ directory, #[test] in tests/integration.rs
  • tempfile crate: tempdir() for isolated test directories
  • std::process::Command for running openssl as a subprocess in tests