verae-staff-iam/docs/TEST-CA-DESIGN.md
George Lambert a973431935 docs: CRL-OCSP-DESIGN + lab-test-ca crl subcommand
Future real-CA revocation design; lab empty CRL generator only.
2026-10-14 01:16:53 -04:00

12 KiB

Test CA design (lab opt-in HTTP + future production)

Status:

  • Offline lab helper shipped: scripts/lab-test-ca.sh
  • Opt-in lab HTTP shipped (LAB ONLY): LAB_CA_ENABLE=1 + initialized lab-pki → POST /enroll/csr may shell-out to lab-test-ca.sh sign → 201 cert PEM + headers X-Lab-CA: 1 / X-Lab-CA-Warning: NOT-FOR-PRODUCTION
  • Default (flag off): POST /enroll/csr remains 501
  • Production mint: still not shipped — never enable lab CA in production (NODE_ENV=production refuses it)

This document describes (1) the lab script, (2) the gated lab HTTP path, and (3) how a future production Signer would replace the default 501. Never production for lab material.

Live mint product backlog and hard “no fake crypto” rules: PKI-MINT-BACKLOG.md. Current proxy-only enroll contract: ENROLL.md.

Lab script + opt-in HTTP (LAB ONLY — never production)

Item Detail
Path scripts/lab-test-ca.sh
Offline init → self-signed lab root under lab-pki/ (gitignored); sign --csr → short-lived leaf PEM + chain on disk; optional crl → empty lab CRL
Opt-in HTTP LAB_CA_ENABLE=1 + lab-pki ready → staff-iam shells out to sign on POST /enroll/csr → 201 + X-Lab-CA: 1
Default Flag off → POST /enroll/csr 501 (unchanged production scaffold)
Production refuse NODE_ENV=production → lab path off even if LAB_CA_ENABLE=1
Warnings Script banner + response warning field + X-Lab-CA-Warning: NOT-FOR-PRODUCTION
Relation to live mint Lab/dev only. Production mint needs a real Signer (see backlog) — lab CA must never be the default or production path
# example (lab machine only)
./scripts/lab-test-ca.sh init --dir ./lab-pki
openssl req -new -newkey rsa:2048 -nodes -keyout dev.key -out dev.csr -subj "/CN=lab-user@staff.local"
# offline sign still works:
./scripts/lab-test-ca.sh sign --dir ./lab-pki --csr dev.csr --out ./lab-pki/leaf.crt.pem
# opt-in HTTP (LAB ONLY):
LAB_CA_ENABLE=1 LAB_PKI_DIR=./lab-pki npm start
curl -sS -D- -X POST http://127.0.0.1:3028/enroll/csr \
  -H 'content-type: application/json' \
  --data-binary @<(python3 -c 'import json,sys; print(json.dumps({"csr":open("dev.csr").read()}))')
# expect: HTTP 201, X-Lab-CA: 1, body.certificate PEM
# default without LAB_CA_ENABLE → 501

Do not point production or shared staging reverse proxies at lab-pki/lab-root.crt.pem.

Purpose

When (and only when) product ships real CSR intake + signing, CI and local dev need a throwaway certificate authority that:

  1. Issues short-lived client certs against known test keys.
  2. Never appears in production builds or default operator paths.
  3. Proves the mint/renew/status code paths without calling a corporate HSM or public ACME.

The test CA is a fixture toolchain, not a staff-iam runtime feature and not a substitute for external PKI in operator docs.

Non-goals (now and later)

Non-goal Why
Return success PEM when LAB_CA_ENABLE is off Default must stay honest 501
Enable lab CA under NODE_ENV=production Hard-refused in server
Ship lab-pki private keys in production images Keys stay gitignored / CI secret only
Soften production mint with static demo PEMs Violates “no fake crypto”; lab path is explicit + headed X-Lab-CA: 1 only
Trust lab roots on real staff fleets Production mint must use a real CA model (see backlog)
Browser “Download test cert” in login / My security Operator path stays proxy/PKI until live mint is real
Reuse test CA roots in staging that faces real staff Test roots are untrusted junk; isolate to CI/dev
Parse or store private keys in IAM user records Metadata only (serial, fingerprint, notAfter) if mint ever lands

