# TLS and JWT _Auto-generated certificates, signed tokens, pinned fingerprints. The cryptographic foundation of Mothership._ ## Overview Mothership's security model rests on two primitives: 1. **TLS** for confidentiality and integrity of the wire. 2. **JWT** for authentication of peers. Both are generated automatically on first start. You do not have to be a cryptographer to run a Mothership safely. But you should understand what is happening, because the defaults are safe only within a known threat model. ## Auto-TLS on First Start When you run `aura mothership start` for the first time, Aura generates a self-signed TLS certificate. Specifically: - A fresh Ed25519 keypair. - An X.509 certificate valid for 365 days. - Subject CN matching the configured `display_name` or hostname. - SANs covering the bind address, `localhost`, and any explicitly configured hostnames. The private key is stored at `~/.config/aura/mothership/tls.key` with mode `0600`. The certificate is stored alongside at `tls.crt`. Both are re-used on subsequent starts. ```bash aura mothership tls info ``` Output: ```text Certificate: subject: CN=Acme Core Mothership sans: DNS:mothership.acme.internal, IP:10.0.1.15 issuer: self-signed not before: 2026-04-21 not after: 2027-04-21 fingerprint: ab:cd:ef:01:23:45:67:89:... key type: Ed25519 ``` ## Self-Signed vs. Let's Encrypt vs. Internal CA You have three supported modes. ### Self-signed (default) Appropriate when: - Your Mothership is only reachable over a private network (LAN, VPN, Tailscale). - Your peers pin the certificate's fingerprint via the join token. The certificate is not trusted by public browsers. That is fine — peers don't use browsers, they use Aura, which performs fingerprint pinning rather than chain validation. ### Let's Encrypt Appropriate when: - Your Mothership is publicly reachable at a DNS name. - You want browsers (for the health endpoint, or a future dashboard) to trust the cert without manual pinning. ```toml [tls] mode = "letsencrypt" domain = "mothership.acme.com" email = "ops@acme.com" ``` On first start with this config, Aura performs an ACME challenge. HTTP-01 is used if port 80 is reachable; DNS-01 is used if you configure a DNS provider. Renewal is automatic, attempted daily starting 30 days before expiry. ### Internal CA Appropriate when: - Your organization operates an internal CA (step-ca, Vault PKI, Active Directory Certificate Services). - You want to integrate with existing cert lifecycle tooling. Provision a cert and key, then: ```toml [tls] mode = "custom" cert = "/etc/aura/mothership/tls.crt" key = "/etc/aura/mothership/tls.key" ``` Aura will read the cert and key on start and reload on `SIGHUP`, so you can rotate certs without restarting the Mothership. ## Fingerprint Pinning In every mode, join tokens embed the Mothership's TLS fingerprint. When a peer connects: 1. The peer opens a TLS connection to the configured URL. 2. The peer extracts the server's leaf certificate fingerprint. 3. The peer compares it against the fingerprint in the join token. 4. If they match, the peer proceeds. If not, the connection is aborted. This is the defense against DNS hijacking and TLS-terminating middleboxes. Even if an attacker owns the DNS record and presents a valid Let's Encrypt cert for your domain, the fingerprint in the join token will not match their certificate, and the handshake aborts. > **Security callout.** Fingerprint pinning breaks if you rotate the TLS certificate mid-week, because existing peers have pinned the old fingerprint. Aura handles this by accepting either the old or new fingerprint for a grace window (default 24 hours) after rotation. Plan rotations during working hours and communicate them. Pin fingerprints are recomputed on every cert reload. `aura mothership tls info` always shows the current fingerprint. ## JWT: What It Proves A JWT presented during join proves two things: 1. The bearer was authorized by the Mothership operator (because the signature is valid under the Mothership's public key). 2. The bearer is connecting to the correct Mothership (because the `tls_fingerprint` claim matches the server cert). It does _not_ prove the bearer is a specific human. The `sub` claim is informational — used for audit logs — not enforced cryptographically. For stronger identity, integrate with an OIDC provider. Mothership can be configured to require a fresh OIDC token from your IdP as part of join: ```toml [jwt] require_oidc = true oidc_issuer = "https://auth.acme.com" oidc_audience = "aura-mothership" ``` With this enabled, the join flow becomes: ```text peer --> IdP: authenticate, receive OIDC token peer --> Mship: present (join_token, oidc_token) Mship --> IdP: verify OIDC token via JWKS Mship --> peer: register if both tokens valid ``` This is optional and off by default. For teams that already run Okta or Entra ID, it turns Mothership into an SSO-backed system with no extra infrastructure. ## Signing Algorithm Details As noted in [join token security](/join-token-security), EdDSA (Ed25519) is the default JWT algorithm. The full list of supported algorithms: | Algorithm | Status | When to use | |---|---|---| | EdDSA (Ed25519) | Default | Every new deployment | | ES256 (ECDSA P-256) | Supported | FIPS environments | | ES384 | Supported | Higher-assurance FIPS | | RS256 | Behind flag | Legacy OIDC integration | | HS256 | **Rejected** | Symmetric keys don't scale | | none | **Rejected** | Always | The `alg` header is validated against the configured algorithm list. A token claiming `alg: none` or `alg: HS256` when the Mothership is configured for EdDSA is rejected before any signature check. This closes the algorithm-confusion class of JWT attacks. ## Key Storage The JWT signing private key lives at `~/.config/aura/mothership/jwt.key`, mode `0600`. It is generated on first start and never transmitted over the network. For high-assurance deployments, you can store the key in a hardware token (YubiKey, Nitrokey) or HSM accessible via PKCS#11: ```toml [jwt] key_store = "pkcs11" pkcs11_lib = "/usr/lib/softhsm/libsofthsm2.so" pkcs11_slot = 0 pkcs11_key_label = "aura-mothership-jwt" ``` With this enabled, the Mothership cannot sign a new token without the HSM being unlocked. This protects against offline disk theft. ## The Peer Handshake, Step by Step ```text 1. peer: TCP connect to mothership.acme.com:7777 2. peer: TLS ClientHello 3. Mship: TLS ServerHello, serves certificate 4. peer: extract leaf fingerprint, compare to token's tls_fingerprint (abort if mismatch) 5. peer: complete TLS handshake 6. peer: send AuraJoin { token: , peer_pubkey: } 7. Mship: verify jwt signature with stored public key 8. Mship: check jti not revoked, not already used (if one_shot) 9. Mship: mint peer certificate, sign with Mothership CA key 10. Mship: return PeerCert { cert, refresh_token, peer_id } 11. peer: store cert + refresh token under ~/.config/aura/peers// 12. peer: open sync streams (WAL subscribe, msg subscribe, etc.) ``` Subsequent reconnects skip steps 6-10 and present the peer certificate directly. Refresh tokens are rotated automatically (see [join token security](/join-token-security)). ## Cipher Suites and TLS Version Mothership requires TLS 1.3. Earlier versions are rejected at the ServerHello. Supported cipher suites: - `TLS_AES_128_GCM_SHA256` - `TLS_AES_256_GCM_SHA384` - `TLS_CHACHA20_POLY1305_SHA256` There is no way to downgrade to TLS 1.2. If a client library does not support 1.3, upgrade the client. This is 2026; TLS 1.2 is retired. ## Revoking TLS Trust If you believe your Mothership's TLS private key has been compromised: 1. Generate a new cert (rotate key): ```bash aura mothership tls rotate ``` 2. Broadcast the new fingerprint to peers. Existing peers within the grace window accept both old and new. 3. Immediately rotate the JWT signing key as well: ```bash aura mothership rotate-key ``` 4. Re-issue join tokens for anyone who was mid-onboarding. Steps 1 and 3 can run independently. Rotating TLS does not invalidate join tokens (they pin the fingerprint and will fail naturally). Rotating the JWT key invalidates every unjoined token. ## Clock Skew JWTs rely on timestamps (`iat`, `exp`). If the Mothership and peer clocks disagree by more than the allowed skew (default 60s), handshakes fail with `token not yet valid` or `token expired`. This is almost always a misconfigured NTP on one side. Run `timedatectl status` (Linux) or `sntp -t 5 time.apple.com` (macOS) on both ends. ## What's Not Here Mothership's TLS+JWT stack deliberately excludes: - **mTLS with client certs at join time.** The JWT _is_ the client credential at join time; mTLS is used for all subsequent connections via the peer certificate. - **CRL / OCSP stapling for public CAs.** We pin fingerprints instead, which is strictly stronger for our threat model. - **TLS 1.2.** Retired. - **Username/password fallback.** Deliberate. If you want SSO, use OIDC. ## A Worked Example Concrete scenario. You are setting up a new Mothership on `mothership.acme.internal` that will be reachable only inside your office LAN and over the Tailscale mesh. 1. You decide to use self-signed TLS. The Mothership's cert will be trusted only via fingerprint pinning, not via any public CA. 2. On first start, Mothership generates an Ed25519 TLS keypair and a self-signed cert valid for 365 days. It prints the fingerprint: `ab:cd:ef:01:...`. You copy this into your team runbook. 3. Mothership generates an Ed25519 JWT signing keypair. The public key is exported for peer verification; the private key stays at `~/.config/aura/mothership/jwt.key`. 4. You issue join tokens to three developers with 24h expiry. Each token embeds the TLS fingerprint `ab:cd:ef:01:...` so even if someone steals the token and redirects DNS to their own server, the handshake will fail. 5. Developers join. Each receives a peer certificate signed by the Mothership's internal CA, plus a refresh token. Subsequent reconnects skip the join token entirely and use mTLS. 6. Eleven months later, you run `aura mothership tls rotate` proactively. The new cert coexists with the old for a 24-hour grace window. Everyone's peer certs are re-minted. You update the team runbook with the new fingerprint. Nothing about this flow requires a CA, a vendor, or a paid certificate. It's cryptography that you own. ## Threat Model Summary A compact summary of what each primitive defends against: | Threat | Defense | |---|---| | Eavesdropping on the wire | TLS 1.3 with AEAD ciphers | | Server impersonation (DNS/MITM) | Fingerprint pinning in join tokens | | Token forgery | Ed25519 signature on JWT | | Algorithm confusion attack on JWT | Explicit `alg` check; `none` and `HS*` rejected | | Replay of stolen join token | One-shot flag + short expiry + `jti` tracking | | Stolen peer cert (laptop theft) | Automatic refresh rotation; admin revocation | | Compromised Mothership disk | HSM-backed signing key (optional) | | Protocol downgrade | TLS 1.2 and below rejected | | Long-lived credentials | All credentials have bounded lifetime | What is explicitly out of scope: insider threats with active Mothership root access. If the attacker owns the Mothership host, they can sign whatever they want. Physical security of the Mothership host, and process isolation of its private keys (HSM, if you need it), are your responsibility. ## Next Steps - [Issue and rotate join tokens](/join-token-security) - [Understand peer-to-peer architecture](/p2p-architecture) - [Troubleshoot handshake and cert errors](/mothership-troubleshooting)