# 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 = , isCA, keyCertSign/crlSign | +------------+------------+ | | server leaf client leaf(s) CN = CN = SAN: DNS: (no SAN) EKU: serverAuth EKU: clientAuth ``` - The **CA** is self-signed with `BasicConstraints: CA`, and key usages `keyCertSign` + `crlSign` + `digitalSignature`. Default lifetime **3650 days**. - A **server leaf** carries `CN = `, a **`DNS:` SAN**, and `extendedKeyUsage = serverAuth`. The DNS SAN is what the client matches against its expected `server_name`. - A **client leaf** carries `CN = ` and `extendedKeyUsage = clientAuth`. The CN is the identity the server learns and records as the session `peer_id`. - Leaf key usages are `digitalSignature` + `keyEncipherment`. Default lifetime **365 days**. - All issued certs (CA and leaves) backdate `not_before` by **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 --out aura pki issue-server --domain --out [--ca ] aura pki issue-client --id --out [--ca ] aura pki revoke --id [--crl ] aura pki list [--crl ] ``` 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). ```bash 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:` SAN and `serverAuth` EKU. ```bash 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 `--domain` must 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 = ` and `clientAuth` EKU. The `` becomes the verified `peer_id` the server sees. ```bash 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. ```bash 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). ```bash aura pki list --crl ~/.aura/revoked.crl # revoked identifiers (CRL: ~/.aura/revoked.crl): # laptop ``` ### End-to-end example ```bash # 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: 1. webpki chain verification against the CA with key usage **`serverAuth`**, plus validity (time) check. 2. The leaf must be valid for the requested `server_name` (DNS SAN match); a mismatch is `NameMismatch`. 3. CRL check (see below). **Client certificate** (`verify_client_cert`), run by the server: 1. webpki chain verification against the CA with key usage **`clientAuth`**, plus validity. 2. The **client id** is extracted as the first Common Name from the leaf subject (missing CN is `MissingIdentity`). 3. CRL check. 4. 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 passes `None` for webpki's own revocation > hooks and relies solely on this set. --- ## Security notes - **Protect the private keys.** `ca.key` is the root of all trust; anyone with it can mint valid server/client certs. `server.key` / `client.key` must 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: CA` unconstrained). 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.