raz-encrypt 2.3.0 → 2.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -21
- package/README.md +488 -448
- package/dist/canonicalJson.d.ts +15 -0
- package/dist/canonicalJson.js +73 -13
- package/dist/canonicalJson.js.map +1 -1
- package/dist/core.d.ts +4 -2
- package/dist/core.js +3 -1
- package/dist/core.js.map +1 -1
- package/dist/crypto/classical.d.ts +2 -0
- package/dist/crypto/classical.js +4 -0
- package/dist/crypto/classical.js.map +1 -1
- package/dist/crypto/confirmedSession.d.ts +47 -0
- package/dist/crypto/confirmedSession.js +84 -0
- package/dist/crypto/confirmedSession.js.map +1 -0
- package/dist/crypto/frameCipher.js +8 -2
- package/dist/crypto/frameCipher.js.map +1 -1
- package/dist/crypto/frameSession.d.ts +40 -0
- package/dist/crypto/frameSession.js +96 -0
- package/dist/crypto/frameSession.js.map +1 -0
- package/dist/crypto/hybrid.d.ts +95 -8
- package/dist/crypto/hybrid.js +374 -49
- package/dist/crypto/hybrid.js.map +1 -1
- package/dist/crypto/nodeHybrid.d.ts +6 -4
- package/dist/crypto/nodeHybrid.js +3 -2
- package/dist/crypto/nodeHybrid.js.map +1 -1
- package/dist/crypto/pqc.d.ts +7 -2
- package/dist/crypto/pqc.js +13 -4
- package/dist/crypto/pqc.js.map +1 -1
- package/dist/crypto/ratchet.js +5 -0
- package/dist/crypto/ratchet.js.map +1 -1
- package/dist/fileCrypto.d.ts +44 -2
- package/dist/fileCrypto.js +198 -28
- package/dist/fileCrypto.js.map +1 -1
- package/dist/identity.d.ts +69 -4
- package/dist/identity.js +148 -13
- package/dist/identity.js.map +1 -1
- package/dist/index.d.ts +8 -7
- package/dist/index.js +4 -4
- package/dist/index.js.map +1 -1
- package/dist/passphrase.d.ts +27 -0
- package/dist/passphrase.js +69 -0
- package/dist/passphrase.js.map +1 -0
- package/dist/store.d.ts +43 -6
- package/dist/store.js +296 -23
- package/dist/store.js.map +1 -1
- package/dist/trust.d.ts +170 -2
- package/dist/trust.js +527 -33
- package/dist/trust.js.map +1 -1
- package/package.json +68 -69
package/dist/store.js
CHANGED
|
@@ -1,14 +1,96 @@
|
|
|
1
|
+
import { gcm } from "@noble/ciphers/aes.js";
|
|
2
|
+
import { equalBytes } from "@noble/ciphers/utils.js";
|
|
3
|
+
import { hkdf } from "@noble/hashes/hkdf.js";
|
|
4
|
+
import { hmac } from "@noble/hashes/hmac.js";
|
|
1
5
|
import { sha384 } from "@noble/hashes/sha2.js";
|
|
2
|
-
import { bytesToHex, utf8ToBytes } from "@noble/hashes/utils.js";
|
|
3
|
-
import { appendFile, mkdir, readFile } from "node:fs/promises";
|
|
4
|
-
import { join } from "node:path";
|
|
6
|
+
import { bytesToHex, concatBytes, hexToBytes, randomBytes, utf8ToBytes } from "@noble/hashes/utils.js";
|
|
7
|
+
import { appendFile, mkdir, readFile, rm } from "node:fs/promises";
|
|
8
|
+
import { join, resolve } from "node:path";
|
|
5
9
|
import { canonicalize } from "./canonicalJson.js";
|
|
10
|
+
import { writeFileAtomic } from "./fileCrypto.js";
|
|
11
|
+
function isV2Entry(entry) {
|
|
12
|
+
return entry.version === 2;
|
|
13
|
+
}
|
|
14
|
+
function stripChainFields(entry) {
|
|
15
|
+
const { prevMac: _prevMac, mac: _mac, ...rest } = entry;
|
|
16
|
+
return rest;
|
|
17
|
+
}
|
|
6
18
|
const GENESIS_HASH = "GENESIS";
|
|
19
|
+
const AUDIT_LOG_HMAC_INFO = utf8ToBytes("raz-encrypt-audit-log-v1|hmac-key");
|
|
20
|
+
// A separate key for the head record, so a head MAC can never be mistaken for an entry MAC (ADR 0019).
|
|
21
|
+
const AUDIT_LOG_HEAD_INFO = utf8ToBytes("raz-encrypt-audit-log-v1|head-hmac-key");
|
|
22
|
+
// A separate key from both MAC keys above, same reasoning (ADR 0027) — never derived from a passphrase, since appendLogEntry/readLog only ever receive an Identity.
|
|
23
|
+
const AUDIT_LOG_ENC_INFO = utf8ToBytes("raz-encrypt-audit-log-v1|enc-key");
|
|
7
24
|
function logFilePath(storageDir) {
|
|
8
25
|
return join(storageDir, "log.jsonl");
|
|
9
26
|
}
|
|
10
|
-
function
|
|
11
|
-
return
|
|
27
|
+
function headFilePath(storageDir) {
|
|
28
|
+
return join(storageDir, "log.head.json");
|
|
29
|
+
}
|
|
30
|
+
/** Migration journal (ADR 0031): the head record the encrypted log WILL have, MACed like a head, written before the log is rewritten. */
|
|
31
|
+
function journalFilePath(storageDir) {
|
|
32
|
+
return join(storageDir, "log.head.next.json");
|
|
33
|
+
}
|
|
34
|
+
/**
|
|
35
|
+
* HKDF-derived from BOTH the classical and PQC signing secrets (concatenated,
|
|
36
|
+
* same composition pattern as hybrid.ts's deriveHybridKey) — matching this
|
|
37
|
+
* library's hybrid posture everywhere else: compromising either secret key
|
|
38
|
+
* alone must not be enough to forge the chain. Recomputed fresh on every
|
|
39
|
+
* call, never persisted (ADR 0009).
|
|
40
|
+
*/
|
|
41
|
+
function deriveAuditLogKey(identity) {
|
|
42
|
+
return hkdf(sha384, concatBytes(identity.classical.secretKey, identity.pqcSign.secretKey), undefined, AUDIT_LOG_HMAC_INFO, 32);
|
|
43
|
+
}
|
|
44
|
+
/**
|
|
45
|
+
* Keyed with deriveAuditLogKey — plain SHA-384 (ADR 0009's rejected
|
|
46
|
+
* approach) lets anyone with filesystem write access to log.jsonl forge the
|
|
47
|
+
* whole chain with no knowledge of the Identity's secret material at all.
|
|
48
|
+
* HMAC means forging it requires that secret. Generic over the entry's own
|
|
49
|
+
* shape (v1 plaintext fields, or v2's `{version,nonce,ciphertext}` — issue
|
|
50
|
+
* #43) since `canonicalize` doesn't care which fields are present; the chain
|
|
51
|
+
* mechanism itself never changes, only the bytes it authenticates.
|
|
52
|
+
*/
|
|
53
|
+
function computeMac(identity, entry) {
|
|
54
|
+
return bytesToHex(hmac(sha384, deriveAuditLogKey(identity), utf8ToBytes(canonicalize(entry))));
|
|
55
|
+
}
|
|
56
|
+
/** Same derivation shape as deriveAuditLogKey, under its own info label (ADR 0027). */
|
|
57
|
+
function deriveAuditLogEncKey(identity) {
|
|
58
|
+
return hkdf(sha384, concatBytes(identity.classical.secretKey, identity.pqcSign.secretKey), undefined, AUDIT_LOG_ENC_INFO, 32);
|
|
59
|
+
}
|
|
60
|
+
/** Associated data for an entry's content AEAD: binds the ciphertext to its format version and its own nonce, same pattern as fileCrypto.ts's headerAad. */
|
|
61
|
+
function entryAad(nonce) {
|
|
62
|
+
return utf8ToBytes(canonicalize({ version: 2, nonce }));
|
|
63
|
+
}
|
|
64
|
+
function encryptEntryContent(identity, content) {
|
|
65
|
+
const nonceBytes = randomBytes(12);
|
|
66
|
+
const nonce = bytesToHex(nonceBytes);
|
|
67
|
+
const ciphertext = gcm(deriveAuditLogEncKey(identity), nonceBytes, entryAad(nonce)).encrypt(utf8ToBytes(canonicalize(content)));
|
|
68
|
+
return { nonce, ciphertext: bytesToHex(ciphertext) };
|
|
69
|
+
}
|
|
70
|
+
/** Throws (never returns something un-decrypted) if the entry doesn't authenticate under this identity's key. */
|
|
71
|
+
function decryptEntryContent(identity, entry) {
|
|
72
|
+
try {
|
|
73
|
+
const plaintext = gcm(deriveAuditLogEncKey(identity), hexToBytes(entry.nonce), entryAad(entry.nonce)).decrypt(hexToBytes(entry.ciphertext));
|
|
74
|
+
return JSON.parse(new TextDecoder().decode(plaintext));
|
|
75
|
+
}
|
|
76
|
+
catch (err) {
|
|
77
|
+
throw new Error(`Audit log entry failed to decrypt: ${err instanceof Error ? err.message : String(err)}`, { cause: err });
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
/**
|
|
81
|
+
* Constant-time MAC comparison (ADR 0009/0019's chain and head MACs are both
|
|
82
|
+
* HMAC-SHA384, so 96 lowercase hex chars). `candidate` is untrusted on-disk
|
|
83
|
+
* data of unknown shape: a non-string, wrong-length, or non-hex value is
|
|
84
|
+
* reported as a mismatch, never thrown, matching verifyKeyConfirmation's
|
|
85
|
+
* contract in crypto/hybrid.ts.
|
|
86
|
+
*
|
|
87
|
+
* Exported (ADR 0026) so the timing-safety test can exercise this exact
|
|
88
|
+
* compare in isolation, without the file I/O its only internal callers do.
|
|
89
|
+
*/
|
|
90
|
+
export function macEquals(candidate, expected) {
|
|
91
|
+
if (typeof candidate !== "string" || !/^[0-9a-f]{96}$/.test(candidate))
|
|
92
|
+
return false;
|
|
93
|
+
return equalBytes(hexToBytes(candidate), hexToBytes(expected));
|
|
12
94
|
}
|
|
13
95
|
async function readStoredEntries(storageDir) {
|
|
14
96
|
try {
|
|
@@ -16,7 +98,15 @@ async function readStoredEntries(storageDir) {
|
|
|
16
98
|
return raw
|
|
17
99
|
.split("\n")
|
|
18
100
|
.filter((line) => line.trim().length > 0)
|
|
19
|
-
.map((line) =>
|
|
101
|
+
.map((line) => {
|
|
102
|
+
const parsed = JSON.parse(line);
|
|
103
|
+
// A corrupted line can be valid JSON that isn't an object (e.g.
|
|
104
|
+
// `null`) — destructuring that downstream throws TypeError instead
|
|
105
|
+
// of being reported as a broken chain link. Coerce to an empty
|
|
106
|
+
// object so it flows safely through as a non-matching entry (found
|
|
107
|
+
// by fuzzing, issue #42).
|
|
108
|
+
return (typeof parsed === "object" && parsed !== null ? parsed : {});
|
|
109
|
+
});
|
|
20
110
|
}
|
|
21
111
|
catch (err) {
|
|
22
112
|
if (err.code === "ENOENT")
|
|
@@ -24,39 +114,222 @@ async function readStoredEntries(storageDir) {
|
|
|
24
114
|
throw err;
|
|
25
115
|
}
|
|
26
116
|
}
|
|
117
|
+
function computeHeadMac(identity, head) {
|
|
118
|
+
const key = hkdf(sha384, concatBytes(identity.classical.secretKey, identity.pqcSign.secretKey), undefined, AUDIT_LOG_HEAD_INFO, 32);
|
|
119
|
+
return bytesToHex(hmac(sha384, key, utf8ToBytes(canonicalize(head))));
|
|
120
|
+
}
|
|
121
|
+
async function readHead(identity, path = headFilePath(identity.storageDir)) {
|
|
122
|
+
let raw;
|
|
123
|
+
try {
|
|
124
|
+
raw = await readFile(path, "utf8");
|
|
125
|
+
}
|
|
126
|
+
catch (err) {
|
|
127
|
+
if (err.code === "ENOENT")
|
|
128
|
+
return { state: "missing" };
|
|
129
|
+
throw err;
|
|
130
|
+
}
|
|
131
|
+
try {
|
|
132
|
+
const parsed = JSON.parse(raw);
|
|
133
|
+
const { headMac, ...rest } = parsed;
|
|
134
|
+
if (rest.version !== 1 ||
|
|
135
|
+
typeof rest.count !== "number" ||
|
|
136
|
+
!Number.isSafeInteger(rest.count) ||
|
|
137
|
+
rest.count < 0 ||
|
|
138
|
+
typeof rest.mac !== "string" ||
|
|
139
|
+
typeof headMac !== "string" ||
|
|
140
|
+
(rest.encrypted !== undefined && rest.encrypted !== true)) {
|
|
141
|
+
return { state: "invalid" };
|
|
142
|
+
}
|
|
143
|
+
// Only ever include `encrypted` when true, exactly as writeHead does —
|
|
144
|
+
// otherwise a legacy head record's canonicalized bytes (and therefore
|
|
145
|
+
// its headMac) would silently change underneath it.
|
|
146
|
+
const head = rest.encrypted === true ? { version: 1, count: rest.count, mac: rest.mac, encrypted: true } : { version: 1, count: rest.count, mac: rest.mac };
|
|
147
|
+
return macEquals(headMac, computeHeadMac(identity, head)) ? { state: "ok", head } : { state: "invalid" };
|
|
148
|
+
}
|
|
149
|
+
catch {
|
|
150
|
+
return { state: "invalid" };
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
/** Compares an already-read head against the entries on disk. Returns undefined when they agree (a head up to a few entries behind is fine: that is a crash between append and head update). */
|
|
154
|
+
function checkHeadAgainst(read, entries) {
|
|
155
|
+
if (read.state === "missing")
|
|
156
|
+
return entries.length === 0 ? undefined : { reason: "head-missing", brokenAtIndex: entries.length };
|
|
157
|
+
if (read.state === "invalid")
|
|
158
|
+
return { reason: "head-invalid", brokenAtIndex: entries.length };
|
|
159
|
+
const { count, mac } = read.head;
|
|
160
|
+
if (count > entries.length)
|
|
161
|
+
return { reason: "truncated", brokenAtIndex: entries.length };
|
|
162
|
+
const expected = count === 0 ? GENESIS_HASH : entries[count - 1].mac;
|
|
163
|
+
if (mac !== expected)
|
|
164
|
+
return { reason: "head-invalid", brokenAtIndex: entries.length };
|
|
165
|
+
return undefined;
|
|
166
|
+
}
|
|
27
167
|
/**
|
|
28
|
-
*
|
|
29
|
-
*
|
|
30
|
-
*
|
|
31
|
-
*
|
|
168
|
+
* The head to judge `entries` by. Normally the head file. If a migration crashed after rewriting the
|
|
169
|
+
* log but before writing the head (ADR 0031), the head file is stale (or, for an empty log, absent); the journal holds the head the
|
|
170
|
+
* rewritten log should have. The journal is believed only if its MAC verifies AND it names exactly
|
|
171
|
+
* this log's tip, so it cannot launder a truncation or an edit. Read-only.
|
|
32
172
|
*/
|
|
33
|
-
|
|
173
|
+
async function effectiveHead(identity, entries) {
|
|
174
|
+
const head = await readHead(identity);
|
|
175
|
+
const headFine = checkHeadAgainst(head, entries) === undefined;
|
|
176
|
+
const headIsEncrypted = head.state === "ok" && head.head.encrypted === true;
|
|
177
|
+
if (headFine && headIsEncrypted)
|
|
178
|
+
return { head, fromJournal: false };
|
|
179
|
+
const journal = await readHead(identity, journalFilePath(identity.storageDir));
|
|
180
|
+
// Also taken when the head is "fine" but not marked encrypted: an empty log has no stale head to notice.
|
|
181
|
+
if (journal.state === "ok" && journal.head.encrypted === true && journal.head.count === entries.length && checkHeadAgainst(journal, entries) === undefined) {
|
|
182
|
+
return { head: journal, fromJournal: true };
|
|
183
|
+
}
|
|
184
|
+
return { head, fromJournal: false };
|
|
185
|
+
}
|
|
186
|
+
async function checkHead(identity, entries) {
|
|
187
|
+
return checkHeadAgainst((await effectiveHead(identity, entries)).head, entries);
|
|
188
|
+
}
|
|
189
|
+
/** Like effectiveHead, but also finishes an interrupted migration (writes the head, removes the journal) and removes a stale journal. Call only from the serialized append/migrate paths. */
|
|
190
|
+
async function recoverHead(identity, entries) {
|
|
191
|
+
const { head, fromJournal } = await effectiveHead(identity, entries);
|
|
192
|
+
if (fromJournal && head.state === "ok") {
|
|
193
|
+
await writeHead(identity, head.head.count, head.head.mac, true);
|
|
194
|
+
await rm(journalFilePath(identity.storageDir), { force: true });
|
|
195
|
+
}
|
|
196
|
+
else if (head.state === "ok" && checkHeadAgainst(head, entries) === undefined) {
|
|
197
|
+
await rm(journalFilePath(identity.storageDir), { force: true }); // stale journal from a crash before the log was rewritten
|
|
198
|
+
}
|
|
199
|
+
return head;
|
|
200
|
+
}
|
|
201
|
+
async function writeHead(identity, count, mac, encrypted, path = headFilePath(identity.storageDir)) {
|
|
202
|
+
const head = encrypted ? { version: 1, count, mac, encrypted: true } : { version: 1, count, mac };
|
|
203
|
+
await writeFileAtomic(path, JSON.stringify({ ...head, headMac: computeHeadMac(identity, head) }));
|
|
204
|
+
}
|
|
205
|
+
const appendState = new Map();
|
|
206
|
+
function stateFor(storageDir) {
|
|
207
|
+
const key = resolve(storageDir);
|
|
208
|
+
let state = appendState.get(key);
|
|
209
|
+
if (!state) {
|
|
210
|
+
state = { queue: Promise.resolve(), tail: null, headTrusted: true, encrypted: false };
|
|
211
|
+
appendState.set(key, state);
|
|
212
|
+
}
|
|
213
|
+
return state;
|
|
214
|
+
}
|
|
215
|
+
async function doAppend(identity, entry, state) {
|
|
34
216
|
const full = { ...entry, at: new Date().toISOString() };
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
217
|
+
if (!state.tail) {
|
|
218
|
+
const existing = await readStoredEntries(identity.storageDir);
|
|
219
|
+
const head = await recoverHead(identity, existing);
|
|
220
|
+
state.tail = { mac: existing.length > 0 ? existing[existing.length - 1].mac : GENESIS_HASH, entryCount: existing.length };
|
|
221
|
+
state.headTrusted = checkHeadAgainst(head, existing) === undefined;
|
|
222
|
+
// The mode also comes from the last stored entry, so deleting the head cannot make
|
|
223
|
+
// new entries land in plaintext inside an encrypted log (ADR 0031).
|
|
224
|
+
const lastIsEncrypted = existing.length > 0 && isV2Entry(existing[existing.length - 1]);
|
|
225
|
+
state.encrypted = (head.state === "ok" && head.head.encrypted === true) || lastIsEncrypted;
|
|
226
|
+
}
|
|
227
|
+
const content = state.encrypted ? { version: 2, ...encryptEntryContent(identity, full) } : { ...full };
|
|
228
|
+
const withPrev = { ...content, prevMac: state.tail.mac };
|
|
229
|
+
const mac = computeMac(identity, withPrev);
|
|
230
|
+
const stored = { ...withPrev, mac };
|
|
231
|
+
await mkdir(identity.storageDir, { recursive: true, mode: 0o700 });
|
|
40
232
|
await appendFile(logFilePath(identity.storageDir), JSON.stringify(stored) + "\n", { mode: 0o600 });
|
|
233
|
+
state.tail = { mac, entryCount: state.tail.entryCount + 1 };
|
|
234
|
+
// Never overwrite a head that already disagreed with the log: that would
|
|
235
|
+
// launder a truncation into a "clean" state.
|
|
236
|
+
if (state.headTrusted)
|
|
237
|
+
await writeHead(identity, state.tail.entryCount, mac, state.encrypted);
|
|
238
|
+
}
|
|
239
|
+
/**
|
|
240
|
+
* Appends one entry to the local, append-only audit log (public metadata
|
|
241
|
+
* only — no secrets). Each entry is chained to the previous one's MAC
|
|
242
|
+
* (see verifyLogIntegrity), keyed from identity's own secret material
|
|
243
|
+
* (ADR 0009) so tampering or deleting past entries is detectable by anyone
|
|
244
|
+
* EXCEPT someone who also holds that secret — not just theoretically
|
|
245
|
+
* append-only, and not forgeable by file-write access alone.
|
|
246
|
+
*/
|
|
247
|
+
export async function appendLogEntry(identity, entry) {
|
|
248
|
+
const state = stateFor(identity.storageDir);
|
|
249
|
+
// Chain onto the previous operation regardless of how it settled (a past
|
|
250
|
+
// failure must not permanently block future appends to this storageDir),
|
|
251
|
+
// but this call's own outcome — via `task`, awaited below — propagates to
|
|
252
|
+
// ITS caller untouched.
|
|
253
|
+
const task = state.queue.catch(() => { }).then(() => doAppend(identity, entry, state));
|
|
254
|
+
state.queue = task.catch(() => { });
|
|
255
|
+
await task;
|
|
41
256
|
}
|
|
257
|
+
/** Transparently decrypts any v2 (encrypted, issue #43) entries alongside plaintext v1 ones. */
|
|
42
258
|
export async function readLog(identity) {
|
|
43
|
-
return (await readStoredEntries(identity.storageDir)).map((
|
|
259
|
+
return (await readStoredEntries(identity.storageDir)).map((entry) => (isV2Entry(entry) ? decryptEntryContent(identity, entry) : stripChainFields(entry)));
|
|
44
260
|
}
|
|
45
261
|
/**
|
|
46
|
-
* Walks the
|
|
262
|
+
* Walks the MAC chain and reports whether it's intact. If an entry was
|
|
47
263
|
* edited or deleted, `brokenAtIndex` is the (0-based) index of the first
|
|
48
|
-
* entry whose chain link no longer matches.
|
|
264
|
+
* entry whose chain link no longer matches. Recomputing a matching MAC
|
|
265
|
+
* requires identity's own secret material (see computeMac/deriveAuditLogKey)
|
|
266
|
+
* — this is what makes forging the chain require more than filesystem
|
|
267
|
+
* write access (ADR 0009). It also checks the authenticated head record
|
|
268
|
+
* (ADR 0019), so deleting the most recent entries is reported too, with
|
|
269
|
+
* `reason: "truncated"`.
|
|
270
|
+
*
|
|
271
|
+
* Not detectable: replacing BOTH log.jsonl and log.head.json with an older,
|
|
272
|
+
* consistent pair, or deleting both files outright. Keep an off-box copy of
|
|
273
|
+
* the head if that matters to you.
|
|
49
274
|
*/
|
|
50
275
|
export async function verifyLogIntegrity(identity) {
|
|
51
276
|
const entries = await readStoredEntries(identity.storageDir);
|
|
52
|
-
let
|
|
277
|
+
let expectedPrevMac = GENESIS_HASH;
|
|
53
278
|
for (let i = 0; i < entries.length; i++) {
|
|
54
|
-
const {
|
|
55
|
-
if (
|
|
279
|
+
const { prevMac, mac, ...rest } = entries[i];
|
|
280
|
+
if (prevMac !== expectedPrevMac || !macEquals(mac, computeMac(identity, { ...rest, prevMac }))) {
|
|
56
281
|
return { intact: false, brokenAtIndex: i };
|
|
57
282
|
}
|
|
58
|
-
|
|
283
|
+
expectedPrevMac = mac;
|
|
59
284
|
}
|
|
285
|
+
const problem = await checkHead(identity, entries);
|
|
286
|
+
if (problem)
|
|
287
|
+
return { intact: false, ...problem };
|
|
60
288
|
return { intact: true };
|
|
61
289
|
}
|
|
290
|
+
/**
|
|
291
|
+
* One-time, explicit opt-in (issue #43/ADR 0027) to encrypt the audit log's
|
|
292
|
+
* entry content, so filesystem access alone no longer reveals what actions
|
|
293
|
+
* this identity has performed. Re-encrypts every existing entry in one pass
|
|
294
|
+
* (there is no mixed-format file afterwards) and marks the head record so
|
|
295
|
+
* every later `appendLogEntry` call encrypts just the one entry it writes —
|
|
296
|
+
* never the whole log again. A no-op if the log is already encrypted.
|
|
297
|
+
* Throws if the log fails its integrity check first: there is nothing safe
|
|
298
|
+
* to migrate if the chain can't be trusted.
|
|
299
|
+
*/
|
|
300
|
+
export async function migrateAuditLogToEncrypted(identity) {
|
|
301
|
+
const state = stateFor(identity.storageDir);
|
|
302
|
+
const task = state.queue.catch(() => { }).then(() => doMigrateToEncrypted(identity, state));
|
|
303
|
+
state.queue = task.catch(() => { });
|
|
304
|
+
await task;
|
|
305
|
+
}
|
|
306
|
+
async function doMigrateToEncrypted(identity, state) {
|
|
307
|
+
const head = await recoverHead(identity, await readStoredEntries(identity.storageDir)); // finish an interrupted earlier migration first
|
|
308
|
+
if (head.state === "ok" && head.head.encrypted === true)
|
|
309
|
+
return; // already encrypted — true no-op
|
|
310
|
+
const integrity = await verifyLogIntegrity(identity);
|
|
311
|
+
if (!integrity.intact) {
|
|
312
|
+
throw new Error("Cannot migrate: the audit log failed its integrity check. Investigate before encrypting it.");
|
|
313
|
+
}
|
|
314
|
+
const entries = await readStoredEntries(identity.storageDir);
|
|
315
|
+
let prevMac = GENESIS_HASH;
|
|
316
|
+
const rewritten = [];
|
|
317
|
+
for (const entry of entries) {
|
|
318
|
+
const content = isV2Entry(entry) ? decryptEntryContent(identity, entry) : stripChainFields(entry);
|
|
319
|
+
const { nonce, ciphertext } = encryptEntryContent(identity, content);
|
|
320
|
+
const withPrev = { version: 2, nonce, ciphertext, prevMac };
|
|
321
|
+
const mac = computeMac(identity, withPrev);
|
|
322
|
+
rewritten.push({ ...withPrev, mac });
|
|
323
|
+
prevMac = mac;
|
|
324
|
+
}
|
|
325
|
+
await mkdir(identity.storageDir, { recursive: true, mode: 0o700 });
|
|
326
|
+
// Crash-safe order (ADR 0031): journal the head the new log will have, then rewrite the log, then the head, then drop the journal.
|
|
327
|
+
await writeHead(identity, rewritten.length, prevMac, true, journalFilePath(identity.storageDir));
|
|
328
|
+
await writeFileAtomic(logFilePath(identity.storageDir), rewritten.map((entry) => JSON.stringify(entry)).join("\n") + (rewritten.length > 0 ? "\n" : ""));
|
|
329
|
+
await writeHead(identity, rewritten.length, prevMac, true);
|
|
330
|
+
await rm(journalFilePath(identity.storageDir), { force: true });
|
|
331
|
+
state.tail = { mac: prevMac, entryCount: rewritten.length };
|
|
332
|
+
state.headTrusted = true;
|
|
333
|
+
state.encrypted = true;
|
|
334
|
+
}
|
|
62
335
|
//# sourceMappingURL=store.js.map
|
package/dist/store.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"store.js","sourceRoot":"","sources":["../src/store.ts"],"names":[],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"store.js","sourceRoot":"","sources":["../src/store.ts"],"names":[],"mappings":"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"}
|
package/dist/trust.d.ts
CHANGED
|
@@ -1,16 +1,75 @@
|
|
|
1
|
-
import type
|
|
1
|
+
import { type Certificate, type VerifyResult } from "./crypto/hybrid.js";
|
|
2
|
+
import { type Identity, type PublicCard } from "./identity.js";
|
|
2
3
|
export interface TrustRecord {
|
|
3
4
|
card: PublicCard;
|
|
4
5
|
trustedAt: string;
|
|
5
6
|
/** Set when this party was later revoked (see untrustParty). Absent = still trusted. */
|
|
6
7
|
revokedAt?: string;
|
|
8
|
+
/**
|
|
9
|
+
* Set alongside revokedAt when this record was tombstoned by a verified
|
|
10
|
+
* rotation (applyRotation), not an ordinary revocation: the fingerprint of
|
|
11
|
+
* the card this party rotated TO. Informational only — verify() still
|
|
12
|
+
* reports "revoked-signer" for this record either way (ADR 0023/issue
|
|
13
|
+
* #37); nothing reads this field to change verification behavior.
|
|
14
|
+
*/
|
|
15
|
+
rotatedTo?: string;
|
|
7
16
|
}
|
|
17
|
+
type Entries = Record<string, TrustRecord>;
|
|
18
|
+
/**
|
|
19
|
+
* Thrown when trust.json cannot be trusted: its MAC does not verify (edited,
|
|
20
|
+
* truncated, copied from another identity), it is not valid JSON, or it is an
|
|
21
|
+
* unauthenticated legacy file that was never explicitly adopted with
|
|
22
|
+
* `migrateLegacyTrustStore`. Everything that consults the trust list fails
|
|
23
|
+
* closed on this rather than reading a file anyone with filesystem access
|
|
24
|
+
* could have written.
|
|
25
|
+
*/
|
|
26
|
+
export declare class TrustStoreIntegrityError extends Error {
|
|
27
|
+
constructor(message: string);
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* Exported (ADR 0026) so the timing-safety test can exercise this exact
|
|
31
|
+
* compare in isolation, without the file I/O its only internal callers do.
|
|
32
|
+
*/
|
|
33
|
+
export declare function macMatches(identity: Identity, entries: Entries, mac: unknown, seq?: number): boolean;
|
|
34
|
+
/** What a caller can keep somewhere an attacker on this machine cannot write (a second device, a remote service) to catch a rollback of every local file at once. Not secret. */
|
|
35
|
+
export interface TrustStoreAnchor {
|
|
36
|
+
seq: number;
|
|
37
|
+
mac: string;
|
|
38
|
+
}
|
|
39
|
+
/** The current `{ seq, mac }` of the trust store. Store it off-box after changes you care about. */
|
|
40
|
+
export declare function getTrustStoreAnchor(identity: Identity): Promise<TrustStoreAnchor>;
|
|
41
|
+
/**
|
|
42
|
+
* Throws TrustStoreIntegrityError if the local trust store is older than `anchor`, or has the
|
|
43
|
+
* same sequence number with different contents. Newer is fine. This is the only check that
|
|
44
|
+
* catches a rollback where trust.json and trust.seq.json were restored together (ADR 0030).
|
|
45
|
+
*/
|
|
46
|
+
export declare function verifyTrustStoreAnchor(identity: Identity, anchor: TrustStoreAnchor): Promise<void>;
|
|
47
|
+
/**
|
|
48
|
+
* One-time, explicit opt-in (issue #43/ADR 0027) to encrypt `trust.json`'s
|
|
49
|
+
* entries, so filesystem access alone no longer reveals who this identity
|
|
50
|
+
* trusts. A no-op if the store is already v4. A legacy (unauthenticated) file
|
|
51
|
+
* is not implicitly adopted here — call `migrateLegacyTrustStore` first, same
|
|
52
|
+
* as reading it would require.
|
|
53
|
+
*/
|
|
54
|
+
export declare function migrateTrustStoreToEncrypted(identity: Identity): Promise<void>;
|
|
55
|
+
/**
|
|
56
|
+
* One-time, explicit adoption of a trust.json written before trust-store
|
|
57
|
+
* MACs existed (a v1 alias map or a v2 fingerprint-keyed file). Those files
|
|
58
|
+
* carry no proof of who wrote them, so they are never read implicitly (an
|
|
59
|
+
* attacker could otherwise plant one and bypass the MAC). Calling this says
|
|
60
|
+
* "I have reviewed the current file and it is mine": its records are kept
|
|
61
|
+
* and the file is rewritten authenticated. Returns the number of records
|
|
62
|
+
* adopted; returns 0 and changes nothing if the file is missing or already
|
|
63
|
+
* authenticated (a modified authenticated file still throws).
|
|
64
|
+
*/
|
|
65
|
+
export declare function migrateLegacyTrustStore(identity: Identity): Promise<number>;
|
|
8
66
|
/**
|
|
9
67
|
* Records another party's public card as trusted, keyed by the card's
|
|
10
68
|
* fingerprint (not alias — two different real parties could otherwise pick
|
|
11
69
|
* the same alias and silently overwrite each other). Public keys are not
|
|
12
70
|
* secret, so this file is plain JSON (unlike identity.enc.json), though it's
|
|
13
|
-
*
|
|
71
|
+
* permission-restricted to the owning user and, since ADR 0015, carries an
|
|
72
|
+
* HMAC (keyed from the Identity's secret keys) so edits are detected.
|
|
14
73
|
*
|
|
15
74
|
* Trusting a card again after it was revoked reinstates it (clears revokedAt).
|
|
16
75
|
*/
|
|
@@ -23,6 +82,14 @@ export declare function trustParty(identity: Identity, card: PublicCard): Promis
|
|
|
23
82
|
* "untrusted-signer" (never trusted at all).
|
|
24
83
|
*/
|
|
25
84
|
export declare function untrustParty(identity: Identity, alias: string): Promise<void>;
|
|
85
|
+
/**
|
|
86
|
+
* Revokes exactly one card, identified by its `fingerprint(card)` string
|
|
87
|
+
* (spaces and case are ignored). Unlike `untrustParty`, parties that merely
|
|
88
|
+
* share an alias are untouched — alias is a non-unique display label.
|
|
89
|
+
* Returns true if a currently-trusted record was revoked, false if no such
|
|
90
|
+
* trusted record exists (a typo'd fingerprint is reported, not ignored).
|
|
91
|
+
*/
|
|
92
|
+
export declare function untrustFingerprint(identity: Identity, fingerprintValue: string): Promise<boolean>;
|
|
26
93
|
/** Every currently-trusted (non-revoked) card. */
|
|
27
94
|
export declare function listTrustedParties(identity: Identity): Promise<PublicCard[]>;
|
|
28
95
|
/** Every trust record ever made, revoked or not — for audit/history purposes. */
|
|
@@ -39,3 +106,104 @@ export declare function getTrustedParty(identity: Identity, alias: string): Prom
|
|
|
39
106
|
export declare function getTrustedPartiesByAlias(identity: Identity, alias: string): Promise<PublicCard[]>;
|
|
40
107
|
/** All trust records (revoked or not) recorded under this alias. */
|
|
41
108
|
export declare function getTrustRecordsByAlias(identity: Identity, alias: string): Promise<TrustRecord[]>;
|
|
109
|
+
/**
|
|
110
|
+
* Verifies a self-signed card (from `exportSignedCard`) — checks the
|
|
111
|
+
* signature is self-consistent (the certificate's signer IS the card it's
|
|
112
|
+
* attached to) and, if the card carries an `expiresAt`, that it hasn't
|
|
113
|
+
* passed. Only on success does this call the ordinary `trustParty` to
|
|
114
|
+
* actually record it — self-signing proves the card is authentic and
|
|
115
|
+
* current, not that you've decided to trust it (Fingerprint confirmation
|
|
116
|
+
* is still the human-level trust decision, same as a bare card).
|
|
117
|
+
*/
|
|
118
|
+
export declare function trustSignedCard(identity: Identity, card: PublicCard, certificate: Certificate): Promise<VerifyResult>;
|
|
119
|
+
export interface RotationDocument {
|
|
120
|
+
oldCard: PublicCard;
|
|
121
|
+
newCard: PublicCard;
|
|
122
|
+
rotatedAt: string;
|
|
123
|
+
}
|
|
124
|
+
export interface RotationStatement {
|
|
125
|
+
document: RotationDocument;
|
|
126
|
+
oldCertificate: Certificate;
|
|
127
|
+
newCertificate: Certificate;
|
|
128
|
+
}
|
|
129
|
+
/**
|
|
130
|
+
* Starts a key rotation: the OLD identity signs a statement naming `newCard`
|
|
131
|
+
* as its successor. Hand the result to `completeRotation`, called with the
|
|
132
|
+
* NEW identity, to get a complete, dual-signed RotationStatement — a single
|
|
133
|
+
* signature from the old key alone is never enough to redirect a peer's
|
|
134
|
+
* trust (see `applyRotation`), so this function's output is not yet usable
|
|
135
|
+
* on its own. Even a dual-signed statement is only as trustworthy as the new
|
|
136
|
+
* fingerprint the receiver confirms out of band (`applyRotation`).
|
|
137
|
+
*/
|
|
138
|
+
export declare function beginRotation(oldIdentity: Identity, newCard: PublicCard, options?: {
|
|
139
|
+
rotatedAt?: string;
|
|
140
|
+
}): Promise<{
|
|
141
|
+
document: RotationDocument;
|
|
142
|
+
oldCertificate: Certificate;
|
|
143
|
+
}>;
|
|
144
|
+
/**
|
|
145
|
+
* Finishes a key rotation: the NEW identity signs the SAME document
|
|
146
|
+
* `beginRotation` produced, proving the new key exists and agrees to be
|
|
147
|
+
* named as successor. This proves the statement's author controls the new
|
|
148
|
+
* key; it does NOT prove the new key belongs to the real owner — an attacker
|
|
149
|
+
* who stole the old key can mint their own "new" identity and complete the
|
|
150
|
+
* statement themselves. Receivers close that gap with `applyRotation`'s
|
|
151
|
+
* out-of-band `confirmNewFingerprint`.
|
|
152
|
+
*/
|
|
153
|
+
export declare function completeRotation(newIdentity: Identity, draft: {
|
|
154
|
+
document: RotationDocument;
|
|
155
|
+
oldCertificate: Certificate;
|
|
156
|
+
}): Promise<RotationStatement>;
|
|
157
|
+
/**
|
|
158
|
+
* Verifies a RotationStatement (both signatures self-consistent, both
|
|
159
|
+
* certificates over the identical document) and, only if `oldCard` already
|
|
160
|
+
* has an existing Trust Record, moves trust to `newCard` and tombstones
|
|
161
|
+
* `oldCard`'s record (revokedAt set, plus rotatedTo pointing at newCard's
|
|
162
|
+
* fingerprint). A statement for a card you never trusted is a no-op — this
|
|
163
|
+
* is not a way to seed your trust store with a card you never decided to
|
|
164
|
+
* trust in the first place.
|
|
165
|
+
*
|
|
166
|
+
* `options.confirmNewFingerprint` is REQUIRED: the new card's fingerprint as
|
|
167
|
+
* the real owner gave it to you over a channel the statement did not travel
|
|
168
|
+
* on (phone call, in person). Dual signatures alone only prove the author
|
|
169
|
+
* holds both keys; an attacker with a stolen old key supplies their own
|
|
170
|
+
* "new" key and signs both. Spacing and case are ignored when comparing.
|
|
171
|
+
* A mismatch returns `{ valid: false, reason: "fingerprint-mismatch" }` and
|
|
172
|
+
* changes nothing; a missing value throws (ADR 0029).
|
|
173
|
+
*/
|
|
174
|
+
export declare function applyRotation(identity: Identity, statement: RotationStatement, options: {
|
|
175
|
+
confirmNewFingerprint: string;
|
|
176
|
+
}): Promise<VerifyResult>;
|
|
177
|
+
export interface RevocationDocument {
|
|
178
|
+
revokedCard: PublicCard;
|
|
179
|
+
revokedAt: string;
|
|
180
|
+
reason?: string;
|
|
181
|
+
}
|
|
182
|
+
/** A Certificate only stores its document's HASH, not the document itself (same as everywhere else in this codebase) — so a revocation notice bundles the document alongside its certificate, the same shape RotationStatement already uses for the same reason. */
|
|
183
|
+
export interface RevocationNotice {
|
|
184
|
+
document: RevocationDocument;
|
|
185
|
+
certificate: Certificate;
|
|
186
|
+
}
|
|
187
|
+
/**
|
|
188
|
+
* Produces a self-signed, portable revocation notice for `identity`'s own
|
|
189
|
+
* card — verifiable by any peer independently of their (or your) local
|
|
190
|
+
* trust-store state. The same function works whether called reactively
|
|
191
|
+
* (retiring a key now) or right after createIdentity, to be stored
|
|
192
|
+
* separately and used later if this identity becomes inaccessible — that's
|
|
193
|
+
* what actually makes "recovery" possible for a lost passphrase or a lost
|
|
194
|
+
* identity file (ADR 0023): there is no special "pre-generated" mode, it's
|
|
195
|
+
* the same notice either way.
|
|
196
|
+
*/
|
|
197
|
+
export declare function createRevocationNotice(identity: Identity, options?: {
|
|
198
|
+
reason?: string;
|
|
199
|
+
}): Promise<RevocationNotice>;
|
|
200
|
+
/**
|
|
201
|
+
* Verifies a revocation notice (self-consistent, well-formed, `revokedAt`
|
|
202
|
+
* a valid ISO instant) and, only if the named card already has an existing
|
|
203
|
+
* Trust Record, tombstones it (revokedAt set) — exactly like
|
|
204
|
+
* `untrustFingerprint`. A notice for a card you never trusted is a no-op,
|
|
205
|
+
* for the same reason `applyRotation` is: this is not a pre-emptive
|
|
206
|
+
* denylist feature.
|
|
207
|
+
*/
|
|
208
|
+
export declare function applyRevocationNotice(identity: Identity, notice: RevocationNotice): Promise<VerifyResult>;
|
|
209
|
+
export {};
|