Skip to content

Latest commit

 

History

History
271 lines (205 loc) · 18.9 KB

File metadata and controls

271 lines (205 loc) · 18.9 KB

Cryptography in Optio

This document inventories every cryptographic primitive Optio uses, explains key management, and describes the post-quantum migration posture. It is the single reference for procurement security reviews, compliance audits, and engineering decision-making around cryptographic changes.


1. Primitive inventory

Purpose Algorithm Key / output size Implementation PQ-safe? Migration trigger
Secrets at rest AES-256-GCM (NIST SP 800-38D) 256-bit key, 96-bit IV, 128-bit auth tag node:crypto via secret-service.ts Yes (Grover's reduces to 128-bit effective security) NIST publishes AES successor
Session token storage SHA-256 of random 32-byte token 256-bit hash session-service.ts Yes (256-bit pre-image resistance) CNSA 2.0 compliance requirement
Internal API auth (pod ↔ API) HMAC-SHA256 with replay protection 256-bit secret hmac-auth-service.ts Yes None required
Webhook signing (outbound) HMAC-SHA256 Per-webhook secret webhook-service.ts via crypto/signer.ts Yes Optional ML-DSA-65 mode when customers demand it
Webhook verification (inbound GitHub) HMAC-SHA256 + timingSafeEqual GitHub-configured secret tickets.ts via crypto/signer.ts Yes Follows GitHub's signing algorithm
GitHub App JWT RS256 (RSA with SHA-256) RSA private key (PEM) github-app-service.ts via crypto/signer.ts No (low HNDL risk; JWT valid 10 min) GitHub adds PQ-safe App auth
Credential secret derivation SHA-256 of "{key}:credential-secret" 256-bit derived key credential-secret-service.ts Yes None required
Envoy sidecar CA Ed25519 self-signed X.509, 30-day validity 256-bit key envoy-sidecar.ts via openssl CLI No (ephemeral, intra-pod only) Set OPTIO_ENVOY_CA_ALG=mldsa44 when OpenSSL 3.5+ is available
Postgres TLS (in-cluster) Helm-generated self-signed CA, 10-year validity RSA 2048 (Helm genCA default) helm/optio/templates/postgres-tls.yaml No (cluster-internal) Helm adds PQ CA generation
Redis TLS (in-cluster) Helm-generated self-signed CA, 10-year validity RSA 2048 (Helm genCA default) helm/optio/templates/redis-tls.yaml No (cluster-internal) Helm adds PQ CA generation
Outbound TLS (fetch to APIs) TLS 1.3 via OpenSSL (X25519MLKEM768 hybrid when upstream supports) Session keys Node.js bundled OpenSSL Yes when upstream supports hybrid Upstream providers ship hybrid TLS
Kubernetes API TLS Whatever the cluster negotiates Session keys @kubernetes/client-node Yes on K8s >= 1.33 (Go 1.24 default) Cluster upgrade
WebSocket upgrade tokens SHA-256 of random 32-byte token 256-bit hash, 30-second TTL session-service.ts Yes Same as session tokens

Implementation notes

  • Crypto abstraction layer: apps/api/src/services/crypto/signer.ts defines Signer and Verifier interfaces with implementations: Rs256Signer, HmacSha256Signer, HmacSha256Verifier, and a stub MlDsa65Signer for future PQ support.
  • Timing-safe comparison: Used in session validation, WebSocket token lookup, webhook verification, and internal HMAC auth — all via crypto.timingSafeEqual().
  • Replay protection: Both inbound GitHub webhooks (tickets.ts, 5-minute window) and internal pod auth (hmac-auth-service.ts, 5-minute window) validate timestamps to prevent replay attacks.

2. Data classifications and shelf lives

Every entry in the secrets table is encrypted with AES-256-GCM. The table below matches each secret type to its expected shelf life and Mosca calculation.

Mosca framework

The Mosca inequality determines when to begin migration:

If x + y > z, you must migrate now.

Where:

  • x = security shelf life (how long the data must remain confidential)
  • y = migration time (how long it takes to deploy new crypto)
  • z = time until a cryptographically relevant quantum computer (CRQC) exists

Current industry estimates place z at 10–15 years (2035–2040). Optio's migration time (y) is estimated at 1–2 years for each primitive.

Secret type Example Shelf life (x) x + y Migrate now? Notes
API keys (ANTHROPIC_API_KEY) Anthropic API key Short (rotatable, < 1 year) 2–3 years No Rotate regularly; HNDL risk is low
OAuth tokens (CLAUDE_CODE_OAUTH_TOKEN) Claude subscription token Short (< 90 days) 1–2 years No Auto-expires; cannot be used retroactively
GitHub tokens (GITHUB_TOKEN) PAT or installation token Short (rotatable) 2–3 years No Should be rotated periodically
Webhook secrets HMAC signing keys Medium (1–3 years typical) 3–5 years No Only protects integrity, not confidentiality
Encryption key (OPTIO_ENCRYPTION_KEY) Master AES-256 key Long (life of deployment) 10+ years Monitor AES-256 remains PQ-safe; risk is key exposure, not algorithm break
GitHub App private key RSA PEM Medium (1–3 years) 3–5 years No JWTs expire in 10 minutes; HNDL value is minimal
Session tokens User auth tokens Short (30-day TTL) 1–2 years No Only hash is stored; token is ephemeral

Conclusion: No secret type currently triggers the Mosca inequality for immediate migration. AES-256-GCM (the only long-shelf-life primitive) is PQ-safe. The primary area to monitor is the GitHub App RSA key, which is not PQ-safe but has very low HNDL (harvest-now, decrypt-later) value due to 10-minute JWT expiry.


3. Key management

OPTIO_ENCRYPTION_KEY

  • Generation: openssl rand -hex 32 (256 bits of entropy).
  • Storage: Kubernetes Secret (helm/optio/templates/secrets.yaml), set via encryption.key in Helm values.
  • Parsing (secret-service.ts):
    • If the value is a 64-character hex string: decoded directly as 32 bytes.
    • Otherwise: SHA-256 hashed to derive a 32-byte key. This fallback exists for convenience but hex input is strongly preferred.
  • Weak key detection: At startup, getEncryptionKey() rejects known-weak values ("change-me-in-production", "changeme", "test", "secret", "password", "default") and throws an error.
  • Rotation: Currently manual. To rotate:
    1. Generate a new key: openssl rand -hex 32
    2. Re-encrypt all secrets in the secrets table with the new key
    3. Update encryption.key in Helm values
    4. Redeploy the API
    • Automated rotation tooling is planned but not yet implemented.

OPTIO_CREDENTIAL_SECRET

  • Derivation (credential-secret-service.ts):
    • If OPTIO_CREDENTIAL_SECRET env var is set: used directly.
    • Otherwise: derived as SHA-256("{OPTIO_ENCRYPTION_KEY}:credential-secret").
  • Helm template (secrets.yaml line 60):
    OPTIO_CREDENTIAL_SECRET: { { printf "%s:credential-secret" .Values.encryption.key | sha256sum } }
    This ensures the Helm-deployed value matches the runtime derivation.
  • Purpose: Used by agent pods to authenticate to the API via HMAC signatures. The secret itself never crosses the wire — only HMAC signatures are transmitted.

Webhook secrets

  • Per-webhook: Each webhook has its own secret, stored AES-256-GCM encrypted in the database with AAD bound to webhook:{url}:secret.
  • GitHub inbound: GITHUB_WEBHOOK_SECRET is stored as a global secret, configured during setup.

GitHub App private key

  • Storage: GITHUB_APP_PRIVATE_KEY env var, delivered via Kubernetes Secret.
  • Format: PEM-encoded RSA private key.
  • Used for: Signing 10-minute JWTs to obtain GitHub App installation tokens.

4. Post-quantum posture

Already PQ-safe

Primitive Why it's safe
AES-256-GCM (secrets at rest) Grover's algorithm reduces effective security to 128 bits, still well above the security margin. No known quantum speedup for GCM authentication.
SHA-256 (session tokens, credential derivation) Grover's reduces pre-image resistance to 128 bits. Collision resistance reduced to 2^128 (still sufficient).
HMAC-SHA256 (webhooks, internal auth) Same SHA-256 reasoning; HMAC construction adds no quantum vulnerability.
Kubernetes API TLS on K8s >= 1.33 Go 1.24 defaults to X25519MLKEM768 hybrid key exchange. See docs/pq-readiness.md.
Outbound TLS to PQ-capable servers Node.js / OpenSSL negotiate X25519MLKEM768 when the server supports it.

Pending migration

Primitive Risk Blocker Tracking
GitHub App JWT (RS256) Low — JWTs expire in 10 min, minimal HNDL value GitHub must add PQ-safe App authentication #324
Envoy sidecar CA (Ed25519) Very low — ephemeral, intra-pod, 30-day validity OPTIO_ENVOY_CA_ALG=mldsa44 ready when OpenSSL 3.5+ ships ML-DSA #326
Helm-generated Postgres/Redis TLS CAs Low — cluster-internal only Helm genCA must support PQ algorithms #327
Outbound TLS to non-PQ servers (GitHub, Anthropic APIs) Medium — depends on upstream Upstream providers must enable hybrid TLS #325

Tracking issues

All post-quantum work is tracked under the post-quantum label:

  • #324 — GitHub App PQ JWT migration
  • #325 — Outbound TLS PQ hybrid enforcement
  • #326 — Envoy sidecar ML-DSA CA migration
  • #327 — Helm TLS CA PQ migration
  • #328 — ML-DSA-65 webhook signing option
  • #329 — AES-256-GCM V2 with mandatory AAD binding
  • #330 — KMS-wrapped encryption key support
  • #331 — PQ TLS metrics and observability
  • #333 — This document (cryptography inventory)

Hardening plan

The audit identified two categories of crypto-agility improvements:

  1. Algorithm selector pattern: Already implemented for the Envoy CA (OPTIO_ENVOY_CA_ALG env var) and GitHub App JWT (GITHUB_APP_JWT_ALG). The crypto/signer.ts abstraction layer supports pluggable algorithms — MlDsa65Signer is stubbed and ready for implementation when Node.js crypto adds ML-DSA support.

  2. Ciphertext versioning: secret-service.ts already includes algorithm version bytes (ALG_AES_256_GCM_V1 = 0x01, ALG_AES_256_GCM_V2_AAD = 0x02). This allows transparent migration to new encryption schemes without re-encrypting all existing data at once.


5. Compliance mapping

CNSA 2.0 (NSA Commercial National Security Algorithm Suite 2.0)

CNSA 2.0 requirement Deadline Optio status
Prefer PQ algorithms for new software 2025 Partial — symmetric crypto (AES-256, SHA-256, HMAC) is compliant; asymmetric (RSA, Ed25519) pending upstream support
Software/firmware signing: ML-DSA 2025 N/A — Optio does not sign software releases
Web browsers/servers: ML-KEM + ML-DSA 2025 Partial — TLS 1.3 with ML-KEM hybrid available; ML-DSA TLS not yet standard
Traditional public-key exclusive disuse 2033 On track — all non-PQ primitives have identified migration paths with external blockers (GitHub, Helm)
Symmetric algorithms No change required Compliant — AES-256 and SHA-256/384 are CNSA 2.0 approved

FIPS 140-3

Optio does not currently claim FIPS 140-3 compliance. Relevant considerations:

  • Node.js OpenSSL: Node.js can be built with OpenSSL in FIPS mode (--openssl-is-fips), but the standard Node.js distribution is not FIPS-validated.
  • AES-256-GCM: FIPS-approved algorithm (SP 800-38D).
  • SHA-256, HMAC-SHA256: FIPS-approved (FIPS 180-4, FIPS 198-1).
  • Ed25519: Included in FIPS 186-5 (2023), but the OpenSSL implementation used by the Envoy sidecar may not be FIPS-validated.
  • RS256: FIPS-approved (FIPS 186-4).

To achieve FIPS 140-3 compliance, Optio would need to:

  1. Use a FIPS-validated Node.js build or OpenSSL provider
  2. Ensure all crypto operations go through the validated module boundary
  3. Restrict key sizes and algorithms to FIPS-approved values
  4. Undergo formal validation (or use a validated crypto module)

6. How to verify PQ status in a running deployment

Check TLS key exchange to upstream services

Use tls.connect() from inside the API pod to probe what key exchange group is negotiated with a given upstream:

kubectl exec deploy/optio-api -n optio -- node -e '
const tls = require("tls");
const targets = [
  { host: "api.anthropic.com", port: 443 },
  { host: "api.github.com", port: 443 },
  { host: "kubernetes.default.svc", port: 443 },
];
for (const t of targets) {
  const sock = tls.connect(t.port, t.host, { servername: t.host }, () => {
    const info = sock.getEphemeralKeyInfo?.();
    console.log(`${t.host}: group=${info?.name ?? "unknown"} proto=${sock.getProtocol()}`);
    sock.end();
  });
  sock.on("error", (e) => console.log(`${t.host}: error=${e.code}`));
}
'

If the upstream supports hybrid PQ, you will see group=x25519_mlkem768.

Check with OpenSSL CLI

kubectl exec deploy/optio-api -n optio -- \
  openssl s_client -connect api.github.com:443 \
  -groups X25519MLKEM768:X25519 \
  -brief 2>&1 | grep -E 'Protocol|Server Temp Key|Groups'

If the server supports ML-KEM hybrid, the output will show Server Temp Key: X25519MLKEM768.

Check Kubernetes API PQ status

See docs/pq-readiness.md for the full verification procedure. On K8s >= 1.33, the expected key exchange group is x25519_mlkem768.

Check Envoy sidecar CA algorithm

# From inside an agent pod:
openssl x509 -in /etc/envoy/ca/ca.crt -noout -text | grep 'Public Key Algorithm'
# Expected: ED25519 (current) or id-ML-DSA-44 (after PQ migration)

Metrics (when #331 lands)

The optio_tls_handshake_total Prometheus metric will expose:

optio_tls_handshake_total{target="api.github.com", group="x25519", version="TLSv1.3"} 42
optio_tls_handshake_total{target="kubernetes.default.svc", group="x25519_mlkem768", version="TLSv1.3"} 108

Use this to monitor PQ adoption across all outbound connections without manual probing.