docs/protocol.md, docs/pki.md, docs/split-tunnel.md — written from the actual implementation (pinned handshake order, ML-KEM-768/FIPS 203, seq||AEAD records with replay window, QUIC/H3 mimicry) including honest v1 limitations. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
9.0 KiB
Aura PKI
Aura uses a small, self-contained X.509 PKI for mutual authentication of the inner handshake. A single self-signed Aura CA issues one server certificate and one client certificate per client. During the handshake the client verifies the server's certificate and the server verifies the client's certificate, both against the CA.
The PKI is implemented in the aura-pki crate (ca.rs, cert.rs, store.rs) and exposed on
the command line as aura pki ... (crates/aura-cli/src/pki.rs,
crates/aura-cli/src/main.rs).
The outer QUIC/TLS layer does not use this PKI — it accepts any certificate (see
protocol.md, "Mimicry layer"). All certificate trust lives in the inner Aura handshake.
Trust model
Aura CA (self-signed)
CN = <ca_name>, isCA, keyCertSign/crlSign
|
+------------+------------+
| |
server leaf client leaf(s)
CN = <domain> CN = <client_id>
SAN: DNS:<domain> (no SAN)
EKU: serverAuth EKU: clientAuth
- The CA is self-signed with
BasicConstraints: CA, and key usageskeyCertSign+crlSign+digitalSignature. Default lifetime 3650 days. - A server leaf carries
CN = <domain>, aDNS:<domain>SAN, andextendedKeyUsage = serverAuth. The DNS SAN is what the client matches against its expectedserver_name. - A client leaf carries
CN = <client_id>andextendedKeyUsage = clientAuth. The CN is the identity the server learns and records as the sessionpeer_id. - Leaf key usages are
digitalSignature+keyEncipherment. Default lifetime 365 days. - All issued certs (CA and leaves) backdate
not_beforeby 5 minutes to tolerate clock skew.
Algorithms
All keys are ECDSA P-256 / SHA-256 (rcgen's default KeyPair::generate). Private keys are
written in PKCS#8 PEM. Chain verification (in cert.rs) accepts ECDSA P-256/SHA-256
(required), and also ECDSA P-384/SHA-384 and Ed25519, so a deployment can switch key types
later without code changes.
File layout
The CLI keeps files in plain directories. Conventional names
(crates/aura-cli/src/pki.rs):
| File | Constant | Contents |
|---|---|---|
ca.crt |
CA_CERT |
CA certificate (PEM) |
ca.key |
CA_KEY |
CA private key (PKCS#8 PEM) — secret |
server.crt |
Server leaf certificate (PEM) | |
server.key |
Server leaf private key (PEM) — secret | |
client.crt |
Client leaf certificate (PEM) | |
client.key |
Client leaf private key (PEM) — secret | |
revoked.crl |
CRL_FILE |
Revocation list (one identifier per line) |
issue-server and issue-client load the CA from ca.crt + ca.key in the CA directory and
write server.{crt,key} / client.{crt,key} into the output directory. Paths beginning with
~ are expanded to the home directory (from $HOME, or $USERPROFILE on Windows).
These names map directly onto the [pki] section of server.toml / client.toml
(ca_cert, cert, key).
aura pki commands
aura pki init --ca-name <CN> --out <DIR>
aura pki issue-server --domain <DNS> --out <DIR> [--ca <CA_DIR>]
aura pki issue-client --id <CLIENT> --out <DIR> [--ca <CA_DIR>]
aura pki revoke --id <ID> [--crl <PATH>]
aura pki list [--crl <PATH>]
For issue-server / issue-client, --ca defaults to the value of --out (so the CA and
the issued leaf can live in the same directory). For revoke / list, --crl defaults to
./revoked.crl.
init — create a CA
Generates a fresh self-signed CA and writes ca.crt + ca.key into --out (creating the
directory if needed).
aura pki init --ca-name "Aura Root CA" --out ~/.aura
# CA generated:
# cert: ~/.aura/ca.crt
# key: ~/.aura/ca.key
issue-server — issue a server certificate
Issues a server leaf for a DNS name, signed by the CA, with a DNS:<domain> SAN and
serverAuth EKU.
aura pki issue-server --domain vpn.example.com --out ~/.aura --ca ~/.aura
# server certificate issued for 'vpn.example.com':
# cert: ~/.aura/server.crt
# key: ~/.aura/server.key
The
--domainmust equal the name the client expects in the handshake. In the shipped client config that name is taken from[client] sni, so the camouflage SNI and the verified server SAN are the same value.
issue-client — issue a client certificate
Issues a client leaf with CN = <id> and clientAuth EKU. The <id> becomes the verified
peer_id the server sees.
aura pki issue-client --id laptop --out ~/.aura --ca ~/.aura
# client certificate issued for 'laptop':
# cert: ~/.aura/client.crt
# key: ~/.aura/client.key
revoke — add to the revocation list
Adds an identifier — a client id / Common Name or a certificate serial (lowercase hex, no separators) — to the CRL file, creating it (and parent directories) if absent.
aura pki revoke --id laptop --crl ~/.aura/revoked.crl
# revoked 'laptop' (CRL: ~/.aura/revoked.crl)
list — show revoked identifiers
Prints the identifiers in the CRL file (empty if the file does not exist).
aura pki list --crl ~/.aura/revoked.crl
# revoked identifiers (CRL: ~/.aura/revoked.crl):
# laptop
End-to-end example
# 1. Create the CA.
aura pki init --ca-name "Aura Root CA" --out ~/.aura
# 2. Issue the server cert for its public DNS name.
aura pki issue-server --domain vpn.example.com --out ~/.aura
# 3. Issue a client cert per device.
aura pki issue-client --id laptop --out ~/.aura
# 4. (later) Revoke a compromised client.
aura pki revoke --id laptop
Verification
Verification is performed by AuraCertVerifier (crates/aura-pki/src/cert.rs), built from
the CA certificate PEM. It uses rustls-webpki to validate the peer's leaf against the CA
trust anchor. The Aura handshake invokes it on each side (see protocol.md).
Server certificate (verify_server_cert), run by the client:
- webpki chain verification against the CA with key usage
serverAuth, plus validity (time) check. - The leaf must be valid for the requested
server_name(DNS SAN match); a mismatch isNameMismatch. - CRL check (see below).
Client certificate (verify_client_cert), run by the server:
- webpki chain verification against the CA with key usage
clientAuth, plus validity. - The client id is extracted as the first Common Name from the leaf subject (missing CN
is
MissingIdentity). - CRL check.
- Returns the client id, which the handshake records as the session
peer_id.
The leaf certificate is sent inline in the handshake (DER, no intermediate chain); the CA
is the single trust anchor. Possession of the leaf's private key is proven separately by the
handshake signature over the transcript (see protocol.md).
Errors surface as PkiError: CertParse, EmptyChain, TrustAnchor, Verification,
NameMismatch, MissingIdentity, Revoked.
Revocation (CRL)
Aura v1 revocation is deliberately minimal (crates/aura-pki/src/store.rs). CrlStore is a
set of revoked identifier strings, where an identifier is either:
- a certificate serial number (lowercase hex, no separators), or
- a client id / Common Name.
During verification, if the CRL is non-empty the leaf is rejected (Revoked) when either
its serial or its Common Name is present in the set. An empty CRL skips the check
entirely.
The on-disk format is one identifier per line; blank lines and # comments are ignored on
load. aura pki revoke / aura pki list manage this file.
v1 limitation: this is a flat allow/deny set, not a signed X.509 CRL. There is no CRL signature, no
nextUpdate, and no automatic distribution — the file must be provisioned to the verifying side out of band. The verifier passesNonefor webpki's own revocation hooks and relies solely on this set.
Security notes
- Protect the private keys.
ca.keyis the root of all trust; anyone with it can mint valid server/client certs.server.key/client.keymust stay on their respective hosts. The CLI writes them with default file permissions — restrict them at the OS level. - The CA is self-signed and unconstrained (
BasicConstraints: CAunconstrained). It is the sole trust anchor; there is no intermediate CA tier in v1. - Server identity is name-bound. The client only accepts a server leaf whose DNS SAN matches the expected name, so a different valid leaf from the same CA will not be accepted for the wrong host.
- Revocation is best-effort (see above): plan to distribute the CRL file and keep it in sync on every server that verifies clients.
- Leaf lifetime is 365 days; plan re-issuance. There is no automated rotation in v1.