summaryrefslogtreecommitdiff
path: root/source/storage/KeyManager.mc
diff options
context:
space:
mode:
authorcirakg <cirakg@cirakg.xyz>2026-10-20 01:51:05 +0200
committercirakg <cirakg@cirakg.xyz>2026-10-20 01:51:05 +0200
commitbc228753bfb4a2faea540cddb20fc39ebd265db5 (patch)
treecd959eb8a037cea49282fc5cfef53bc9209efbb0 /source/storage/KeyManager.mc
parent81a5b903297e59d599dd0d6f580b89597630909d (diff)
feat(storage): add keyed secret fingerprints to record headers
An 8-byte HMAC of kind, type and secret (key derived from the device key) in header tag 13 lets imports find duplicates of locked entries while the watch is locked. Vault gains anyHeader() and storage(). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Diffstat (limited to 'source/storage/KeyManager.mc')
-rw-r--r--source/storage/KeyManager.mc24
1 files changed, 24 insertions, 0 deletions
diff --git a/source/storage/KeyManager.mc b/source/storage/KeyManager.mc
index ebb0a62..567288f 100644
--- a/source/storage/KeyManager.mc
+++ b/source/storage/KeyManager.mc
@@ -33,6 +33,9 @@ class KeyManager {
private var _store as Persist.Backend;
private var _deviceKeys as SealedBox.Keys? = null;
+ private var _fingerprintKey as ByteArray? = null;
+
+ static const FINGERPRINT_BYTES = 8;
function initialize(store as Persist.Backend) {
_store = store;
@@ -55,11 +58,32 @@ class KeyManager {
_store.setValue(DEVICE_KEY, raw);
}
keys = new SealedBox.Keys(raw);
+ _fingerprintKey = Kdf.hkdf(raw, []b, Bytes.fromUtf8("OTPW fp v1"), KEY_BYTES);
Bytes.wipe(raw);
_deviceKeys = keys;
return keys;
}
+ // An 8-byte keyed fingerprint of an entry's kind, type and secret (or
+ // payload): equal for duplicates, meaningless without the device key.
+ // Stored in the readable header so imports can find duplicates of locked
+ // entries while the watch is locked (docs/SECURITY.md).
+ function fingerprint(entry as Entry) as ByteArray {
+ deviceKeys();
+ var input = Bytes.fromUtf8("OTPW-FP-1");
+ input.add(entry.kind);
+ input.add(entry.type);
+ var material = entry.kind == Entry.KIND_OTP ? entry.secret : entry.payload;
+ if (material != null) {
+ input.addAll(material);
+ }
+ var mac = Hmac.nativeSha256(_fingerprintKey as ByteArray, input);
+ Bytes.wipe(input);
+ var out = mac.slice(0, FINGERPRINT_BYTES);
+ Bytes.wipe(mac);
+ return out;
+ }
+
// True when a PIN has been set.
function hasPin() as Boolean {
return _store.getValue(WRAP_KEY) != null;