Default honest contract: schema 200 fields-only, POST 501, status csrPostStatus: 501. Lab tests may set LAB_CA_ENABLE=1 + temp lab-pki and assert 201 + X-Lab-CA: 1 (skip if no openssl).

Relationship to live PKI backlog

PKI-MINT-BACKLOG.md lists what production mint needs (CA model, AuthZ, CSR policy, bind to user, renew, revoke). Lab/test tooling is a companion, not a substitute:

Product mint (future)          Lab CA (today, opt-in)
─────────────────────          ──────────────────────────
External/HSM/EST signer   ←→   scripts/lab-test-ca.sh
POST /enroll/csr succeeds      LAB_CA_ENABLE=1 + lab-pki → 201 + X-Lab-CA:1
(never default lab)            Default off → 501; production NODE_ENV refuses
Proxy trusts corp roots        Dev proxy may trust lab-pki root (lab only)
Stage Lab script HTTP POST /enroll/csr
Default today Offline init/sign on disk 501 scaffold
Lab opt-in today Same script, server shell-out 201 + X-Lab-CA: 1 when flag + pki ready
Live production mint Still offline fixture / CI helper Real production Signer (drop default 501)

Do not enable LAB_CA_ENABLE by default or in production images.

Layout

Shipped now (offline script only):

platform/verae-staff-iam/
  scripts/
    lab-test-ca.sh             # LAB ONLY: init root + sign CSR + empty crl → PEM on disk
  lab-pki/                     # default output dir (gitignored; keys never committed)

Proposed later (when live mint + gated test signer are scheduled):

platform/verae-staff-iam/
  test/pki/                    # gitignored material OR committed *public* only
    README.md                  # how to regenerate; no private keys in git if policy says so
    root-ca.crt                # test root (public)
    intermediate-ca.crt        # optional
  scripts/
    lab-test-ca.sh             # keep as offline fallback / pre-HTTP fixture
    test-ca-init.sh            # optional rename/split once CI profile solidifies
    test-ca-sign-csr.sh
  src/pki/                     # only if in-process signer is chosen later
    testCa.js                  # gated by STAFF_TEST_CA=1; absent from prod image
                               # MUST NOT call lab-test-ca.sh from default server boot

Private keys for the lab/test CA stay on the developer machine or CI secret store (LAB_PKI_DIR / future TEST_CA_KEY_DIR), never in the IAM JSON user store, never in Docker images tagged for production, never logged. The Node process must not read lab-pki/ unless a future explicit non-production gate is designed — default server path stays 501.

Trust and environment gates

Variable Effect
LAB_CA_ENABLE=1 Shipped. Allows shell-out lab sign on POST /enroll/csr when lab-pki ready
LAB_PKI_DIR Lab CA directory (default ./lab-pki)
NODE_ENV=production Refuses lab CA even if LAB_CA_ENABLE=1
STAFF_MTLS_ENFORCE Unchanged: still requires real proxy verify headers

Fail closed: missing key dir, production env, or unset flag → 501 (no mint).

