raz-encrypt 0.3.0 → 2.1.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 +442 -212
- 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 +6 -2
- package/dist/core.js +4 -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 +186 -25
- package/dist/crypto/hybrid.js +512 -54
- 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.d.ts +68 -0
- package/dist/crypto/ratchet.js +94 -0
- package/dist/crypto/ratchet.js.map +1 -0
- package/dist/fileCrypto.d.ts +44 -2
- package/dist/fileCrypto.js +197 -28
- package/dist/fileCrypto.js.map +1 -1
- package/dist/identity.d.ts +69 -4
- package/dist/identity.js +111 -12
- 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 +42 -5
- package/dist/store.js +254 -22
- package/dist/store.js.map +1 -1
- package/dist/trust.d.ts +143 -2
- package/dist/trust.js +376 -27
- package/dist/trust.js.map +1 -1
- package/package.json +62 -48
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Passphrase handling shared by every place a passphrase becomes key
|
|
3
|
+
* material (ADR 0017).
|
|
4
|
+
*
|
|
5
|
+
* Normalization: the same visible passphrase can be encoded as different
|
|
6
|
+
* Unicode code point sequences (composed vs decomposed accents, full-width
|
|
7
|
+
* digits, ...) depending on the keyboard, OS or app that typed it, which
|
|
8
|
+
* would derive different keys. Passphrases are normalized to NFKC before
|
|
9
|
+
* the KDF so what the user sees is what unlocks. For pure-ASCII passphrases
|
|
10
|
+
* NFKC is the identity, so nothing changes for them.
|
|
11
|
+
*/
|
|
12
|
+
export function normalizePassphrase(passphrase) {
|
|
13
|
+
return passphrase.normalize("NFKC");
|
|
14
|
+
}
|
|
15
|
+
export const MIN_PASSPHRASE_LENGTH = 12;
|
|
16
|
+
/**
|
|
17
|
+
* A deliberately small blocklist of the passphrases that fit a 12-character
|
|
18
|
+
* minimum and appear at the top of every breach list. NIST SP 800-63B asks
|
|
19
|
+
* verifiers to compare against known-compromised values; a few dozen entries
|
|
20
|
+
* cannot do that job, so treat this as a floor, not a strength meter. Apps
|
|
21
|
+
* that care should run a real estimator (e.g. zxcvbn) before calling
|
|
22
|
+
* createIdentity.
|
|
23
|
+
*/
|
|
24
|
+
const COMMON_PASSPHRASES = new Set([
|
|
25
|
+
"passwordpassword",
|
|
26
|
+
"password1234",
|
|
27
|
+
"password12345",
|
|
28
|
+
"password123456",
|
|
29
|
+
"passw0rd1234",
|
|
30
|
+
"123456789012",
|
|
31
|
+
"1234567890123",
|
|
32
|
+
"12345678901234",
|
|
33
|
+
"123456123456",
|
|
34
|
+
"1234512345",
|
|
35
|
+
"111111111111",
|
|
36
|
+
"000000000000",
|
|
37
|
+
"qwertyuiopas",
|
|
38
|
+
"qwertyuiop12",
|
|
39
|
+
"qwertyuiop123",
|
|
40
|
+
"qwertyqwerty",
|
|
41
|
+
"asdfghjklqwe",
|
|
42
|
+
"zxcvbnmasdfg",
|
|
43
|
+
"abcdefghijkl",
|
|
44
|
+
"abcdefghijklm",
|
|
45
|
+
"abcabcabcabc",
|
|
46
|
+
"iloveyouiloveyou",
|
|
47
|
+
"letmeinletmein",
|
|
48
|
+
"welcome12345",
|
|
49
|
+
"welcome123456",
|
|
50
|
+
"administrator",
|
|
51
|
+
"changemechangeme",
|
|
52
|
+
"trustno1trustno1",
|
|
53
|
+
"monkeymonkey",
|
|
54
|
+
"footballfootball",
|
|
55
|
+
]);
|
|
56
|
+
/** Throws if `passphrase` (already normalized) is unacceptable for a NEW identity. Loading an existing identity never calls this. */
|
|
57
|
+
export function assertAcceptablePassphrase(normalized) {
|
|
58
|
+
const codePoints = [...normalized];
|
|
59
|
+
if (codePoints.length < MIN_PASSPHRASE_LENGTH) {
|
|
60
|
+
throw new Error(`Passphrase must be at least ${MIN_PASSPHRASE_LENGTH} characters long.`);
|
|
61
|
+
}
|
|
62
|
+
if (new Set(codePoints).size === 1) {
|
|
63
|
+
throw new Error("Passphrase must not be a single repeated character.");
|
|
64
|
+
}
|
|
65
|
+
if (COMMON_PASSPHRASES.has(normalized.toLowerCase().replace(/\s+/g, ""))) {
|
|
66
|
+
throw new Error("Passphrase is on the list of very common passphrases; choose something less guessable.");
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
//# sourceMappingURL=passphrase.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"passphrase.js","sourceRoot":"","sources":["../src/passphrase.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AACH,MAAM,UAAU,mBAAmB,CAAC,UAAkB;IACpD,OAAO,UAAU,CAAC,SAAS,CAAC,MAAM,CAAC,CAAC;AACtC,CAAC;AAED,MAAM,CAAC,MAAM,qBAAqB,GAAG,EAAE,CAAC;AAExC;;;;;;;GAOG;AACH,MAAM,kBAAkB,GAAG,IAAI,GAAG,CAAC;IACjC,kBAAkB;IAClB,cAAc;IACd,eAAe;IACf,gBAAgB;IAChB,cAAc;IACd,cAAc;IACd,eAAe;IACf,gBAAgB;IAChB,cAAc;IACd,YAAY;IACZ,cAAc;IACd,cAAc;IACd,cAAc;IACd,cAAc;IACd,eAAe;IACf,cAAc;IACd,cAAc;IACd,cAAc;IACd,cAAc;IACd,eAAe;IACf,cAAc;IACd,kBAAkB;IAClB,gBAAgB;IAChB,cAAc;IACd,eAAe;IACf,eAAe;IACf,kBAAkB;IAClB,kBAAkB;IAClB,cAAc;IACd,kBAAkB;CACnB,CAAC,CAAC;AAeH,qIAAqI;AACrI,MAAM,UAAU,0BAA0B,CAAC,UAAkB;IAC3D,MAAM,UAAU,GAAG,CAAC,GAAG,UAAU,CAAC,CAAC;IACnC,IAAI,UAAU,CAAC,MAAM,GAAG,qBAAqB,EAAE,CAAC;QAC9C,MAAM,IAAI,KAAK,CAAC,+BAA+B,qBAAqB,mBAAmB,CAAC,CAAC;IAC3F,CAAC;IACD,IAAI,IAAI,GAAG,CAAC,UAAU,CAAC,CAAC,IAAI,KAAK,CAAC,EAAE,CAAC;QACnC,MAAM,IAAI,KAAK,CAAC,qDAAqD,CAAC,CAAC;IACzE,CAAC;IACD,IAAI,kBAAkB,CAAC,GAAG,CAAC,UAAU,CAAC,WAAW,EAAE,CAAC,OAAO,CAAC,MAAM,EAAE,EAAE,CAAC,CAAC,EAAE,CAAC;QACzE,MAAM,IAAI,KAAK,CAAC,wFAAwF,CAAC,CAAC;IAC5G,CAAC;AACH,CAAC"}
|
package/dist/store.d.ts
CHANGED
|
@@ -6,23 +6,60 @@ export interface LogEntry {
|
|
|
6
6
|
result?: string;
|
|
7
7
|
at: string;
|
|
8
8
|
}
|
|
9
|
+
/**
|
|
10
|
+
* Constant-time MAC comparison (ADR 0009/0019's chain and head MACs are both
|
|
11
|
+
* HMAC-SHA384, so 96 lowercase hex chars). `candidate` is untrusted on-disk
|
|
12
|
+
* data of unknown shape: a non-string, wrong-length, or non-hex value is
|
|
13
|
+
* reported as a mismatch, never thrown, matching verifyKeyConfirmation's
|
|
14
|
+
* contract in crypto/hybrid.ts.
|
|
15
|
+
*
|
|
16
|
+
* Exported (ADR 0026) so the timing-safety test can exercise this exact
|
|
17
|
+
* compare in isolation, without the file I/O its only internal callers do.
|
|
18
|
+
*/
|
|
19
|
+
export declare function macEquals(candidate: unknown, expected: string): boolean;
|
|
9
20
|
/**
|
|
10
21
|
* Appends one entry to the local, append-only audit log (public metadata
|
|
11
|
-
* only — no secrets). Each entry is chained to the previous one's
|
|
12
|
-
* (see verifyLogIntegrity)
|
|
13
|
-
*
|
|
22
|
+
* only — no secrets). Each entry is chained to the previous one's MAC
|
|
23
|
+
* (see verifyLogIntegrity), keyed from identity's own secret material
|
|
24
|
+
* (ADR 0009) so tampering or deleting past entries is detectable by anyone
|
|
25
|
+
* EXCEPT someone who also holds that secret — not just theoretically
|
|
26
|
+
* append-only, and not forgeable by file-write access alone.
|
|
14
27
|
*/
|
|
15
28
|
export declare function appendLogEntry(identity: Identity, entry: Omit<LogEntry, "at">): Promise<void>;
|
|
29
|
+
/** Transparently decrypts any v2 (encrypted, issue #43) entries alongside plaintext v1 ones. */
|
|
16
30
|
export declare function readLog(identity: Identity): Promise<LogEntry[]>;
|
|
17
31
|
export type LogIntegrityResult = {
|
|
18
32
|
intact: true;
|
|
19
33
|
} | {
|
|
20
34
|
intact: false;
|
|
35
|
+
/** First entry whose chain link failed; for the reasons below, the number of entries actually present. */
|
|
21
36
|
brokenAtIndex: number;
|
|
37
|
+
/** Absent for an edited or deleted entry inside the chain. "truncated": entries at the end were removed. "head-missing" / "head-invalid": the head record that would show this is gone or not authentic. */
|
|
38
|
+
reason?: "truncated" | "head-missing" | "head-invalid";
|
|
22
39
|
};
|
|
23
40
|
/**
|
|
24
|
-
* Walks the
|
|
41
|
+
* Walks the MAC chain and reports whether it's intact. If an entry was
|
|
25
42
|
* edited or deleted, `brokenAtIndex` is the (0-based) index of the first
|
|
26
|
-
* entry whose chain link no longer matches.
|
|
43
|
+
* entry whose chain link no longer matches. Recomputing a matching MAC
|
|
44
|
+
* requires identity's own secret material (see computeMac/deriveAuditLogKey)
|
|
45
|
+
* — this is what makes forging the chain require more than filesystem
|
|
46
|
+
* write access (ADR 0009). It also checks the authenticated head record
|
|
47
|
+
* (ADR 0019), so deleting the most recent entries is reported too, with
|
|
48
|
+
* `reason: "truncated"`.
|
|
49
|
+
*
|
|
50
|
+
* Not detectable: replacing BOTH log.jsonl and log.head.json with an older,
|
|
51
|
+
* consistent pair, or deleting both files outright. Keep an off-box copy of
|
|
52
|
+
* the head if that matters to you.
|
|
27
53
|
*/
|
|
28
54
|
export declare function verifyLogIntegrity(identity: Identity): Promise<LogIntegrityResult>;
|
|
55
|
+
/**
|
|
56
|
+
* One-time, explicit opt-in (issue #43/ADR 0027) to encrypt the audit log's
|
|
57
|
+
* entry content, so filesystem access alone no longer reveals what actions
|
|
58
|
+
* this identity has performed. Re-encrypts every existing entry in one pass
|
|
59
|
+
* (there is no mixed-format file afterwards) and marks the head record so
|
|
60
|
+
* every later `appendLogEntry` call encrypts just the one entry it writes —
|
|
61
|
+
* never the whole log again. A no-op if the log is already encrypted.
|
|
62
|
+
* Throws if the log fails its integrity check first: there is nothing safe
|
|
63
|
+
* to migrate if the chain can't be trusted.
|
|
64
|
+
*/
|
|
65
|
+
export declare function migrateAuditLogToEncrypted(identity: Identity): Promise<void>;
|
package/dist/store.js
CHANGED
|
@@ -1,14 +1,92 @@
|
|
|
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";
|
|
6
|
+
import { bytesToHex, concatBytes, hexToBytes, randomBytes, utf8ToBytes } from "@noble/hashes/utils.js";
|
|
3
7
|
import { appendFile, mkdir, readFile } from "node:fs/promises";
|
|
4
|
-
import { join } from "node:path";
|
|
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
|
+
/**
|
|
31
|
+
* HKDF-derived from BOTH the classical and PQC signing secrets (concatenated,
|
|
32
|
+
* same composition pattern as hybrid.ts's deriveHybridKey) — matching this
|
|
33
|
+
* library's hybrid posture everywhere else: compromising either secret key
|
|
34
|
+
* alone must not be enough to forge the chain. Recomputed fresh on every
|
|
35
|
+
* call, never persisted (ADR 0009).
|
|
36
|
+
*/
|
|
37
|
+
function deriveAuditLogKey(identity) {
|
|
38
|
+
return hkdf(sha384, concatBytes(identity.classical.secretKey, identity.pqcSign.secretKey), undefined, AUDIT_LOG_HMAC_INFO, 32);
|
|
39
|
+
}
|
|
40
|
+
/**
|
|
41
|
+
* Keyed with deriveAuditLogKey — plain SHA-384 (ADR 0009's rejected
|
|
42
|
+
* approach) lets anyone with filesystem write access to log.jsonl forge the
|
|
43
|
+
* whole chain with no knowledge of the Identity's secret material at all.
|
|
44
|
+
* HMAC means forging it requires that secret. Generic over the entry's own
|
|
45
|
+
* shape (v1 plaintext fields, or v2's `{version,nonce,ciphertext}` — issue
|
|
46
|
+
* #43) since `canonicalize` doesn't care which fields are present; the chain
|
|
47
|
+
* mechanism itself never changes, only the bytes it authenticates.
|
|
48
|
+
*/
|
|
49
|
+
function computeMac(identity, entry) {
|
|
50
|
+
return bytesToHex(hmac(sha384, deriveAuditLogKey(identity), utf8ToBytes(canonicalize(entry))));
|
|
51
|
+
}
|
|
52
|
+
/** Same derivation shape as deriveAuditLogKey, under its own info label (ADR 0027). */
|
|
53
|
+
function deriveAuditLogEncKey(identity) {
|
|
54
|
+
return hkdf(sha384, concatBytes(identity.classical.secretKey, identity.pqcSign.secretKey), undefined, AUDIT_LOG_ENC_INFO, 32);
|
|
55
|
+
}
|
|
56
|
+
/** 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. */
|
|
57
|
+
function entryAad(nonce) {
|
|
58
|
+
return utf8ToBytes(canonicalize({ version: 2, nonce }));
|
|
59
|
+
}
|
|
60
|
+
function encryptEntryContent(identity, content) {
|
|
61
|
+
const nonceBytes = randomBytes(12);
|
|
62
|
+
const nonce = bytesToHex(nonceBytes);
|
|
63
|
+
const ciphertext = gcm(deriveAuditLogEncKey(identity), nonceBytes, entryAad(nonce)).encrypt(utf8ToBytes(canonicalize(content)));
|
|
64
|
+
return { nonce, ciphertext: bytesToHex(ciphertext) };
|
|
65
|
+
}
|
|
66
|
+
/** Throws (never returns something un-decrypted) if the entry doesn't authenticate under this identity's key. */
|
|
67
|
+
function decryptEntryContent(identity, entry) {
|
|
68
|
+
try {
|
|
69
|
+
const plaintext = gcm(deriveAuditLogEncKey(identity), hexToBytes(entry.nonce), entryAad(entry.nonce)).decrypt(hexToBytes(entry.ciphertext));
|
|
70
|
+
return JSON.parse(new TextDecoder().decode(plaintext));
|
|
71
|
+
}
|
|
72
|
+
catch (err) {
|
|
73
|
+
throw new Error(`Audit log entry failed to decrypt: ${err instanceof Error ? err.message : String(err)}`);
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
/**
|
|
77
|
+
* Constant-time MAC comparison (ADR 0009/0019's chain and head MACs are both
|
|
78
|
+
* HMAC-SHA384, so 96 lowercase hex chars). `candidate` is untrusted on-disk
|
|
79
|
+
* data of unknown shape: a non-string, wrong-length, or non-hex value is
|
|
80
|
+
* reported as a mismatch, never thrown, matching verifyKeyConfirmation's
|
|
81
|
+
* contract in crypto/hybrid.ts.
|
|
82
|
+
*
|
|
83
|
+
* Exported (ADR 0026) so the timing-safety test can exercise this exact
|
|
84
|
+
* compare in isolation, without the file I/O its only internal callers do.
|
|
85
|
+
*/
|
|
86
|
+
export function macEquals(candidate, expected) {
|
|
87
|
+
if (typeof candidate !== "string" || !/^[0-9a-f]{96}$/.test(candidate))
|
|
88
|
+
return false;
|
|
89
|
+
return equalBytes(hexToBytes(candidate), hexToBytes(expected));
|
|
12
90
|
}
|
|
13
91
|
async function readStoredEntries(storageDir) {
|
|
14
92
|
try {
|
|
@@ -16,7 +94,15 @@ async function readStoredEntries(storageDir) {
|
|
|
16
94
|
return raw
|
|
17
95
|
.split("\n")
|
|
18
96
|
.filter((line) => line.trim().length > 0)
|
|
19
|
-
.map((line) =>
|
|
97
|
+
.map((line) => {
|
|
98
|
+
const parsed = JSON.parse(line);
|
|
99
|
+
// A corrupted line can be valid JSON that isn't an object (e.g.
|
|
100
|
+
// `null`) — destructuring that downstream throws TypeError instead
|
|
101
|
+
// of being reported as a broken chain link. Coerce to an empty
|
|
102
|
+
// object so it flows safely through as a non-matching entry (found
|
|
103
|
+
// by fuzzing, issue #42).
|
|
104
|
+
return (typeof parsed === "object" && parsed !== null ? parsed : {});
|
|
105
|
+
});
|
|
20
106
|
}
|
|
21
107
|
catch (err) {
|
|
22
108
|
if (err.code === "ENOENT")
|
|
@@ -24,39 +110,185 @@ async function readStoredEntries(storageDir) {
|
|
|
24
110
|
throw err;
|
|
25
111
|
}
|
|
26
112
|
}
|
|
113
|
+
function computeHeadMac(identity, head) {
|
|
114
|
+
const key = hkdf(sha384, concatBytes(identity.classical.secretKey, identity.pqcSign.secretKey), undefined, AUDIT_LOG_HEAD_INFO, 32);
|
|
115
|
+
return bytesToHex(hmac(sha384, key, utf8ToBytes(canonicalize(head))));
|
|
116
|
+
}
|
|
117
|
+
async function readHead(identity) {
|
|
118
|
+
let raw;
|
|
119
|
+
try {
|
|
120
|
+
raw = await readFile(headFilePath(identity.storageDir), "utf8");
|
|
121
|
+
}
|
|
122
|
+
catch (err) {
|
|
123
|
+
if (err.code === "ENOENT")
|
|
124
|
+
return { state: "missing" };
|
|
125
|
+
throw err;
|
|
126
|
+
}
|
|
127
|
+
try {
|
|
128
|
+
const parsed = JSON.parse(raw);
|
|
129
|
+
const { headMac, ...rest } = parsed;
|
|
130
|
+
if (rest.version !== 1 ||
|
|
131
|
+
typeof rest.count !== "number" ||
|
|
132
|
+
!Number.isSafeInteger(rest.count) ||
|
|
133
|
+
rest.count < 0 ||
|
|
134
|
+
typeof rest.mac !== "string" ||
|
|
135
|
+
typeof headMac !== "string" ||
|
|
136
|
+
(rest.encrypted !== undefined && rest.encrypted !== true)) {
|
|
137
|
+
return { state: "invalid" };
|
|
138
|
+
}
|
|
139
|
+
// Only ever include `encrypted` when true, exactly as writeHead does —
|
|
140
|
+
// otherwise a legacy head record's canonicalized bytes (and therefore
|
|
141
|
+
// its headMac) would silently change underneath it.
|
|
142
|
+
const head = rest.encrypted === true ? { version: 1, count: rest.count, mac: rest.mac, encrypted: true } : { version: 1, count: rest.count, mac: rest.mac };
|
|
143
|
+
return macEquals(headMac, computeHeadMac(identity, head)) ? { state: "ok", head } : { state: "invalid" };
|
|
144
|
+
}
|
|
145
|
+
catch {
|
|
146
|
+
return { state: "invalid" };
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
/** 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). */
|
|
150
|
+
function checkHeadAgainst(read, entries) {
|
|
151
|
+
if (read.state === "missing")
|
|
152
|
+
return entries.length === 0 ? undefined : { reason: "head-missing", brokenAtIndex: entries.length };
|
|
153
|
+
if (read.state === "invalid")
|
|
154
|
+
return { reason: "head-invalid", brokenAtIndex: entries.length };
|
|
155
|
+
const { count, mac } = read.head;
|
|
156
|
+
if (count > entries.length)
|
|
157
|
+
return { reason: "truncated", brokenAtIndex: entries.length };
|
|
158
|
+
const expected = count === 0 ? GENESIS_HASH : entries[count - 1].mac;
|
|
159
|
+
if (mac !== expected)
|
|
160
|
+
return { reason: "head-invalid", brokenAtIndex: entries.length };
|
|
161
|
+
return undefined;
|
|
162
|
+
}
|
|
163
|
+
async function checkHead(identity, entries) {
|
|
164
|
+
return checkHeadAgainst(await readHead(identity), entries);
|
|
165
|
+
}
|
|
166
|
+
async function writeHead(identity, count, mac, encrypted) {
|
|
167
|
+
const head = encrypted ? { version: 1, count, mac, encrypted: true } : { version: 1, count, mac };
|
|
168
|
+
await writeFileAtomic(headFilePath(identity.storageDir), JSON.stringify({ ...head, headMac: computeHeadMac(identity, head) }));
|
|
169
|
+
}
|
|
170
|
+
const appendState = new Map();
|
|
171
|
+
function stateFor(storageDir) {
|
|
172
|
+
const key = resolve(storageDir);
|
|
173
|
+
let state = appendState.get(key);
|
|
174
|
+
if (!state) {
|
|
175
|
+
state = { queue: Promise.resolve(), tail: null, headTrusted: true, encrypted: false };
|
|
176
|
+
appendState.set(key, state);
|
|
177
|
+
}
|
|
178
|
+
return state;
|
|
179
|
+
}
|
|
180
|
+
async function doAppend(identity, entry, state) {
|
|
181
|
+
const full = { ...entry, at: new Date().toISOString() };
|
|
182
|
+
if (!state.tail) {
|
|
183
|
+
const existing = await readStoredEntries(identity.storageDir);
|
|
184
|
+
const head = await readHead(identity);
|
|
185
|
+
state.tail = { mac: existing.length > 0 ? existing[existing.length - 1].mac : GENESIS_HASH, entryCount: existing.length };
|
|
186
|
+
state.headTrusted = checkHeadAgainst(head, existing) === undefined;
|
|
187
|
+
state.encrypted = head.state === "ok" && head.head.encrypted === true;
|
|
188
|
+
}
|
|
189
|
+
const content = state.encrypted ? { version: 2, ...encryptEntryContent(identity, full) } : { ...full };
|
|
190
|
+
const withPrev = { ...content, prevMac: state.tail.mac };
|
|
191
|
+
const mac = computeMac(identity, withPrev);
|
|
192
|
+
const stored = { ...withPrev, mac };
|
|
193
|
+
await mkdir(identity.storageDir, { recursive: true, mode: 0o700 });
|
|
194
|
+
await appendFile(logFilePath(identity.storageDir), JSON.stringify(stored) + "\n", { mode: 0o600 });
|
|
195
|
+
state.tail = { mac, entryCount: state.tail.entryCount + 1 };
|
|
196
|
+
// Never overwrite a head that already disagreed with the log: that would
|
|
197
|
+
// launder a truncation into a "clean" state.
|
|
198
|
+
if (state.headTrusted)
|
|
199
|
+
await writeHead(identity, state.tail.entryCount, mac, state.encrypted);
|
|
200
|
+
}
|
|
27
201
|
/**
|
|
28
202
|
* Appends one entry to the local, append-only audit log (public metadata
|
|
29
|
-
* only — no secrets). Each entry is chained to the previous one's
|
|
30
|
-
* (see verifyLogIntegrity)
|
|
31
|
-
*
|
|
203
|
+
* only — no secrets). Each entry is chained to the previous one's MAC
|
|
204
|
+
* (see verifyLogIntegrity), keyed from identity's own secret material
|
|
205
|
+
* (ADR 0009) so tampering or deleting past entries is detectable by anyone
|
|
206
|
+
* EXCEPT someone who also holds that secret — not just theoretically
|
|
207
|
+
* append-only, and not forgeable by file-write access alone.
|
|
32
208
|
*/
|
|
33
209
|
export async function appendLogEntry(identity, entry) {
|
|
34
|
-
const
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
210
|
+
const state = stateFor(identity.storageDir);
|
|
211
|
+
// Chain onto the previous operation regardless of how it settled (a past
|
|
212
|
+
// failure must not permanently block future appends to this storageDir),
|
|
213
|
+
// but this call's own outcome — via `task`, awaited below — propagates to
|
|
214
|
+
// ITS caller untouched.
|
|
215
|
+
const task = state.queue.catch(() => { }).then(() => doAppend(identity, entry, state));
|
|
216
|
+
state.queue = task.catch(() => { });
|
|
217
|
+
await task;
|
|
41
218
|
}
|
|
219
|
+
/** Transparently decrypts any v2 (encrypted, issue #43) entries alongside plaintext v1 ones. */
|
|
42
220
|
export async function readLog(identity) {
|
|
43
|
-
return (await readStoredEntries(identity.storageDir)).map((
|
|
221
|
+
return (await readStoredEntries(identity.storageDir)).map((entry) => (isV2Entry(entry) ? decryptEntryContent(identity, entry) : stripChainFields(entry)));
|
|
44
222
|
}
|
|
45
223
|
/**
|
|
46
|
-
* Walks the
|
|
224
|
+
* Walks the MAC chain and reports whether it's intact. If an entry was
|
|
47
225
|
* edited or deleted, `brokenAtIndex` is the (0-based) index of the first
|
|
48
|
-
* entry whose chain link no longer matches.
|
|
226
|
+
* entry whose chain link no longer matches. Recomputing a matching MAC
|
|
227
|
+
* requires identity's own secret material (see computeMac/deriveAuditLogKey)
|
|
228
|
+
* — this is what makes forging the chain require more than filesystem
|
|
229
|
+
* write access (ADR 0009). It also checks the authenticated head record
|
|
230
|
+
* (ADR 0019), so deleting the most recent entries is reported too, with
|
|
231
|
+
* `reason: "truncated"`.
|
|
232
|
+
*
|
|
233
|
+
* Not detectable: replacing BOTH log.jsonl and log.head.json with an older,
|
|
234
|
+
* consistent pair, or deleting both files outright. Keep an off-box copy of
|
|
235
|
+
* the head if that matters to you.
|
|
49
236
|
*/
|
|
50
237
|
export async function verifyLogIntegrity(identity) {
|
|
51
238
|
const entries = await readStoredEntries(identity.storageDir);
|
|
52
|
-
let
|
|
239
|
+
let expectedPrevMac = GENESIS_HASH;
|
|
53
240
|
for (let i = 0; i < entries.length; i++) {
|
|
54
|
-
const {
|
|
55
|
-
if (
|
|
241
|
+
const { prevMac, mac, ...rest } = entries[i];
|
|
242
|
+
if (prevMac !== expectedPrevMac || !macEquals(mac, computeMac(identity, { ...rest, prevMac }))) {
|
|
56
243
|
return { intact: false, brokenAtIndex: i };
|
|
57
244
|
}
|
|
58
|
-
|
|
245
|
+
expectedPrevMac = mac;
|
|
59
246
|
}
|
|
247
|
+
const problem = await checkHead(identity, entries);
|
|
248
|
+
if (problem)
|
|
249
|
+
return { intact: false, ...problem };
|
|
60
250
|
return { intact: true };
|
|
61
251
|
}
|
|
252
|
+
/**
|
|
253
|
+
* One-time, explicit opt-in (issue #43/ADR 0027) to encrypt the audit log's
|
|
254
|
+
* entry content, so filesystem access alone no longer reveals what actions
|
|
255
|
+
* this identity has performed. Re-encrypts every existing entry in one pass
|
|
256
|
+
* (there is no mixed-format file afterwards) and marks the head record so
|
|
257
|
+
* every later `appendLogEntry` call encrypts just the one entry it writes —
|
|
258
|
+
* never the whole log again. A no-op if the log is already encrypted.
|
|
259
|
+
* Throws if the log fails its integrity check first: there is nothing safe
|
|
260
|
+
* to migrate if the chain can't be trusted.
|
|
261
|
+
*/
|
|
262
|
+
export async function migrateAuditLogToEncrypted(identity) {
|
|
263
|
+
const state = stateFor(identity.storageDir);
|
|
264
|
+
const task = state.queue.catch(() => { }).then(() => doMigrateToEncrypted(identity, state));
|
|
265
|
+
state.queue = task.catch(() => { });
|
|
266
|
+
await task;
|
|
267
|
+
}
|
|
268
|
+
async function doMigrateToEncrypted(identity, state) {
|
|
269
|
+
const head = await readHead(identity);
|
|
270
|
+
if (head.state === "ok" && head.head.encrypted === true)
|
|
271
|
+
return; // already encrypted — true no-op
|
|
272
|
+
const integrity = await verifyLogIntegrity(identity);
|
|
273
|
+
if (!integrity.intact) {
|
|
274
|
+
throw new Error("Cannot migrate: the audit log failed its integrity check. Investigate before encrypting it.");
|
|
275
|
+
}
|
|
276
|
+
const entries = await readStoredEntries(identity.storageDir);
|
|
277
|
+
let prevMac = GENESIS_HASH;
|
|
278
|
+
const rewritten = [];
|
|
279
|
+
for (const entry of entries) {
|
|
280
|
+
const content = isV2Entry(entry) ? decryptEntryContent(identity, entry) : stripChainFields(entry);
|
|
281
|
+
const { nonce, ciphertext } = encryptEntryContent(identity, content);
|
|
282
|
+
const withPrev = { version: 2, nonce, ciphertext, prevMac };
|
|
283
|
+
const mac = computeMac(identity, withPrev);
|
|
284
|
+
rewritten.push({ ...withPrev, mac });
|
|
285
|
+
prevMac = mac;
|
|
286
|
+
}
|
|
287
|
+
await mkdir(identity.storageDir, { recursive: true, mode: 0o700 });
|
|
288
|
+
await writeFileAtomic(logFilePath(identity.storageDir), rewritten.map((entry) => JSON.stringify(entry)).join("\n") + (rewritten.length > 0 ? "\n" : ""));
|
|
289
|
+
await writeHead(identity, rewritten.length, prevMac, true);
|
|
290
|
+
state.tail = { mac: prevMac, entryCount: rewritten.length };
|
|
291
|
+
state.headTrusted = true;
|
|
292
|
+
state.encrypted = true;
|
|
293
|
+
}
|
|
62
294
|
//# 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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"}
|