Compare commits
1
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
8d3e6da65c |
@@ -180,8 +180,8 @@ period.
|
||||
|
||||
#### `secret version promote <secret-name> <version>`
|
||||
|
||||
Promotes a specific version to current by updating the symlink. Does not modify
|
||||
any timestamps, allowing for rollback scenarios.
|
||||
Promotes a specific version to current by rewriting the secret's `current` file
|
||||
to name it. Does not modify any timestamps, allowing for rollback scenarios.
|
||||
|
||||
#### `secret version remove <secret-name> <version> [--force]` / `secret version rm` ⚠️ 🛑
|
||||
|
||||
@@ -278,8 +278,13 @@ Decrypts data using an Age key stored as a secret.
|
||||
|
||||
### Directory Structure
|
||||
|
||||
The state directory is `berlin.sneak.pkg.secret` in the user's configuration
|
||||
directory: on Linux `$XDG_CONFIG_HOME`, or `~/.config` when that is unset; on
|
||||
macOS `~/Library/Application Support`. When `SB_SECRET_STATE_DIR` is set, it is
|
||||
the state directory instead. On Linux:
|
||||
|
||||
```
|
||||
~/.local/share/secret/
|
||||
~/.config/berlin.sneak.pkg.secret/
|
||||
├── vaults.d/
|
||||
│ ├── default/
|
||||
│ │ ├── unlockers.d/
|
||||
@@ -292,12 +297,12 @@ Decrypts data using an Age key stored as a secret.
|
||||
│ │ │ │ │ │ ├── pub.age # Version public key
|
||||
│ │ │ │ │ │ ├── priv.age # Version private key (encrypted)
|
||||
│ │ │ │ │ │ ├── value.age # Encrypted value
|
||||
│ │ │ │ │ │ └── metadata.json # Unencrypted metadata
|
||||
│ │ │ │ │ │ └── metadata.age # Encrypted metadata
|
||||
│ │ │ │ │ └── 20231216.001/ # Another version
|
||||
│ │ │ │ └── current -> versions/20231216.001
|
||||
│ │ │ │ └── current # Current version's name: 20231216.001
|
||||
│ │ │ └── database%password/ # Secret: database/password
|
||||
│ │ │ ├── versions/
|
||||
│ │ │ └── current -> versions/20231215.001
|
||||
│ │ │ └── current # Current version's name: 20231215.001
|
||||
│ │ ├── vault-metadata.json # Vault metadata
|
||||
│ │ ├── pub.age # Long-term public key
|
||||
│ │ └── current-unlocker # Current unlocker's directory name
|
||||
@@ -307,9 +312,13 @@ Decrypts data using an Age key stored as a secret.
|
||||
│ ├── vault-metadata.json
|
||||
│ ├── pub.age
|
||||
│ └── current-unlocker
|
||||
└── currentvault -> vaults.d/default
|
||||
├── currentvault # Current vault's name: default
|
||||
└── lock # Locked by each command that changes anything
|
||||
```
|
||||
|
||||
`current`, `currentvault` and `current-unlocker` are plain files that each hold
|
||||
one name. Changing one replaces it in one rename, so it is never half-written.
|
||||
|
||||
### Key Management and Encryption Flow
|
||||
|
||||
#### 1: Long-term Keys
|
||||
@@ -336,7 +345,7 @@ Unlockers provide different authentication methods to access the long-term keys:
|
||||
|
||||
3. **Keychain Unlockers** (macOS only):
|
||||
- Stores unlock keys in macOS Keychain
|
||||
- Protected by system authentication (Touch ID, password)
|
||||
- Kept on this Mac only: the keychain item is never synced to other devices
|
||||
- Automatic unlocking when Keychain is unlocked
|
||||
- Cross-application integration
|
||||
|
||||
@@ -344,8 +353,10 @@ Unlockers provide different authentication methods to access the long-term keys:
|
||||
- Hardware-backed key storage using Apple Secure Enclave
|
||||
- Uses `sc_auth` / CryptoTokenKit for SE key management (no Apple Developer
|
||||
Program required)
|
||||
- ECIES encryption: vault long-term key encrypted directly by SE hardware
|
||||
- Protected by biometric authentication (Touch ID) or system password
|
||||
- ECIES encryption: the vault long-term key is encrypted directly to the SE
|
||||
key, and only the SE can decrypt it
|
||||
- The SE key cannot leave this Mac; using it asks for no Touch ID or
|
||||
password
|
||||
|
||||
Each vault maintains its own set of unlockers and one long-term key. The
|
||||
long-term key is encrypted to each unlocker, allowing any authorized unlocker to
|
||||
@@ -355,7 +366,7 @@ access vault secrets.
|
||||
|
||||
- Each secret version has its own encryption key pair
|
||||
- Private key encrypted to the vault's long-term key
|
||||
- Provides forward secrecy and granular access control
|
||||
- A version's private key decrypts only that version's value and metadata
|
||||
|
||||
### Environment Variables
|
||||
|
||||
@@ -505,17 +516,21 @@ secret decrypt encryption/mykey --input document.txt.age --output document.txt
|
||||
|
||||
### File Formats
|
||||
|
||||
- **age Files**: Standard age encryption format (.age extension)
|
||||
- **Metadata**: Unencrypted JSON format with timestamps and type information
|
||||
- **Vault Metadata**: JSON containing vault name, creation time, derivation
|
||||
index, and public key hash
|
||||
- **age Files**: Standard age encryption format (.age extension), except
|
||||
`pub.age`, which holds an age public key as text
|
||||
- **Metadata**: `vault-metadata.json` and `unlocker-metadata.json` are
|
||||
unencrypted JSON with a creation time, and `unlocker-metadata.json` also
|
||||
records the unlocker's type; a version's `metadata.age` is JSON encrypted to
|
||||
the version's public key
|
||||
- **Vault Metadata**: JSON containing creation time, derivation index, and the
|
||||
public key hashes described below
|
||||
|
||||
### Vault Management
|
||||
|
||||
- **Derivation Index**: Each vault uses a unique derivation index from the
|
||||
mnemonic, and thus a unique key pair
|
||||
- **Public Key Hash**: Double SHA-256 hash of the index-0 public key identifies
|
||||
vaults from the same mnemonic
|
||||
- **Public Key Hash**: Double SHA-256 hash of the vault's public key; the same
|
||||
hash of the index-0 public key identifies vaults from the same mnemonic
|
||||
- **Automatic Key Derivation**: When creating vaults with a mnemonic, keys are
|
||||
automatically derived
|
||||
|
||||
@@ -566,8 +581,6 @@ The project includes comprehensive tests:
|
||||
|
||||
```bash
|
||||
make test # Run all tests
|
||||
go test ./... # Unit tests
|
||||
go test -tags=integration -v ./internal/cli # Integration tests
|
||||
```
|
||||
|
||||
## Entrypoints
|
||||
|
||||
@@ -18,6 +18,17 @@ https://git.eeqj.de/sneak/secret/milestone/12
|
||||
|
||||
# Completed Steps
|
||||
|
||||
- 2026-10-04: README's Storage Architecture, `secret version promote`,
|
||||
Technical Details and Testing text matches the code
|
||||
(https://git.eeqj.de/sneak/secret/issues/102). `current` and
|
||||
`currentvault` are plain files holding a name, not symbolic links; a
|
||||
version's metadata is the encrypted `metadata.age`; the state directory is
|
||||
`berlin.sneak.pkg.secret` in the user's configuration directory, not
|
||||
`~/.local/share/secret`, and holds the `lock` file. Also corrected: the
|
||||
code sets up no Touch ID for the keychain or Secure Enclave unlocker, and
|
||||
the Secure Enclave only decrypts; per-version keys give no forward
|
||||
secrecy; `pub.age` is not age-encrypted; vault metadata holds no vault
|
||||
name. Testing lists only `make test`.
|
||||
- 2026-10-04: A failed `secret unlocker add keychain` or
|
||||
`secret unlocker add secure-enclave` no longer leaves its keychain item or
|
||||
Secure Enclave key behind (https://git.eeqj.de/sneak/secret/issues/89).
|
||||
|
||||
Reference in New Issue
Block a user