← Week 1: Certificate Issuance Pipeline

Day 5: Client Cert Issuance (mTLS)

Phase 2 · June 21, 2026

← Week 1: Certificate Issuance Pipeline

Agenda (2–3 hours)

  • Read (20 min): RFC 5280 §4.2.1.12 EKU OIDs for clientAuth; rustls ClientCertVerifier docs
  • Build (120 min): Implement issue_client_cert() in issue.rs
  • Verify (30 min): Confirm client cert passes rustls's client verifier
← Week 1: Certificate Issuance Pipeline

Client Cert vs Server Cert: The Differences

Field Server Cert Client Cert
ExtendedKeyUsage id-kp-serverAuth id-kp-clientAuth
SubjectAltName dNSName hostname rfc822Name (email) or directoryName or custom
Subject CN hostname (optional) user/device identity
Validated by client (hostname check) server (ClientCertVerifier)

For device provisioning: the SAN or subject CN typically carries a device ID,
serial number, or service name — whatever the server uses to authorize the client.

← Week 1: Certificate Issuance Pipeline

rcgen: Issuing a Client Cert

pub fn issue_client_cert(
    identity: &str,       // e.g., "device-12345" or "alice@example.com"
    issuer: &Ca,
) -> anyhow::Result<(rcgen::Certificate, rcgen::KeyPair)> {
    let key = KeyPair::generate()?;

    let mut params = CertificateParams::default();
    params.distinguished_name.push(DnType::CommonName, identity);

    // Use rfc822Name SAN for email-style identities,
    // or omit SAN for device certs where CN is the identifier
    params.subject_alt_names = vec![
        SanType::Rfc822Name(identity.to_string()),
    ];

    params.is_ca = IsCa::NoCa;
    params.key_usages = vec![KeyUsagePurpose::DigitalSignature];
    params.extended_key_usages = vec![ExtendedKeyUsagePurpose::ClientAuth];
    params.not_before = OffsetDateTime::now_utc();
    params.not_after  = OffsetDateTime::now_utc() + Duration::days(365);

    let cert = params.signed_by(&key, &issuer.cert, &issuer.key)?;
    Ok((cert, key))
}
← Week 1: Certificate Issuance Pipeline

Using Device IDs in Client Certs

For satellite device provisioning, client certs typically identify devices, not people.
Two common patterns:

CN as device ID:

subject: CN=device-SN123456789

Custom OID extension (for structured device metadata):

1.2.840.10045.4.3.2 = device-type:leo-terminal, region:us-east

For the toy PKI: CN is sufficient. In Phase 5 (HSM), you'll revisit how the CA
validates device identity before issuing.

← Week 1: Certificate Issuance Pipeline

rustls: Server-Side Client Cert Verification

rustls's WebPkiClientVerifier checks:

  1. Chain builds up to a trusted CA in the server's RootCertStore
  2. Validity period
  3. clientAuth EKU is present

It does not check revocation by default. Your service must add CRL/OCSP checking.

let client_verifier = WebPkiClientVerifier::builder(
    Arc::new(roots)  // same root store as server uses
).build()?;

let server_cfg = ServerConfig::builder()
    .with_client_cert_verifier(client_verifier)
    .with_single_cert(server_chain, server_key)?;
← Week 1: Certificate Issuance Pipeline

Challenge Assignment

Implement issue_client_cert() in issue.rs.

Write a test test_client_cert_issuance that:

  1. Creates root → intermediate → client cert for "test-device-001"
  2. Asserts the cert has clientAuth EKU and no serverAuth
  3. Asserts the subject CN is "test-device-001"
  4. Builds a rustls ServerConfig with WebPkiClientVerifier using the root CA
  5. Verifies the client cert chain is accepted by the verifier

Then answer in writing: for your team's provisioning service, what should be in
the client cert's SAN or CN to enable authorization decisions? How would the server
distinguish between a provisioning request from a valid device vs an unknown one?

← Week 1: Certificate Issuance Pipeline

Resources

  • RFC 5280 §4.2.1.12: id-kp-clientAuth OID = 1.3.6.1.5.5.7.3.2
  • rustls WebPkiClientVerifier: docs.rs/rustls
  • rcgen SanType::Rfc822Name for email-style identities
  • rcgen SanType::OtherName for custom identity types