docs: re-lead positioning on the two-factor-KDF thesis; document the key-file second factor
README: open with the two-independent-secrets-into-KDF thesis; reframe the
second factor as pluggable (reference image or key file); keep the stego
deep-dive as a distinctive option; update all scenario tables, recovery section,
env-var hint, and roadmap line to use "second-factor secret" language.
docs/CRYPTO.md: add "Pluggable second-factor containers" subsection explaining
that the 32-byte input to Argon2id is identical regardless of container;
reference-image / key-file / recovery-QR are interchangeable carriers.
Update org-vs-personal comparison table and entropy labels.
docs/FORMATS.md: document the .relkey armor format (relicario-keyfile-v1 +
base64 line) and the params.json second_factor field ("image"|"keyfile",
absent means "image" for back-compat). Cite crates/relicario-core/src/keyfile.rs
keyfile_encode/keyfile_decode with TODO-pin comments. Update KDF input formula
from image_secret to second_factor_secret.
DESIGN.md: split secrets-map "Reference image bytes" row to distinguish image
and key-file containers; add key-file row noting keyfileBase64 in
chrome.storage.local has the same in-the-clear posture as imageBase64.
Update vault-layout note and KDF conventions row.
docs/SECURITY.md: add "Second-factor storage posture" subsection explicitly
stating .relkey and keyfileBase64 are the second factor in the clear — same
posture as the reference JPEG / imageBase64, not weaker, protected by the
passphrase invariant. Add RELICARIO_KEYFILE to the env-var trust surface table.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014THSV6cA4Gxa7bxFfisHBB
This commit is contained in:
@@ -106,7 +106,7 @@ Every relicario vault — whether on disk for the CLI or in a git remote read by
|
||||
└── <aid>.enc # encrypted attachment blob; aid is content-addressed SHA-256
|
||||
```
|
||||
|
||||
The reference image (`reference.jpg`) lives **outside** the vault by convention — it is the second factor and the user's responsibility to safeguard. It is not in `.relicario/`, not in `items/`, and never committed to git.
|
||||
The second-factor container (either `reference.jpg` for the reference-image path or a `.relkey` file for the key-file path) lives **outside** the vault by convention — it is the second factor and the user's responsibility to safeguard. It is not in `.relicario/`, not in `items/`, and never committed to git.
|
||||
|
||||
This layout is not formally versioned — the **content** within each `.enc` file carries its own version byte (see § Versioning below). The directory layout itself is conventional and changes would be breaking.
|
||||
|
||||
@@ -142,8 +142,9 @@ The threat model differs by codebase. This is the per-secret per-codebase reside
|
||||
| Secret | relicario-core | relicario-cli | extension SW | extension popup/vault/content/setup |
|
||||
|---|---|---|---|---|
|
||||
| Passphrase (UTF-8 bytes) | `Zeroizing<String>` only during a single `derive_master_key` call | Same, in `UnlockedVault::unlock_interactive` | Same, used briefly to derive master key inside WASM | Never seen — entered into a `<input type="password">`, sent to SW via `unlock` message, immediately forgotten |
|
||||
| Reference image bytes | Held by caller; core only reads | Held by `UnlockedVault::unlock_interactive` long enough to extract the secret | Same | Setup wizard holds the bytes briefly during create/attach modes |
|
||||
| Image secret (32 B) | `Zeroizing<[u8;32]>` during KDF | Same | Same | Never sees it |
|
||||
| Reference image bytes (image-path vaults) | Held by caller; core only reads | Held by `UnlockedVault::unlock_interactive` long enough to call `imgsecret::extract()` | Same (stored as `imageBase64` in `chrome.storage.local` — in the clear, same posture as the key file below) | Setup wizard holds the bytes briefly during create/attach modes |
|
||||
| Key file bytes (key-file-path vaults) | Held by caller; core only reads via `keyfile_decode()` | Held by `UnlockedVault::unlock_interactive` long enough to decode the secret | Stored as `keyfileBase64` in `chrome.storage.local` — in the clear, same posture as `imageBase64` above | Setup wizard holds the bytes briefly during create mode |
|
||||
| Second-factor secret (32 B) — extracted from either container | `Zeroizing<[u8;32]>` during KDF | Same | Same | Never sees it |
|
||||
| Master key | `Zeroizing<[u8;32]>` returned by `derive_master_key` | `UnlockedVault.master_key` for the lifetime of one CLI invocation | WASM-side memory behind an opaque `SessionHandle`; JS never sees the bytes | Never sees it |
|
||||
| Item secret (password, card number, etc.) | `Zeroizing<String>` / `Zeroizing<Vec<u8>>` | Same | Briefly held in WASM during `item_decrypt`; results passed to popup as plaintext for display | Held in DOM (the user is staring at it); cleared when view changes |
|
||||
| Device private key | — | Filesystem under `~/.config/relicario/devices/<name>.key` (mode 0600) | `chrome.storage.local.device_private_key` | — |
|
||||
@@ -200,7 +201,7 @@ Core tests use **fast Argon2id params** (m=256, t=1, p=1) so they don't take for
|
||||
| AEAD ciphertext starts with version byte | `core/crypto.rs` | Format identification; reject v1 blobs cleanly |
|
||||
| Item IDs are random 16-char hex (64 bits) | `core/ids.rs` | Stable, short, no information leak |
|
||||
| Attachment IDs are content-addressed (first 32 hex chars / 128 bits of SHA-256) | `core/ids.rs` | Dedup; integrity check |
|
||||
| KDF input is length-prefixed | `core/crypto.rs` | Prevents `passphrase || image_secret` collisions |
|
||||
| KDF input is length-prefixed | `core/crypto.rs` | Prevents `passphrase || second_factor_secret` collisions |
|
||||
| Git history is preserved as audit log; never squash | CLI commits; SW commits | Per-action history is a feature |
|
||||
| Per-action git commits with structured messages | `cli` (via `commit_paths`); SW (via vault.ts helpers) | Greppable, useful as audit log |
|
||||
| Hardened git invocations (`-c core.hooksPath=/dev/null` etc.) | CLI's `helpers::git_command`; SW does not shell out | Prevent hostile hooks; no GPG prompt holding key alive |
|
||||
|
||||
Reference in New Issue
Block a user