Signing flow (future test only)

  1. Test generates a keypair + PKCS#10 CSR (OpenSSL, step, or node-forge in the test process).
  2. CSR PEM posted to the future live POST /enroll/csr or passed to test-ca-sign-csr.sh for pure unit tests that do not boot HTTP.
  3. Test CA validates: PEM parse, key type (e.g. P-256 or RSA-2048+), CN/SAN allow-list for fixtures (test-user@staff.local, deviceId prefix test-).
  4. Sign with intermediate; return cert chain PEM (and optional PKCS#12 with a test-only passphrase).
  5. Test asserts response shape, then feeds the client cert to a dev reverse proxy that forwards the same verify/DN/PEM headers ENROLL.md already documents.
  6. GET /enroll/status should show mtlsPresent / proxy flags from headers — still no PEM bodies in status JSON.

In-process mint (if chosen) would call the same validation + sign helpers the production CA client interface uses; the test CA is one implementation of Signer, not a parallel fake route.

What tests should prove

Layer Assertion
Contract (default) GET /enroll/csr/schema → 200, mint: false; POST /enroll/csr → 501; no cert fields
Lab integration (today) LAB_CA_ENABLE=1 + lab-pki + openssl → 201 + X-Lab-CA: 1 + certificate PEM; skip if no openssl
Unit (future production) CSR policy rejects bad PEM / wrong key size / disallowed CN
Integration (future production) Real Signer → 201 without lab headers; serial stored as metadata
Proxy (future) Client presents test cert; proxy headers flip proxyHeadersSeen.*; enforce path works
Negative NODE_ENV=production + test flag → no sign; anonymous rate limit; revoke on offboard

Do not add “golden” static client certs that the HTTP API returns as if newly minted. Fixtures may pre-place cert files on disk for proxy tests without going through mint.

CA profile (suggested defaults)

  • Root: RSA-4096 or P-384, pathlen 1, 10-year notAfter (local only).
  • Intermediate: pathlen 0, EKU clientAuth (+ optional digitalSignature).
  • Leaf: notAfter ≤ 7 days for CI; SAN URI or DNS under .test / email bound to fixture username.
  • Hash: SHA-256.
  • Serial: random 64-bit+; persist fingerprint in test DB only.

Exact algorithms should match whatever production CA model is selected in PKI-MINT-BACKLOG.md so tests exercise the real policy engine.

Operator vs developer story

  • Operators (production): unchanged — issue workstation certs in corporate PKI; proxy terminates mTLS; IAM flag + enforce. See ENROLL.md. Never run lab-test-ca.sh for real staff devices.
  • Developers (lab now): run scripts/lab-test-ca.sh init, set LAB_CA_ENABLE=1, keep material under gitignored lab-pki/, optionally point a local dev proxy at lab-root.crt.pem. Expect X-Lab-CA: 1 on 201 — never treat as production.
  • Developers (default): leave LAB_CA_ENABLE unset → HTTP mint stays 501.
  • CI (future): generate or restore test CA into a job-local volume; tear down after; never publish leaves as release artifacts.

Implementation order (blocked on product)

  1. Finish production CA model decision in PKI-MINT-BACKLOG.md (“Left for live PKI”).
  2. Define Signer interface + AuthN/AuthZ for POST /enroll/csr.
  3. Add this test CA as the first Signer implementation behind env gates.
  4. Wire integration tests; keep production images free of test keys and STAFF_TEST_CA defaults.
  5. Update ENROLL.md / README only when mint is real — not before.

Hard rules

  1. Default server path does not mint — LAB_CA_ENABLE off → 501. Lab HTTP is explicit opt-in only.
  2. No fake crypto in production API — production stays 501 until real Signer; lab 201 always carries X-Lab-CA: 1 + warning body.
  3. Lab/test roots never trust-anchor production.
  4. Never enable lab CA by default or under NODE_ENV=production.
  5. Cross-link changes: production mint PR must update PKI-MINT-BACKLOG.md, this doc, and ENROLL.md together.
  • PKI-MINT-BACKLOG.md — shipped proxy/schema/501 surface; live mint checklist; no-fake-crypto rules
  • CRL-OCSP-DESIGN.md — future production CRL/OCSP; lab empty CRL helper
  • ENROLL.md — mTLS states, proxy headers, CSR schema/scaffold
  • README.md — service overview and security gates
  • TOTP.md — orthogonal authenticator 2FA (real crypto; unrelated to device-cert mint)