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/identity.js
CHANGED
|
@@ -1,9 +1,35 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { equalBytes } from "@noble/ciphers/utils.js";
|
|
2
|
+
import { bytesToHex, hexToBytes, utf8ToBytes } from "@noble/hashes/utils.js";
|
|
2
3
|
import { existsSync } from "node:fs";
|
|
3
4
|
import { join } from "node:path";
|
|
4
|
-
import { generateClassicalKeypair } from "./crypto/classical.js";
|
|
5
|
-
import {
|
|
6
|
-
import {
|
|
5
|
+
import { classicalSign, classicalVerify, generateClassicalKeypair, isValidClassicalKeypairShape } from "./crypto/classical.js";
|
|
6
|
+
import { signCore } from "./crypto/hybrid.js";
|
|
7
|
+
import { decryptFileWithFormat, DecryptionFailedError, encryptToFile, upgradeLegacyFile } from "./fileCrypto.js";
|
|
8
|
+
import { assertAcceptablePassphrase, MIN_PASSPHRASE_LENGTH, normalizePassphrase } from "./passphrase.js";
|
|
9
|
+
import { generatePqcKemKeypair, generatePqcSignKeypair, isValidPqcKemKeypairShape, isValidPqcSignKeypairShape, pqcDecapsulate, pqcEncapsulate, pqcSign, pqcVerify, } from "./crypto/pqc.js";
|
|
10
|
+
function isHex(value) {
|
|
11
|
+
return typeof value === "string" && value.length % 2 === 0 && /^[0-9a-f]*$/i.test(value);
|
|
12
|
+
}
|
|
13
|
+
function isValidKeypairShape(value) {
|
|
14
|
+
if (typeof value !== "object" || value === null)
|
|
15
|
+
return false;
|
|
16
|
+
const v = value;
|
|
17
|
+
return isHex(v.publicKey) && isHex(v.secretKey);
|
|
18
|
+
}
|
|
19
|
+
/**
|
|
20
|
+
* Structural shape check for a decrypted identity file's plaintext. In
|
|
21
|
+
* normal operation AEAD authentication already guarantees this shape, so a
|
|
22
|
+
* malformed value here would mean a wrong/corrupted ciphertext slipped past
|
|
23
|
+
* decryption (practically impossible without the passphrase) — this exists
|
|
24
|
+
* so that case fails closed with DecryptionFailedError instead of crashing
|
|
25
|
+
* on a missing/non-hex field (found by fuzzing, issue #42).
|
|
26
|
+
*/
|
|
27
|
+
function isValidSerializedIdentityShape(data) {
|
|
28
|
+
if (typeof data !== "object" || data === null)
|
|
29
|
+
return false;
|
|
30
|
+
const d = data;
|
|
31
|
+
return typeof d.alias === "string" && isValidKeypairShape(d.classical) && isValidKeypairShape(d.pqcSign) && isValidKeypairShape(d.pqcKem);
|
|
32
|
+
}
|
|
7
33
|
function identityFilePath(storageDir) {
|
|
8
34
|
return join(storageDir, "identity.enc.json");
|
|
9
35
|
}
|
|
@@ -42,20 +68,28 @@ function deserialize(storageDir, data) {
|
|
|
42
68
|
},
|
|
43
69
|
};
|
|
44
70
|
}
|
|
45
|
-
export
|
|
71
|
+
export { MIN_PASSPHRASE_LENGTH };
|
|
46
72
|
/**
|
|
47
73
|
* Generates a new hybrid classical+post-quantum identity (one ECDSA/ECDH-P384
|
|
48
74
|
* keypair, one ML-DSA-87 signing keypair, one ML-KEM-1024 key-exchange
|
|
49
75
|
* keypair) and persists it encrypted-at-rest under `storageDir`.
|
|
50
76
|
*
|
|
51
|
-
*
|
|
52
|
-
*
|
|
53
|
-
*
|
|
77
|
+
* The passphrase is normalized to Unicode NFKC, must be at least
|
|
78
|
+
* MIN_PASSPHRASE_LENGTH characters (counted as code points after
|
|
79
|
+
* normalization), must not be a single repeated character, and must not be
|
|
80
|
+
* on a small blocklist of very common passphrases (ADR 0017). These checks
|
|
81
|
+
* only apply here, not to loadIdentity, so an identity created before they
|
|
82
|
+
* existed can still be loaded with its original passphrase.
|
|
83
|
+
*
|
|
84
|
+
* `options.checkPassphrase`, if supplied, runs after all of the above pass
|
|
85
|
+
* (ADR 0021) — this is where real breach-corpus checking belongs, not in
|
|
86
|
+
* this library's own, deliberately small, blocklist.
|
|
54
87
|
*/
|
|
55
88
|
export async function createIdentity(options) {
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
89
|
+
const passphrase = normalizePassphrase(options.passphrase);
|
|
90
|
+
assertAcceptablePassphrase(passphrase);
|
|
91
|
+
if (options.checkPassphrase)
|
|
92
|
+
await options.checkPassphrase(passphrase);
|
|
59
93
|
const filePath = identityFilePath(options.storageDir);
|
|
60
94
|
if (!options.overwrite && existsSync(filePath)) {
|
|
61
95
|
throw new Error(`An identity already exists at ${filePath}. Pass { overwrite: true } to replace it.`);
|
|
@@ -67,14 +101,102 @@ export async function createIdentity(options) {
|
|
|
67
101
|
pqcSign: generatePqcSignKeypair(),
|
|
68
102
|
pqcKem: generatePqcKemKeypair(),
|
|
69
103
|
};
|
|
70
|
-
await encryptToFile(filePath,
|
|
104
|
+
await encryptToFile(filePath, passphrase, serialize(identity));
|
|
71
105
|
return identity;
|
|
72
106
|
}
|
|
73
107
|
/** Loads a previously-created identity back from disk. */
|
|
74
108
|
export async function loadIdentity(options) {
|
|
75
|
-
const
|
|
109
|
+
const filePath = identityFilePath(options.storageDir);
|
|
110
|
+
const normalized = normalizePassphrase(options.passphrase);
|
|
111
|
+
// Normalized form first; the raw form only matters for a file written
|
|
112
|
+
// before normalization existed under a passphrase NFKC would change.
|
|
113
|
+
const candidates = normalized === options.passphrase ? [normalized] : [normalized, options.passphrase];
|
|
114
|
+
const { data, legacy, passphraseIndex } = await decryptFileWithFormat(filePath, candidates);
|
|
115
|
+
if (!isValidSerializedIdentityShape(data))
|
|
116
|
+
throw new DecryptionFailedError();
|
|
117
|
+
if (legacy || passphraseIndex > 0) {
|
|
118
|
+
// Upgrade a pre-versioned file (and/or re-key one made under an
|
|
119
|
+
// un-normalized passphrase) to the current format under the normalized
|
|
120
|
+
// passphrase now that the passphrase is proven correct. Best effort: a
|
|
121
|
+
// read-only directory or a full disk must not stop the identity from
|
|
122
|
+
// loading.
|
|
123
|
+
try {
|
|
124
|
+
await upgradeLegacyFile(filePath, normalized, data);
|
|
125
|
+
}
|
|
126
|
+
catch {
|
|
127
|
+
// The original file is untouched (see upgradeLegacyFile); try again on the next load.
|
|
128
|
+
}
|
|
129
|
+
}
|
|
76
130
|
return deserialize(options.storageDir, data);
|
|
77
131
|
}
|
|
132
|
+
/**
|
|
133
|
+
* Builds an Identity from a caller-supplied KeyStore instead of a
|
|
134
|
+
* passphrase-encrypted file — `identity.enc.json` and a passphrase are
|
|
135
|
+
* never part of the picture. The returned key material's shape (byte
|
|
136
|
+
* lengths) is validated before trusting it, so a buggy KeyStore fails
|
|
137
|
+
* clearly here rather than with a cryptic error the first time a signing
|
|
138
|
+
* call happens to touch the bad bytes. `storageDir` is still required: the
|
|
139
|
+
* trust store and audit log are a separate concern from where secret keys
|
|
140
|
+
* live, unaffected by this function.
|
|
141
|
+
*/
|
|
142
|
+
export async function loadIdentityFromKeyStore(options) {
|
|
143
|
+
const { classical, pqcSign, pqcKem } = await options.keyStore.load();
|
|
144
|
+
if (!isValidClassicalKeypairShape(classical))
|
|
145
|
+
throw new Error("KeyStore returned a classical keypair with the wrong byte length(s) for P-384.");
|
|
146
|
+
if (!isValidPqcSignKeypairShape(pqcSign))
|
|
147
|
+
throw new Error("KeyStore returned a pqcSign keypair with the wrong byte length(s) for ML-DSA-87.");
|
|
148
|
+
if (!isValidPqcKemKeypairShape(pqcKem))
|
|
149
|
+
throw new Error("KeyStore returned a pqcKem keypair with the wrong byte length(s) for ML-KEM-1024.");
|
|
150
|
+
assertKeypairsMatch(classical, pqcSign, pqcKem);
|
|
151
|
+
return { alias: options.alias, storageDir: options.storageDir, classical, pqcSign, pqcKem };
|
|
152
|
+
}
|
|
153
|
+
/**
|
|
154
|
+
* Proves each secret key pairs with its public key by one round trip, once at load (issue #60).
|
|
155
|
+
* Length checks alone let a KeyStore return A's secret key with B's public key: the identity would
|
|
156
|
+
* sign with A while publishing B, and peers would see verification failures far from the cause.
|
|
157
|
+
*/
|
|
158
|
+
function assertKeypairsMatch(classical, pqcSignKeys, pqcKemKeys) {
|
|
159
|
+
const probe = utf8ToBytes("raz-encrypt keypair self-check");
|
|
160
|
+
let ok;
|
|
161
|
+
try {
|
|
162
|
+
ok = classicalVerify(classicalSign(probe, classical.secretKey), probe, classical.publicKey);
|
|
163
|
+
}
|
|
164
|
+
catch {
|
|
165
|
+
ok = false;
|
|
166
|
+
}
|
|
167
|
+
if (!ok)
|
|
168
|
+
throw new Error("KeyStore returned a classical public key that does not match its secret key (P-384).");
|
|
169
|
+
try {
|
|
170
|
+
ok = pqcVerify(pqcSign(probe, pqcSignKeys.secretKey), probe, pqcSignKeys.publicKey);
|
|
171
|
+
}
|
|
172
|
+
catch {
|
|
173
|
+
ok = false;
|
|
174
|
+
}
|
|
175
|
+
if (!ok)
|
|
176
|
+
throw new Error("KeyStore returned a pqcSign public key that does not match its secret key (ML-DSA-87).");
|
|
177
|
+
try {
|
|
178
|
+
const { cipherText, sharedSecret } = pqcEncapsulate(pqcKemKeys.publicKey);
|
|
179
|
+
ok = equalBytes(pqcDecapsulate(cipherText, pqcKemKeys.secretKey), sharedSecret);
|
|
180
|
+
}
|
|
181
|
+
catch {
|
|
182
|
+
ok = false;
|
|
183
|
+
}
|
|
184
|
+
if (!ok)
|
|
185
|
+
throw new Error("KeyStore returned a pqcKem public key that does not match its secret key (ML-KEM-1024).");
|
|
186
|
+
}
|
|
187
|
+
/**
|
|
188
|
+
* Best-effort wipe of the secret key bytes held by `identity`: overwrites
|
|
189
|
+
* them with zeros so they do not linger in this process's memory after you
|
|
190
|
+
* are done. The identity is unusable afterwards. Limits: JavaScript gives no
|
|
191
|
+
* guarantee about copies the engine or GC already made, and the hex strings
|
|
192
|
+
* produced while saving or loading an identity are immutable and cannot be
|
|
193
|
+
* wiped. This narrows the exposure window; it is not a secure-erase.
|
|
194
|
+
*/
|
|
195
|
+
export function zeroizeIdentity(identity) {
|
|
196
|
+
identity.classical.secretKey.fill(0);
|
|
197
|
+
identity.pqcSign.secretKey.fill(0);
|
|
198
|
+
identity.pqcKem.secretKey.fill(0);
|
|
199
|
+
}
|
|
78
200
|
/** The shareable public half of an identity — hand this to the other party out of band. */
|
|
79
201
|
export function exportPublicIdentity(identity) {
|
|
80
202
|
return {
|
|
@@ -84,4 +206,17 @@ export function exportPublicIdentity(identity) {
|
|
|
84
206
|
pqcKemPublicKey: bytesToHex(identity.pqcKem.publicKey),
|
|
85
207
|
};
|
|
86
208
|
}
|
|
209
|
+
/**
|
|
210
|
+
* A self-signed, optionally time-limited version of `exportPublicIdentity`'s
|
|
211
|
+
* card (ADR 0023/issue #37): the identity signs its own card, so a peer can
|
|
212
|
+
* verify it wasn't tampered with and — if `options.expiresAt` was set —
|
|
213
|
+
* reject it once expired, enforcing a cryptoperiod the bare card has no way
|
|
214
|
+
* to carry (it has no signature at all). `exportPublicIdentity` itself is
|
|
215
|
+
* unchanged; this is a separate, opt-in function precisely so every
|
|
216
|
+
* existing caller of the bare card is unaffected. Verify and trust a
|
|
217
|
+
* received one with `trustSignedCard`, not `trustParty`.
|
|
218
|
+
*/
|
|
219
|
+
export async function exportSignedCard(identity, options = {}) {
|
|
220
|
+
return signCore(identity, exportPublicIdentity(identity), options);
|
|
221
|
+
}
|
|
87
222
|
//# sourceMappingURL=identity.js.map
|
package/dist/identity.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"identity.js","sourceRoot":"","sources":["../src/identity.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,UAAU,EAAE,MAAM,wBAAwB,CAAC;
|
|
1
|
+
{"version":3,"file":"identity.js","sourceRoot":"","sources":["../src/identity.ts"],"names":[],"mappings":"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"}
|
package/dist/index.d.ts
CHANGED
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
export { canonicalize, hashDocument } from "./canonicalJson.js";
|
|
2
2
|
export { fingerprint } from "./fingerprint.js";
|
|
3
|
-
export { decryptFrom, encryptFor, sign, UntrustedPartyError, verify } from "./crypto/nodeHybrid.js";
|
|
4
|
-
export type { Certificate, EncryptedBox, VerifyFailureReason, VerifyResult } from "./crypto/nodeHybrid.js";
|
|
5
|
-
export { createIdentity, exportPublicIdentity, loadIdentity, MIN_PASSPHRASE_LENGTH } from "./identity.js";
|
|
6
|
-
export type { CreateIdentityOptions, Identity, LoadIdentityOptions, PublicCard } from "./identity.js";
|
|
7
|
-
export {
|
|
3
|
+
export { decryptFrom, DecryptionError, encryptFor, ReplayedBoxError, sign, UntrustedPartyError, verify } from "./crypto/nodeHybrid.js";
|
|
4
|
+
export type { BoxReplayInfo, Certificate, EncryptedBox, VerifyFailureReason, VerifyResult } from "./crypto/nodeHybrid.js";
|
|
5
|
+
export { createIdentity, exportPublicIdentity, exportSignedCard, loadIdentity, loadIdentityFromKeyStore, MIN_PASSPHRASE_LENGTH, zeroizeIdentity, } from "./identity.js";
|
|
6
|
+
export type { CreateIdentityOptions, Identity, KeyStore, LoadIdentityFromKeyStoreOptions, LoadIdentityOptions, PublicCard } from "./identity.js";
|
|
7
|
+
export type { PassphraseChecker } from "./passphrase.js";
|
|
8
|
+
export { migrateAuditLogToEncrypted, readLog, verifyLogIntegrity } from "./store.js";
|
|
8
9
|
export type { LogEntry, LogIntegrityResult } from "./store.js";
|
|
9
|
-
export { getTrustedParty, getTrustedPartiesByAlias, getTrustRecordsByAlias, listTrustedParties, listTrustRecords, trustParty, untrustParty, } from "./trust.js";
|
|
10
|
-
export type { TrustRecord } from "./trust.js";
|
|
10
|
+
export { applyRevocationNotice, applyRotation, beginRotation, completeRotation, createRevocationNotice, getTrustedParty, getTrustStoreAnchor, getTrustedPartiesByAlias, getTrustRecordsByAlias, listTrustedParties, listTrustRecords, migrateLegacyTrustStore, migrateTrustStoreToEncrypted, TrustStoreIntegrityError, trustParty, trustSignedCard, untrustFingerprint, untrustParty, verifyTrustStoreAnchor, } from "./trust.js";
|
|
11
|
+
export type { RevocationDocument, RevocationNotice, RotationDocument, RotationStatement, TrustRecord, TrustStoreAnchor } from "./trust.js";
|
package/dist/index.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
export { canonicalize, hashDocument } from "./canonicalJson.js";
|
|
2
2
|
export { fingerprint } from "./fingerprint.js";
|
|
3
|
-
export { decryptFrom, encryptFor, sign, UntrustedPartyError, verify } from "./crypto/nodeHybrid.js";
|
|
4
|
-
export { createIdentity, exportPublicIdentity, loadIdentity, MIN_PASSPHRASE_LENGTH } from "./identity.js";
|
|
5
|
-
export { readLog, verifyLogIntegrity } from "./store.js";
|
|
6
|
-
export { getTrustedParty, getTrustedPartiesByAlias, getTrustRecordsByAlias, listTrustedParties, listTrustRecords, trustParty, untrustParty, } from "./trust.js";
|
|
3
|
+
export { decryptFrom, DecryptionError, encryptFor, ReplayedBoxError, sign, UntrustedPartyError, verify } from "./crypto/nodeHybrid.js";
|
|
4
|
+
export { createIdentity, exportPublicIdentity, exportSignedCard, loadIdentity, loadIdentityFromKeyStore, MIN_PASSPHRASE_LENGTH, zeroizeIdentity, } from "./identity.js";
|
|
5
|
+
export { migrateAuditLogToEncrypted, readLog, verifyLogIntegrity } from "./store.js";
|
|
6
|
+
export { applyRevocationNotice, applyRotation, beginRotation, completeRotation, createRevocationNotice, getTrustedParty, getTrustStoreAnchor, getTrustedPartiesByAlias, getTrustRecordsByAlias, listTrustedParties, listTrustRecords, migrateLegacyTrustStore, migrateTrustStoreToEncrypted, TrustStoreIntegrityError, trustParty, trustSignedCard, untrustFingerprint, untrustParty, verifyTrustStoreAnchor, } from "./trust.js";
|
|
7
7
|
//# sourceMappingURL=index.js.map
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAC;AAChE,OAAO,EAAE,WAAW,EAAE,MAAM,kBAAkB,CAAC;AAC/C,OAAO,EAAE,WAAW,EAAE,UAAU,EAAE,IAAI,EAAE,mBAAmB,EAAE,MAAM,EAAE,MAAM,wBAAwB,CAAC;
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAC;AAChE,OAAO,EAAE,WAAW,EAAE,MAAM,kBAAkB,CAAC;AAC/C,OAAO,EAAE,WAAW,EAAE,eAAe,EAAE,UAAU,EAAE,gBAAgB,EAAE,IAAI,EAAE,mBAAmB,EAAE,MAAM,EAAE,MAAM,wBAAwB,CAAC;AAEvI,OAAO,EACL,cAAc,EACd,oBAAoB,EACpB,gBAAgB,EAChB,YAAY,EACZ,wBAAwB,EACxB,qBAAqB,EACrB,eAAe,GAChB,MAAM,eAAe,CAAC;AAGvB,OAAO,EAAE,0BAA0B,EAAE,OAAO,EAAE,kBAAkB,EAAE,MAAM,YAAY,CAAC;AAErF,OAAO,EACL,qBAAqB,EACrB,aAAa,EACb,aAAa,EACb,gBAAgB,EAChB,sBAAsB,EACtB,eAAe,EACf,mBAAmB,EACnB,wBAAwB,EACxB,sBAAsB,EACtB,kBAAkB,EAClB,gBAAgB,EAChB,uBAAuB,EACvB,4BAA4B,EAC5B,wBAAwB,EACxB,UAAU,EACV,eAAe,EACf,kBAAkB,EAClB,YAAY,EACZ,sBAAsB,GACvB,MAAM,YAAY,CAAC"}
|
|
@@ -0,0 +1,27 @@
|
|
|
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 declare function normalizePassphrase(passphrase: string): string;
|
|
13
|
+
export declare const MIN_PASSPHRASE_LENGTH = 12;
|
|
14
|
+
/**
|
|
15
|
+
* A caller-supplied passphrase check, run by createIdentity AFTER the
|
|
16
|
+
* built-in checks (length, repeated-character, the small blocklist above)
|
|
17
|
+
* pass. Receives the already-normalized passphrase. Throw (synchronously or
|
|
18
|
+
* via a rejected promise) to reject it with your own message — e.g. because
|
|
19
|
+
* it's found in a real breach corpus, or scores poorly under a strength
|
|
20
|
+
* estimator. This is the actual fix for NIST SP 800-63B's breach-check
|
|
21
|
+
* expectation (ADR 0021): the built-in blocklist above is deliberately not
|
|
22
|
+
* grown to try to meet that bar itself, and this library adds no built-in
|
|
23
|
+
* network-calling checker (e.g. an HIBP lookup) of its own.
|
|
24
|
+
*/
|
|
25
|
+
export type PassphraseChecker = (normalized: string) => void | Promise<void>;
|
|
26
|
+
/** Throws if `passphrase` (already normalized) is unacceptable for a NEW identity. Loading an existing identity never calls this. */
|
|
27
|
+
export declare function assertAcceptablePassphrase(normalized: string): void;
|
|
@@ -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
|
@@ -1,28 +1,65 @@
|
|
|
1
1
|
import type { Identity } from "./identity.js";
|
|
2
2
|
export interface LogEntry {
|
|
3
|
-
action: "sign" | "verify" | "encrypt" | "decrypt";
|
|
3
|
+
action: "sign" | "verify" | "encrypt" | "decrypt" | "trust" | "untrust" | "rotate" | "revoke";
|
|
4
4
|
documentHash?: string;
|
|
5
5
|
counterpartAlias?: string;
|
|
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>;
|