Implement secure OS-backed secret storage
This commit is contained in:
@@ -46,6 +46,8 @@ Git remotes are HTTPS-only. URLs containing user information, passwords,
|
||||
queries, or fragments are rejected. `server_id` and `application_id` are opaque
|
||||
references used to retrieve credentials from the operating-system secret
|
||||
store; duplicate names and duplicate reference pairs are errors.
|
||||
The HTTPS account name is stored inside the protected credential record, not in
|
||||
TOML. OpenPGP passphrases are addressed by the resolved primary fingerprint.
|
||||
|
||||
Passwords, passphrases, tokens, credentials, private keys, and other secret
|
||||
values are forbidden in TOML. Unknown fields are rejected. Parse errors never
|
||||
|
||||
@@ -52,6 +52,9 @@ debug representation is redacted and its allocation is zeroed on drop. The
|
||||
OpenPGP backend's password type also zeroes its owned storage. Plaintext enters
|
||||
the message encoder through an owning reader instead of being copied into an
|
||||
ordinary intermediate buffer.
|
||||
The production provider is the storage-owned native secret store documented in
|
||||
[`secure-secret-storage.md`](secure-secret-storage.md); protected-key
|
||||
passphrases are addressed only by their primary fingerprint.
|
||||
|
||||
Detached `.gpg-id.sig` files use binary-document signatures with SHA-256 and
|
||||
issuer fingerprint/key-ID metadata. Verification succeeds only when the valid
|
||||
|
||||
54
docs/secure-secret-storage.md
Normal file
54
docs/secure-secret-storage.md
Normal file
@@ -0,0 +1,54 @@
|
||||
# Secure secret storage
|
||||
|
||||
`crates/storage` owns the complete secret-storage contract. Configuration,
|
||||
password-store repositories, Git configuration, command arguments, and logs
|
||||
contain only opaque identifiers; passphrases, tokens, and HTTPS passwords are
|
||||
stored by the operating system.
|
||||
|
||||
`SecretReference` has two validated forms. OpenPGP passphrases are keyed by the
|
||||
primary fingerprint. HTTPS Git credentials are keyed by purpose, server ID,
|
||||
application ID, and account. The account is kept inside the protected record,
|
||||
so a configured server/application pair can retrieve it without adding an
|
||||
account or secret value to TOML. References, locators, store state, and errors
|
||||
all use redacted `Debug` output.
|
||||
|
||||
Stored values use a small versioned binary envelope containing their reference
|
||||
and secret bytes. Retrieval validates the envelope and exact reference before
|
||||
release. Malformed or ambiguous records produce a typed `Corrupted` error;
|
||||
asking for another account at the same Git locator produces `Missing` without
|
||||
altering the stored account. `create` refuses to overwrite, `replace` requires
|
||||
an exact existing record, and missing, denied, cancelled, locked, corrupted,
|
||||
unsupported-policy, and unavailable-store results remain distinct. Returned
|
||||
and cached bytes use `SecretBytes`, which zeroizes its allocation when dropped.
|
||||
|
||||
## Operating-system adapters
|
||||
|
||||
The platform-selection code is isolated in
|
||||
`crates/storage/src/secret_store/platform.rs` and calls only safe Rust APIs:
|
||||
|
||||
- macOS uses legacy Keychain for command-line-compatible device-unlocked
|
||||
credentials and Protected Data for `RequireUserPresence`; iOS uses Protected
|
||||
Data. User cancellation is mapped from the native Security Framework status.
|
||||
- Windows uses Credential Manager. Store operations are serialized because the
|
||||
upstream adapter documents unreliable same-entry sequencing across threads.
|
||||
- Linux uses Secret Service through zbus with the Rust cryptography feature. It
|
||||
does not launch `secret-tool`, a shell, or any other helper. A missing session
|
||||
service is a typed `Unavailable` result.
|
||||
|
||||
IronStorage contains no direct platform FFI or unsafe Rust. The selected adapter
|
||||
crates own their OS calls, so there is no project-local FFI safety contract
|
||||
beyond providing validated UTF-8 identifiers and bounded byte slices.
|
||||
|
||||
## Locking and caching
|
||||
|
||||
A new store starts logically locked. `unlock` must succeed before create,
|
||||
retrieve, replace, or delete; `lock` immediately clears all cached values even
|
||||
if the platform lock operation reports an error. Caching is disabled unless a
|
||||
caller explicitly selects `SecretCachePolicy::Timed`. Timed policies are capped
|
||||
at 128 entries and 15 minutes, expire lazily, and are always cleared on lock.
|
||||
|
||||
The same unlocked store implements the OpenPGP `SecretProvider` and HTTPS Git
|
||||
`GitCredentialProvider`. The CLI uses it for terminal `show` and embedded `git
|
||||
fetch`, proving that protected keys and remote authentication are resolved only
|
||||
through opaque references. Tests inject a memory backend and never access a
|
||||
developer or CI user keyring.
|
||||
Reference in New Issue
Block a user