Step 17: Encryption core — passphrase-only, selective per-file.
Manifest schema: Entry gains Encrypted, PlaintextHash fields. Manifest gains Encryption section with KekSlots map (passphrase slot with Argon2id params, salt, and wrapped DEK as base64). garden/encrypt.go: EncryptInit (generate DEK, wrap with passphrase KEK), UnlockDEK (derive KEK, unwrap), encryptBlob/decryptBlob using XChaCha20-Poly1305 with random 24-byte nonces. Modified operations: - Add: optional encrypt flag, stores encrypted blob + plaintext_hash - Checkpoint: detects changes via plaintext_hash, re-encrypts - Restore: decrypts encrypted blobs before writing - Diff: decrypts stored blob before comparing - Status: compares against plaintext_hash for encrypted entries 10 tests covering init, persistence, unlock, add-encrypted, restore round-trip, checkpoint, status, diff, requires-DEK guard. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -11,21 +11,41 @@ import (
|
||||
|
||||
// Entry represents a single tracked file, directory, or symlink.
|
||||
type Entry struct {
|
||||
Path string `yaml:"path"`
|
||||
Hash string `yaml:"hash,omitempty"`
|
||||
Type string `yaml:"type"`
|
||||
Mode string `yaml:"mode,omitempty"`
|
||||
Target string `yaml:"target,omitempty"`
|
||||
Updated time.Time `yaml:"updated"`
|
||||
Path string `yaml:"path"`
|
||||
Hash string `yaml:"hash,omitempty"`
|
||||
PlaintextHash string `yaml:"plaintext_hash,omitempty"`
|
||||
Encrypted bool `yaml:"encrypted,omitempty"`
|
||||
Type string `yaml:"type"`
|
||||
Mode string `yaml:"mode,omitempty"`
|
||||
Target string `yaml:"target,omitempty"`
|
||||
Updated time.Time `yaml:"updated"`
|
||||
}
|
||||
|
||||
// KekSlot describes a single KEK source that can unwrap the DEK.
|
||||
type KekSlot struct {
|
||||
Type string `yaml:"type"` // "passphrase" or "fido2"
|
||||
Argon2Time int `yaml:"argon2_time,omitempty"` // passphrase only
|
||||
Argon2Memory int `yaml:"argon2_memory,omitempty"` // passphrase only (KiB)
|
||||
Argon2Threads int `yaml:"argon2_threads,omitempty"` // passphrase only
|
||||
CredentialID string `yaml:"credential_id,omitempty"` // fido2 only (base64)
|
||||
Salt string `yaml:"salt"` // base64-encoded
|
||||
WrappedDEK string `yaml:"wrapped_dek"` // base64-encoded
|
||||
}
|
||||
|
||||
// Encryption holds the encryption configuration embedded in the manifest.
|
||||
type Encryption struct {
|
||||
Algorithm string `yaml:"algorithm"`
|
||||
KekSlots map[string]*KekSlot `yaml:"kek_slots"`
|
||||
}
|
||||
|
||||
// Manifest is the top-level manifest describing all tracked entries.
|
||||
type Manifest struct {
|
||||
Version int `yaml:"version"`
|
||||
Created time.Time `yaml:"created"`
|
||||
Updated time.Time `yaml:"updated"`
|
||||
Message string `yaml:"message,omitempty"`
|
||||
Files []Entry `yaml:"files"`
|
||||
Version int `yaml:"version"`
|
||||
Created time.Time `yaml:"created"`
|
||||
Updated time.Time `yaml:"updated"`
|
||||
Message string `yaml:"message,omitempty"`
|
||||
Files []Entry `yaml:"files"`
|
||||
Encryption *Encryption `yaml:"encryption,omitempty"`
|
||||
}
|
||||
|
||||
// New creates a new empty manifest with Version 1 and timestamps set to now.
|
||||
|
||||
Reference in New Issue
Block a user