Add mciasdb rekey command

- internal/db/accounts.go: add ListAccountsWithTOTP,
  ListAllPGCredentials, TOTPRekeyRow, PGRekeyRow, and
  Rekey — atomic transaction that replaces master_key_salt,
  signing_key_enc/nonce, all TOTP enc/nonce, and all
  pg_password enc/nonce in one SQLite BEGIN/COMMIT
- cmd/mciasdb/rekey.go: runRekey — decrypts all secrets
  under old master key, prompts for new passphrase (with
  confirmation), derives new key from fresh Argon2id salt,
  re-encrypts everything, and commits atomically
- cmd/mciasdb/main.go: wire "rekey" command + update usage
- Tests: DB-layer tests for ListAccountsWithTOTP,
  ListAllPGCredentials, Rekey (happy path, empty DB, salt
  replacement); command-level TestRekeyCommandRoundTrip
  verifies full round-trip and adversarially confirms old
  key no longer decrypts after rekey

Security: fresh random salt is always generated so a
reused passphrase still produces an independent key; old
and new master keys are zeroed via defer; no passphrase or
key material appears in logs or audit events; the entire
re-encryption is done in-memory before the single atomic
DB write so the database is never in a mixed state.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-03-15 13:27:29 -07:00
parent 23a27be57e
commit 0d38bbae00
5 changed files with 658 additions and 0 deletions

View File

@@ -39,6 +39,8 @@
//
// pgcreds get --id UUID
// pgcreds set --id UUID --host H --port P --db D --user U
//
// rekey
package main
import (
@@ -107,6 +109,8 @@ func main() {
tool.runAudit(subArgs)
case "pgcreds":
tool.runPGCreds(subArgs)
case "rekey":
tool.runRekey(subArgs)
default:
fatalf("unknown command %q; run with no args for usage", command)
}
@@ -259,6 +263,9 @@ Commands:
pgcreds set --id UUID --host H [--port P] --db D --user U
(password is prompted interactively)
rekey Re-encrypt all secrets under a new master passphrase
(prompts interactively; requires server to be stopped)
NOTE: mciasdb bypasses the mciassrv API and operates directly on the SQLite
file. Use it only when the server is unavailable or for break-glass recovery.
All write operations are recorded in the audit log.