mirror of
https://github.com/shankar0123/certctl.git
synced 2026-06-07 12:41:30 +00:00
90210c9334
Pre-login rows previously persisted the OIDC state, nonce, and PKCE
verifier as plaintext columns; an operator restoring an unredacted
backup of oidc_pre_login_sessions to a debug environment leaked every
in-flight handshake. If the IdP also leaked the auth code in the same
window (logged at a misconfigured TLS terminator, etc.), the attacker
could exchange code + verifier directly. RFC 7636 §7 requires verifier
confidentiality.
This commit:
- Migration 000041 adds {state,nonce,pkce_verifier}_enc BYTEA columns
and makes the legacy plaintext columns nullable. A follow-up
migration drops the plaintext columns once the rolling deploy
completes.
- internal/repository/postgres/oidc_prelogin.go::Create encrypts the
three secrets via crypto.EncryptIfKeySet (v3 magic 0x03 + per-row
salt + nonce + AES-256-GCM tag) and writes only the encrypted
columns; legacy plaintext stays NULL on the write path.
- LookupAndConsume prefers encrypted columns via materialize(),
falling back to the legacy plaintext only when _enc is NULL — the
rolling-deploy compat layer that 000042 will retire.
- NewPreLoginRepository takes encryptionKey; cmd/server/main.go threads
cfg.Encryption.ConfigEncryptionKey in.
- Encryption key reuses CERTCTL_CONFIG_ENCRYPTION_KEY (same passphrase
already protecting OIDC client secrets and SessionSigningKey material).
No new env var.
Why encryption-at-rest, not HMAC: the spec's HMAC approach required
moving plaintext into the cookie (the cookie currently carries only
row ID + HMAC). Re-shaping the cookie wire format would be a larger
refactor; the audit explicitly admits encryption-at-rest is an
acceptable closure (weaker because backups still contain decryptable
ciphertext, but the encryption key is held separately from the DB
backup, and the 10-minute TTL further bounds usable secret window).
Three new regression tests in oidc_prelogin_encryption_test.go pin:
(a) _enc columns contain v3-format ciphertext, NOT plaintext
substrings, post-Create
(b) legacy plaintext columns are NULL post-Create (defends against
future patches that re-introduce plaintext writes)
(c) LookupAndConsume round-trips state/nonce/verifier byte-for-byte
A fourth test pins the legacy-row fallback for rolling-deploy compat.
Refs: cowork/auth-bundles-audit-2026-05-10.md HIGH-5
Spec: cowork/auth-bundles-fixes-2026-05-10/09-high-5-prelogin-secret-protection.md
39 lines
2.1 KiB
SQL
39 lines
2.1 KiB
SQL
-- =============================================================================
|
|
-- 2026-05-10 Audit / HIGH-5 closure
|
|
-- =============================================================================
|
|
--
|
|
-- Pre-login rows in oidc_pre_login_sessions used to persist OIDC state, nonce,
|
|
-- and the PKCE verifier as plaintext columns. An operator restoring a backup
|
|
-- to a debug environment without redacting handshake-table data leaked every
|
|
-- in-flight verifier; combined with a separately-leaked authorization code
|
|
-- (e.g. logged at a misconfigured TLS terminator), the attacker could exchange
|
|
-- code + verifier directly. RFC 7636 §7 requires verifier confidentiality.
|
|
--
|
|
-- This migration adds {state,nonce,pkce_verifier}_enc BYTEA columns alongside
|
|
-- the existing plaintext columns. The new repository write path emits only the
|
|
-- encrypted columns (via internal/crypto.EncryptIfKeySet, v3 blob format —
|
|
-- magic(0x03) || salt(16) || nonce(12) || ciphertext+tag, AES-256-GCM with
|
|
-- per-row salt + nonce). The existing plaintext columns are made nullable so
|
|
-- the new write path doesn't have to populate them; in-flight handshakes from
|
|
-- pre-deploy code paths still consume the legacy plaintext columns until the
|
|
-- 10-minute absolute TTL expires every legacy row.
|
|
--
|
|
-- A follow-up migration (queued for v2.1.1) drops the plaintext columns once
|
|
-- the rolling deploy completes. We do NOT bundle the DROP into 000041 because
|
|
-- in-flight handshakes during deploy would break.
|
|
--
|
|
-- The encryption key reuses CERTCTL_CONFIG_ENCRYPTION_KEY — the same passphrase
|
|
-- already protecting OIDC client secrets, session signing keys, and other
|
|
-- secret-bearing rows. No new env var.
|
|
-- =============================================================================
|
|
|
|
ALTER TABLE oidc_pre_login_sessions
|
|
ADD COLUMN IF NOT EXISTS state_enc BYTEA,
|
|
ADD COLUMN IF NOT EXISTS nonce_enc BYTEA,
|
|
ADD COLUMN IF NOT EXISTS pkce_verifier_enc BYTEA;
|
|
|
|
ALTER TABLE oidc_pre_login_sessions
|
|
ALTER COLUMN state DROP NOT NULL,
|
|
ALTER COLUMN nonce DROP NOT NULL,
|
|
ALTER COLUMN pkce_verifier DROP NOT NULL;
|