This commit is contained in:
2026-08-10 21:15:19 +01:00
parent d7ca591b76
commit 4da942786e
97 changed files with 105039 additions and 3370 deletions
+26
View File
@@ -12,6 +12,7 @@ import (
"encoding/hex"
"fmt"
"io"
"sync"
"golang.org/x/crypto/hkdf"
)
@@ -57,17 +58,42 @@ func decodeKey(hexStr string) ([]byte, error) {
return b, nil
}
// keyCache memoizes deriveKey results so repeatedly encrypting/decrypting the
// same record (e.g. re-viewing the same folder) skips the HKDF recompute —
// this is a real, measured cost when done per-message across a folder
// listing. Keyed on the raw master key bytes (as a string; no new exposure,
// the same bytes already live in-process via MasterKey.Current/Previous)
// alongside recordID/purpose so a key-rotation's Current vs Previous never
// collide.
// ponytail: unbounded — every distinct (recordID,purpose) ever seen stays
// cached for the process lifetime. Add an LRU/TTL eviction if a long-uptime
// instance with a very large distinct-message count ever makes this a real
// memory concern; not needed for a first pass.
var keyCache sync.Map
type keyCacheKey struct {
master string
recordID string
purpose string
}
// deriveKey produces a per-record 32-byte key from the master key using HKDF-SHA256.
// recordID should be a stable, unique identifier for the record (e.g. message ID,
// contact UID) — using the same recordID always derives the same key, which is
// required for decryption to work.
func deriveKey(master []byte, recordID string, purpose string) ([]byte, error) {
ck := keyCacheKey{master: string(master), recordID: recordID, purpose: purpose}
if v, ok := keyCache.Load(ck); ok {
return v.([]byte), nil
}
info := []byte(purpose + ":" + recordID)
r := hkdf.New(sha256.New, master, nil, info)
key := make([]byte, keySize)
if _, err := io.ReadFull(r, key); err != nil {
return nil, fmt.Errorf("hkdf derive: %w", err)
}
keyCache.Store(ck, key)
return key, nil
}