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
package/dist/fileCrypto.js
CHANGED
|
@@ -1,45 +1,214 @@
|
|
|
1
1
|
import { gcm } from "@noble/ciphers/aes.js";
|
|
2
|
-
import {
|
|
2
|
+
import { scryptAsync } from "@noble/hashes/scrypt.js";
|
|
3
3
|
import { bytesToHex, hexToBytes, randomBytes, utf8ToBytes } from "@noble/hashes/utils.js";
|
|
4
|
-
import { mkdir, readFile,
|
|
4
|
+
import { mkdir, open, readFile, rename, rm } from "node:fs/promises";
|
|
5
5
|
import { dirname } from "node:path";
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
}
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
6
|
+
import { canonicalize } from "./canonicalJson.js";
|
|
7
|
+
// Encrypted-at-rest file format (ADR 0016).
|
|
8
|
+
//
|
|
9
|
+
// v2 (current): { version: 2, kdf: { name: "scrypt", N, r, p }, salt, nonce, ciphertext }
|
|
10
|
+
// - the KDF parameters live in the file, so the cost can be raised later
|
|
11
|
+
// without breaking files written today;
|
|
12
|
+
// - the header (version, kdf, salt, nonce) is authenticated as AES-GCM AAD;
|
|
13
|
+
// - written atomically (temp file + fsync + rename).
|
|
14
|
+
// v1 (legacy, read-only): { salt, nonce, ciphertext } with fixed scrypt
|
|
15
|
+
// N=2^17 r=8 p=1 and no AAD. Still readable forever; callers may upgrade it.
|
|
16
|
+
/** Cost used for newly written files. Stored in each file, so it can be raised without a migration. */
|
|
17
|
+
export const DEFAULT_KDF = { name: "scrypt", N: 2 ** 17, r: 8, p: 1 };
|
|
18
|
+
const DK_LEN = 32;
|
|
19
|
+
/** The only parameters v1 files could ever have used. */
|
|
20
|
+
const LEGACY_KDF = { name: "scrypt", N: 2 ** 17, r: 8, p: 1 };
|
|
21
|
+
// A file we are asked to open must not be able to make us allocate unbounded
|
|
22
|
+
// memory (scrypt needs ~128*N*r bytes) or burn unbounded CPU.
|
|
23
|
+
const MIN_N = 2 ** 14;
|
|
24
|
+
const MAX_N = 2 ** 20;
|
|
25
|
+
const MAX_N_TIMES_R = 2 ** 23; // ~1 GiB of scrypt memory
|
|
26
|
+
const MAX_R = 16;
|
|
27
|
+
const MAX_P = 4;
|
|
25
28
|
export class DecryptionFailedError extends Error {
|
|
26
29
|
constructor() {
|
|
27
30
|
super("Failed to decrypt local store: wrong passphrase or corrupted/tampered file.");
|
|
28
31
|
this.name = "DecryptionFailedError";
|
|
29
32
|
}
|
|
30
33
|
}
|
|
31
|
-
|
|
34
|
+
/** The file was written by a newer raz-encrypt than this one understands. */
|
|
35
|
+
export class UnsupportedFileVersionError extends Error {
|
|
36
|
+
constructor(version) {
|
|
37
|
+
super(`Local store has format version ${String(version)}, which this version of raz-encrypt does not understand. Upgrade raz-encrypt.`);
|
|
38
|
+
this.name = "UnsupportedFileVersionError";
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
function isPowerOfTwo(n) {
|
|
42
|
+
return Number.isInteger(n) && n > 0 && (n & (n - 1)) === 0;
|
|
43
|
+
}
|
|
44
|
+
/** Rejects KDF parameters outside the range this library would ever write, before spending any memory or CPU on them. */
|
|
45
|
+
function validateKdf(kdf) {
|
|
46
|
+
const k = kdf;
|
|
47
|
+
const ok = typeof k === "object" &&
|
|
48
|
+
k !== null &&
|
|
49
|
+
k.name === "scrypt" &&
|
|
50
|
+
isPowerOfTwo(k.N) &&
|
|
51
|
+
k.N >= MIN_N &&
|
|
52
|
+
k.N <= MAX_N &&
|
|
53
|
+
Number.isInteger(k.r) &&
|
|
54
|
+
k.r >= 1 &&
|
|
55
|
+
k.r <= MAX_R &&
|
|
56
|
+
Number.isInteger(k.p) &&
|
|
57
|
+
k.p >= 1 &&
|
|
58
|
+
k.p <= MAX_P &&
|
|
59
|
+
k.N * k.r <= MAX_N_TIMES_R;
|
|
60
|
+
if (!ok)
|
|
61
|
+
throw new DecryptionFailedError();
|
|
62
|
+
}
|
|
63
|
+
async function deriveKey(passphrase, salt, kdf) {
|
|
64
|
+
return scryptAsync(utf8ToBytes(passphrase), salt, { N: kdf.N, r: kdf.r, p: kdf.p, dkLen: DK_LEN });
|
|
65
|
+
}
|
|
66
|
+
/** The authenticated header bytes: everything in the file except the ciphertext itself. */
|
|
67
|
+
function headerAad(file) {
|
|
68
|
+
return utf8ToBytes(canonicalize({ version: file.version, kdf: file.kdf, salt: file.salt, nonce: file.nonce }));
|
|
69
|
+
}
|
|
70
|
+
async function encryptToString(passphrase, data) {
|
|
71
|
+
const salt = randomBytes(16);
|
|
72
|
+
const nonce = randomBytes(12);
|
|
73
|
+
const kdf = { ...DEFAULT_KDF };
|
|
74
|
+
const key = await deriveKey(passphrase, salt, kdf);
|
|
75
|
+
const header = { version: 2, kdf, salt: bytesToHex(salt), nonce: bytesToHex(nonce) };
|
|
76
|
+
const ciphertext = gcm(key, nonce, headerAad(header)).encrypt(utf8ToBytes(JSON.stringify(data)));
|
|
77
|
+
const file = { ...header, ciphertext: bytesToHex(ciphertext) };
|
|
78
|
+
return JSON.stringify(file);
|
|
79
|
+
}
|
|
80
|
+
/**
|
|
81
|
+
* Atomically replaces `filePath` with `contents` (mode 0o600): write a temp
|
|
82
|
+
* file in the same directory, fsync it, rename it over the target, then
|
|
83
|
+
* best-effort fsync the directory. A crash at any point leaves either the
|
|
84
|
+
* complete old file or the complete new file — never a partial one.
|
|
85
|
+
*/
|
|
86
|
+
export async function writeFileAtomic(filePath, contents, beforeRename) {
|
|
87
|
+
const dir = dirname(filePath);
|
|
88
|
+
await mkdir(dir, { recursive: true, mode: 0o700 });
|
|
89
|
+
const tmpPath = `${filePath}.${process.pid}.${Date.now()}.tmp`;
|
|
90
|
+
try {
|
|
91
|
+
const handle = await open(tmpPath, "w", 0o600);
|
|
92
|
+
try {
|
|
93
|
+
await handle.writeFile(contents, "utf8");
|
|
94
|
+
await handle.sync();
|
|
95
|
+
}
|
|
96
|
+
finally {
|
|
97
|
+
await handle.close();
|
|
98
|
+
}
|
|
99
|
+
if (beforeRename)
|
|
100
|
+
await beforeRename();
|
|
101
|
+
await rename(tmpPath, filePath);
|
|
102
|
+
}
|
|
103
|
+
catch (err) {
|
|
104
|
+
await rm(tmpPath, { force: true });
|
|
105
|
+
throw err;
|
|
106
|
+
}
|
|
107
|
+
try {
|
|
108
|
+
const dirHandle = await open(dir, "r");
|
|
109
|
+
try {
|
|
110
|
+
await dirHandle.sync();
|
|
111
|
+
}
|
|
112
|
+
finally {
|
|
113
|
+
await dirHandle.close();
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
catch {
|
|
117
|
+
// Directory fsync is best-effort (unsupported on some platforms/filesystems).
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
/** Encrypts `data` (any JSON-serializable value) at rest under a passphrase-derived AES-256-GCM key, in the current (v2) format, atomically. */
|
|
121
|
+
export async function encryptToFile(filePath, passphrase, data) {
|
|
122
|
+
await writeFileAtomic(filePath, await encryptToString(passphrase, data));
|
|
123
|
+
}
|
|
124
|
+
async function decryptParsed(payload, passphrases) {
|
|
125
|
+
if (typeof payload !== "object" || payload === null)
|
|
126
|
+
throw new DecryptionFailedError();
|
|
127
|
+
const version = payload.version;
|
|
128
|
+
let salt;
|
|
129
|
+
let nonce;
|
|
130
|
+
let ciphertext;
|
|
131
|
+
let kdf;
|
|
132
|
+
let aad;
|
|
133
|
+
let legacy;
|
|
134
|
+
try {
|
|
135
|
+
if (version === undefined) {
|
|
136
|
+
const v1 = payload;
|
|
137
|
+
salt = hexToBytes(v1.salt);
|
|
138
|
+
nonce = hexToBytes(v1.nonce);
|
|
139
|
+
ciphertext = hexToBytes(v1.ciphertext);
|
|
140
|
+
kdf = { ...LEGACY_KDF };
|
|
141
|
+
aad = undefined;
|
|
142
|
+
legacy = true;
|
|
143
|
+
}
|
|
144
|
+
else if (version === 2) {
|
|
145
|
+
const v2 = payload;
|
|
146
|
+
validateKdf(v2.kdf);
|
|
147
|
+
salt = hexToBytes(v2.salt);
|
|
148
|
+
nonce = hexToBytes(v2.nonce);
|
|
149
|
+
ciphertext = hexToBytes(v2.ciphertext);
|
|
150
|
+
kdf = v2.kdf;
|
|
151
|
+
aad = headerAad(v2);
|
|
152
|
+
legacy = false;
|
|
153
|
+
}
|
|
154
|
+
else {
|
|
155
|
+
throw new UnsupportedFileVersionError(version);
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
catch (err) {
|
|
159
|
+
if (err instanceof UnsupportedFileVersionError)
|
|
160
|
+
throw err;
|
|
161
|
+
throw new DecryptionFailedError();
|
|
162
|
+
}
|
|
163
|
+
for (let passphraseIndex = 0; passphraseIndex < passphrases.length; passphraseIndex++) {
|
|
164
|
+
const key = await deriveKey(passphrases[passphraseIndex], salt, kdf);
|
|
165
|
+
try {
|
|
166
|
+
const plaintext = gcm(key, nonce, aad).decrypt(ciphertext);
|
|
167
|
+
return { data: JSON.parse(new TextDecoder().decode(plaintext)), legacy, passphraseIndex };
|
|
168
|
+
}
|
|
169
|
+
catch {
|
|
170
|
+
// Try the next candidate passphrase, if any.
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
throw new DecryptionFailedError();
|
|
174
|
+
}
|
|
175
|
+
/**
|
|
176
|
+
* Decrypts a file of any supported version and reports whether it was the
|
|
177
|
+
* legacy format and which candidate passphrase worked. `passphrases` are tried
|
|
178
|
+
* in order (callers pass the normalized form first, then the raw form for
|
|
179
|
+
* files written before normalization existed). A missing file rejects with
|
|
180
|
+
* the filesystem's ENOENT error.
|
|
181
|
+
*/
|
|
182
|
+
export async function decryptFileWithFormat(filePath, passphrases) {
|
|
32
183
|
const raw = await readFile(filePath, "utf8");
|
|
33
|
-
|
|
34
|
-
const salt = hexToBytes(payload.salt);
|
|
35
|
-
const nonce = hexToBytes(payload.nonce);
|
|
36
|
-
const key = deriveKey(passphrase, salt);
|
|
184
|
+
let payload;
|
|
37
185
|
try {
|
|
38
|
-
|
|
39
|
-
return JSON.parse(new TextDecoder().decode(plaintext));
|
|
186
|
+
payload = JSON.parse(raw);
|
|
40
187
|
}
|
|
41
188
|
catch {
|
|
42
189
|
throw new DecryptionFailedError();
|
|
43
190
|
}
|
|
191
|
+
return (await decryptParsed(payload, Array.isArray(passphrases) ? passphrases : [passphrases]));
|
|
192
|
+
}
|
|
193
|
+
export async function decryptFromFile(filePath, passphrase) {
|
|
194
|
+
return (await decryptFileWithFormat(filePath, passphrase)).data;
|
|
195
|
+
}
|
|
196
|
+
/**
|
|
197
|
+
* Rewrites a file in the current format, under `passphrase` (used for a
|
|
198
|
+
* legacy v1 file, or one unlocked by a pre-normalization passphrase). Safe by construction:
|
|
199
|
+
* the new file is written to a temp path, re-read and decrypted with the same
|
|
200
|
+
* passphrase to prove it round-trips to `data`, and only then renamed over
|
|
201
|
+
* the original — so the old file stays intact until the instant the verified
|
|
202
|
+
* replacement atomically takes its place.
|
|
203
|
+
*/
|
|
204
|
+
export async function upgradeLegacyFile(filePath, passphrase, data) {
|
|
205
|
+
const contents = await encryptToString(passphrase, data);
|
|
206
|
+
const expected = JSON.stringify(data);
|
|
207
|
+
await writeFileAtomic(filePath, contents, async () => {
|
|
208
|
+
// Verify the exact bytes we are about to publish, with the real passphrase.
|
|
209
|
+
const check = await decryptParsed(JSON.parse(contents), [passphrase]);
|
|
210
|
+
if (JSON.stringify(check.data) !== expected)
|
|
211
|
+
throw new Error("Upgraded identity file failed its round-trip check; leaving the original untouched.");
|
|
212
|
+
});
|
|
44
213
|
}
|
|
45
214
|
//# sourceMappingURL=fileCrypto.js.map
|
package/dist/fileCrypto.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"fileCrypto.js","sourceRoot":"","sources":["../src/fileCrypto.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,GAAG,EAAE,MAAM,uBAAuB,CAAC;AAC5C,OAAO,EAAE,
|
|
1
|
+
{"version":3,"file":"fileCrypto.js","sourceRoot":"","sources":["../src/fileCrypto.ts"],"names":[],"mappings":"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"}
|
package/dist/identity.d.ts
CHANGED
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
import { type ClassicalKeypair } from "./crypto/classical.js";
|
|
2
|
+
import { type Certificate, type SignOptions } from "./crypto/hybrid.js";
|
|
3
|
+
import { MIN_PASSPHRASE_LENGTH, type PassphraseChecker } from "./passphrase.js";
|
|
2
4
|
import { type PqcKemKeypair, type PqcSignKeypair } from "./crypto/pqc.js";
|
|
3
5
|
export interface Identity {
|
|
4
6
|
alias: string;
|
|
@@ -14,22 +16,31 @@ export interface PublicCard {
|
|
|
14
16
|
pqcSignPublicKey: string;
|
|
15
17
|
pqcKemPublicKey: string;
|
|
16
18
|
}
|
|
17
|
-
export
|
|
19
|
+
export { MIN_PASSPHRASE_LENGTH };
|
|
18
20
|
export interface CreateIdentityOptions {
|
|
19
21
|
storageDir: string;
|
|
20
22
|
passphrase: string;
|
|
21
23
|
alias: string;
|
|
22
24
|
/** Overwrite an existing identity at this storageDir. Defaults to false. */
|
|
23
25
|
overwrite?: boolean;
|
|
26
|
+
/** Optional, runs after the built-in checks below. See PassphraseChecker (ADR 0021). */
|
|
27
|
+
checkPassphrase?: PassphraseChecker;
|
|
24
28
|
}
|
|
25
29
|
/**
|
|
26
30
|
* Generates a new hybrid classical+post-quantum identity (one ECDSA/ECDH-P384
|
|
27
31
|
* keypair, one ML-DSA-87 signing keypair, one ML-KEM-1024 key-exchange
|
|
28
32
|
* keypair) and persists it encrypted-at-rest under `storageDir`.
|
|
29
33
|
*
|
|
30
|
-
*
|
|
31
|
-
*
|
|
32
|
-
*
|
|
34
|
+
* The passphrase is normalized to Unicode NFKC, must be at least
|
|
35
|
+
* MIN_PASSPHRASE_LENGTH characters (counted as code points after
|
|
36
|
+
* normalization), must not be a single repeated character, and must not be
|
|
37
|
+
* on a small blocklist of very common passphrases (ADR 0017). These checks
|
|
38
|
+
* only apply here, not to loadIdentity, so an identity created before they
|
|
39
|
+
* existed can still be loaded with its original passphrase.
|
|
40
|
+
*
|
|
41
|
+
* `options.checkPassphrase`, if supplied, runs after all of the above pass
|
|
42
|
+
* (ADR 0021) — this is where real breach-corpus checking belongs, not in
|
|
43
|
+
* this library's own, deliberately small, blocklist.
|
|
33
44
|
*/
|
|
34
45
|
export declare function createIdentity(options: CreateIdentityOptions): Promise<Identity>;
|
|
35
46
|
export interface LoadIdentityOptions {
|
|
@@ -38,5 +49,59 @@ export interface LoadIdentityOptions {
|
|
|
38
49
|
}
|
|
39
50
|
/** Loads a previously-created identity back from disk. */
|
|
40
51
|
export declare function loadIdentity(options: LoadIdentityOptions): Promise<Identity>;
|
|
52
|
+
/**
|
|
53
|
+
* Supplies an Identity's secret-key material from somewhere other than
|
|
54
|
+
* `identity.enc.json` — an OS keychain, a TPM, an HSM, anything (issue
|
|
55
|
+
* #38/ADR 0024). Scoped to key SOURCING, not operation delegation: once
|
|
56
|
+
* `load()` returns, this library operates on the returned Uint8Arrays
|
|
57
|
+
* directly in its own process memory exactly as it does for a file-loaded
|
|
58
|
+
* Identity. There is no way to make classicalSign/pqcSign/etc. forward the
|
|
59
|
+
* actual signing operation to the device instead — see ADR 0024 for why
|
|
60
|
+
* that's out of scope (and, for the post-quantum half, not achievable with
|
|
61
|
+
* any hardware that exists today regardless).
|
|
62
|
+
*/
|
|
63
|
+
export interface KeyStore {
|
|
64
|
+
load(): Promise<{
|
|
65
|
+
classical: ClassicalKeypair;
|
|
66
|
+
pqcSign: PqcSignKeypair;
|
|
67
|
+
pqcKem: PqcKemKeypair;
|
|
68
|
+
}>;
|
|
69
|
+
}
|
|
70
|
+
export interface LoadIdentityFromKeyStoreOptions {
|
|
71
|
+
alias: string;
|
|
72
|
+
storageDir: string;
|
|
73
|
+
keyStore: KeyStore;
|
|
74
|
+
}
|
|
75
|
+
/**
|
|
76
|
+
* Builds an Identity from a caller-supplied KeyStore instead of a
|
|
77
|
+
* passphrase-encrypted file — `identity.enc.json` and a passphrase are
|
|
78
|
+
* never part of the picture. The returned key material's shape (byte
|
|
79
|
+
* lengths) is validated before trusting it, so a buggy KeyStore fails
|
|
80
|
+
* clearly here rather than with a cryptic error the first time a signing
|
|
81
|
+
* call happens to touch the bad bytes. `storageDir` is still required: the
|
|
82
|
+
* trust store and audit log are a separate concern from where secret keys
|
|
83
|
+
* live, unaffected by this function.
|
|
84
|
+
*/
|
|
85
|
+
export declare function loadIdentityFromKeyStore(options: LoadIdentityFromKeyStoreOptions): Promise<Identity>;
|
|
86
|
+
/**
|
|
87
|
+
* Best-effort wipe of the secret key bytes held by `identity`: overwrites
|
|
88
|
+
* them with zeros so they do not linger in this process's memory after you
|
|
89
|
+
* are done. The identity is unusable afterwards. Limits: JavaScript gives no
|
|
90
|
+
* guarantee about copies the engine or GC already made, and the hex strings
|
|
91
|
+
* produced while saving or loading an identity are immutable and cannot be
|
|
92
|
+
* wiped. This narrows the exposure window; it is not a secure-erase.
|
|
93
|
+
*/
|
|
94
|
+
export declare function zeroizeIdentity(identity: Identity): void;
|
|
41
95
|
/** The shareable public half of an identity — hand this to the other party out of band. */
|
|
42
96
|
export declare function exportPublicIdentity(identity: Identity): PublicCard;
|
|
97
|
+
/**
|
|
98
|
+
* A self-signed, optionally time-limited version of `exportPublicIdentity`'s
|
|
99
|
+
* card (ADR 0023/issue #37): the identity signs its own card, so a peer can
|
|
100
|
+
* verify it wasn't tampered with and — if `options.expiresAt` was set —
|
|
101
|
+
* reject it once expired, enforcing a cryptoperiod the bare card has no way
|
|
102
|
+
* to carry (it has no signature at all). `exportPublicIdentity` itself is
|
|
103
|
+
* unchanged; this is a separate, opt-in function precisely so every
|
|
104
|
+
* existing caller of the bare card is unaffected. Verify and trust a
|
|
105
|
+
* received one with `trustSignedCard`, not `trustParty`.
|
|
106
|
+
*/
|
|
107
|
+
export declare function exportSignedCard(identity: Identity, options?: SignOptions): Promise<Certificate>;
|
package/dist/identity.js
CHANGED
|
@@ -1,9 +1,34 @@
|
|
|
1
1
|
import { bytesToHex, hexToBytes } from "@noble/hashes/utils.js";
|
|
2
2
|
import { existsSync } from "node:fs";
|
|
3
3
|
import { join } from "node:path";
|
|
4
|
-
import { generateClassicalKeypair } from "./crypto/classical.js";
|
|
5
|
-
import {
|
|
6
|
-
import {
|
|
4
|
+
import { generateClassicalKeypair, isValidClassicalKeypairShape } from "./crypto/classical.js";
|
|
5
|
+
import { signCore } from "./crypto/hybrid.js";
|
|
6
|
+
import { decryptFileWithFormat, DecryptionFailedError, encryptToFile, upgradeLegacyFile } from "./fileCrypto.js";
|
|
7
|
+
import { assertAcceptablePassphrase, MIN_PASSPHRASE_LENGTH, normalizePassphrase } from "./passphrase.js";
|
|
8
|
+
import { generatePqcKemKeypair, generatePqcSignKeypair, isValidPqcKemKeypairShape, isValidPqcSignKeypairShape, } from "./crypto/pqc.js";
|
|
9
|
+
function isHex(value) {
|
|
10
|
+
return typeof value === "string" && value.length % 2 === 0 && /^[0-9a-f]*$/i.test(value);
|
|
11
|
+
}
|
|
12
|
+
function isValidKeypairShape(value) {
|
|
13
|
+
if (typeof value !== "object" || value === null)
|
|
14
|
+
return false;
|
|
15
|
+
const v = value;
|
|
16
|
+
return isHex(v.publicKey) && isHex(v.secretKey);
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* Structural shape check for a decrypted identity file's plaintext. In
|
|
20
|
+
* normal operation AEAD authentication already guarantees this shape, so a
|
|
21
|
+
* malformed value here would mean a wrong/corrupted ciphertext slipped past
|
|
22
|
+
* decryption (practically impossible without the passphrase) — this exists
|
|
23
|
+
* so that case fails closed with DecryptionFailedError instead of crashing
|
|
24
|
+
* on a missing/non-hex field (found by fuzzing, issue #42).
|
|
25
|
+
*/
|
|
26
|
+
function isValidSerializedIdentityShape(data) {
|
|
27
|
+
if (typeof data !== "object" || data === null)
|
|
28
|
+
return false;
|
|
29
|
+
const d = data;
|
|
30
|
+
return typeof d.alias === "string" && isValidKeypairShape(d.classical) && isValidKeypairShape(d.pqcSign) && isValidKeypairShape(d.pqcKem);
|
|
31
|
+
}
|
|
7
32
|
function identityFilePath(storageDir) {
|
|
8
33
|
return join(storageDir, "identity.enc.json");
|
|
9
34
|
}
|
|
@@ -42,20 +67,28 @@ function deserialize(storageDir, data) {
|
|
|
42
67
|
},
|
|
43
68
|
};
|
|
44
69
|
}
|
|
45
|
-
export
|
|
70
|
+
export { MIN_PASSPHRASE_LENGTH };
|
|
46
71
|
/**
|
|
47
72
|
* Generates a new hybrid classical+post-quantum identity (one ECDSA/ECDH-P384
|
|
48
73
|
* keypair, one ML-DSA-87 signing keypair, one ML-KEM-1024 key-exchange
|
|
49
74
|
* keypair) and persists it encrypted-at-rest under `storageDir`.
|
|
50
75
|
*
|
|
51
|
-
*
|
|
52
|
-
*
|
|
53
|
-
*
|
|
76
|
+
* The passphrase is normalized to Unicode NFKC, must be at least
|
|
77
|
+
* MIN_PASSPHRASE_LENGTH characters (counted as code points after
|
|
78
|
+
* normalization), must not be a single repeated character, and must not be
|
|
79
|
+
* on a small blocklist of very common passphrases (ADR 0017). These checks
|
|
80
|
+
* only apply here, not to loadIdentity, so an identity created before they
|
|
81
|
+
* existed can still be loaded with its original passphrase.
|
|
82
|
+
*
|
|
83
|
+
* `options.checkPassphrase`, if supplied, runs after all of the above pass
|
|
84
|
+
* (ADR 0021) — this is where real breach-corpus checking belongs, not in
|
|
85
|
+
* this library's own, deliberately small, blocklist.
|
|
54
86
|
*/
|
|
55
87
|
export async function createIdentity(options) {
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
88
|
+
const passphrase = normalizePassphrase(options.passphrase);
|
|
89
|
+
assertAcceptablePassphrase(passphrase);
|
|
90
|
+
if (options.checkPassphrase)
|
|
91
|
+
await options.checkPassphrase(passphrase);
|
|
59
92
|
const filePath = identityFilePath(options.storageDir);
|
|
60
93
|
if (!options.overwrite && existsSync(filePath)) {
|
|
61
94
|
throw new Error(`An identity already exists at ${filePath}. Pass { overwrite: true } to replace it.`);
|
|
@@ -67,14 +100,67 @@ export async function createIdentity(options) {
|
|
|
67
100
|
pqcSign: generatePqcSignKeypair(),
|
|
68
101
|
pqcKem: generatePqcKemKeypair(),
|
|
69
102
|
};
|
|
70
|
-
await encryptToFile(filePath,
|
|
103
|
+
await encryptToFile(filePath, passphrase, serialize(identity));
|
|
71
104
|
return identity;
|
|
72
105
|
}
|
|
73
106
|
/** Loads a previously-created identity back from disk. */
|
|
74
107
|
export async function loadIdentity(options) {
|
|
75
|
-
const
|
|
108
|
+
const filePath = identityFilePath(options.storageDir);
|
|
109
|
+
const normalized = normalizePassphrase(options.passphrase);
|
|
110
|
+
// Normalized form first; the raw form only matters for a file written
|
|
111
|
+
// before normalization existed under a passphrase NFKC would change.
|
|
112
|
+
const candidates = normalized === options.passphrase ? [normalized] : [normalized, options.passphrase];
|
|
113
|
+
const { data, legacy, passphraseIndex } = await decryptFileWithFormat(filePath, candidates);
|
|
114
|
+
if (!isValidSerializedIdentityShape(data))
|
|
115
|
+
throw new DecryptionFailedError();
|
|
116
|
+
if (legacy || passphraseIndex > 0) {
|
|
117
|
+
// Upgrade a pre-versioned file (and/or re-key one made under an
|
|
118
|
+
// un-normalized passphrase) to the current format under the normalized
|
|
119
|
+
// passphrase now that the passphrase is proven correct. Best effort: a
|
|
120
|
+
// read-only directory or a full disk must not stop the identity from
|
|
121
|
+
// loading.
|
|
122
|
+
try {
|
|
123
|
+
await upgradeLegacyFile(filePath, normalized, data);
|
|
124
|
+
}
|
|
125
|
+
catch {
|
|
126
|
+
// The original file is untouched (see upgradeLegacyFile); try again on the next load.
|
|
127
|
+
}
|
|
128
|
+
}
|
|
76
129
|
return deserialize(options.storageDir, data);
|
|
77
130
|
}
|
|
131
|
+
/**
|
|
132
|
+
* Builds an Identity from a caller-supplied KeyStore instead of a
|
|
133
|
+
* passphrase-encrypted file — `identity.enc.json` and a passphrase are
|
|
134
|
+
* never part of the picture. The returned key material's shape (byte
|
|
135
|
+
* lengths) is validated before trusting it, so a buggy KeyStore fails
|
|
136
|
+
* clearly here rather than with a cryptic error the first time a signing
|
|
137
|
+
* call happens to touch the bad bytes. `storageDir` is still required: the
|
|
138
|
+
* trust store and audit log are a separate concern from where secret keys
|
|
139
|
+
* live, unaffected by this function.
|
|
140
|
+
*/
|
|
141
|
+
export async function loadIdentityFromKeyStore(options) {
|
|
142
|
+
const { classical, pqcSign, pqcKem } = await options.keyStore.load();
|
|
143
|
+
if (!isValidClassicalKeypairShape(classical))
|
|
144
|
+
throw new Error("KeyStore returned a classical keypair with the wrong byte length(s) for P-384.");
|
|
145
|
+
if (!isValidPqcSignKeypairShape(pqcSign))
|
|
146
|
+
throw new Error("KeyStore returned a pqcSign keypair with the wrong byte length(s) for ML-DSA-87.");
|
|
147
|
+
if (!isValidPqcKemKeypairShape(pqcKem))
|
|
148
|
+
throw new Error("KeyStore returned a pqcKem keypair with the wrong byte length(s) for ML-KEM-1024.");
|
|
149
|
+
return { alias: options.alias, storageDir: options.storageDir, classical, pqcSign, pqcKem };
|
|
150
|
+
}
|
|
151
|
+
/**
|
|
152
|
+
* Best-effort wipe of the secret key bytes held by `identity`: overwrites
|
|
153
|
+
* them with zeros so they do not linger in this process's memory after you
|
|
154
|
+
* are done. The identity is unusable afterwards. Limits: JavaScript gives no
|
|
155
|
+
* guarantee about copies the engine or GC already made, and the hex strings
|
|
156
|
+
* produced while saving or loading an identity are immutable and cannot be
|
|
157
|
+
* wiped. This narrows the exposure window; it is not a secure-erase.
|
|
158
|
+
*/
|
|
159
|
+
export function zeroizeIdentity(identity) {
|
|
160
|
+
identity.classical.secretKey.fill(0);
|
|
161
|
+
identity.pqcSign.secretKey.fill(0);
|
|
162
|
+
identity.pqcKem.secretKey.fill(0);
|
|
163
|
+
}
|
|
78
164
|
/** The shareable public half of an identity — hand this to the other party out of band. */
|
|
79
165
|
export function exportPublicIdentity(identity) {
|
|
80
166
|
return {
|
|
@@ -84,4 +170,17 @@ export function exportPublicIdentity(identity) {
|
|
|
84
170
|
pqcKemPublicKey: bytesToHex(identity.pqcKem.publicKey),
|
|
85
171
|
};
|
|
86
172
|
}
|
|
173
|
+
/**
|
|
174
|
+
* A self-signed, optionally time-limited version of `exportPublicIdentity`'s
|
|
175
|
+
* card (ADR 0023/issue #37): the identity signs its own card, so a peer can
|
|
176
|
+
* verify it wasn't tampered with and — if `options.expiresAt` was set —
|
|
177
|
+
* reject it once expired, enforcing a cryptoperiod the bare card has no way
|
|
178
|
+
* to carry (it has no signature at all). `exportPublicIdentity` itself is
|
|
179
|
+
* unchanged; this is a separate, opt-in function precisely so every
|
|
180
|
+
* existing caller of the bare card is unaffected. Verify and trust a
|
|
181
|
+
* received one with `trustSignedCard`, not `trustParty`.
|
|
182
|
+
*/
|
|
183
|
+
export async function exportSignedCard(identity, options = {}) {
|
|
184
|
+
return signCore(identity, exportPublicIdentity(identity), options);
|
|
185
|
+
}
|
|
87
186
|
//# 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;AAChE,OAAO,EAAE,UAAU,EAAE,MAAM,SAAS,CAAC;AACrC,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AACjC,OAAO,EAAE,wBAAwB,EAAyB,MAAM,uBAAuB,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, getTrustedPartiesByAlias, getTrustRecordsByAlias, listTrustedParties, listTrustRecords, migrateLegacyTrustStore, migrateTrustStoreToEncrypted, TrustStoreIntegrityError, trustParty, trustSignedCard, untrustFingerprint, untrustParty, } from "./trust.js";
|
|
11
|
+
export type { RevocationDocument, RevocationNotice, RotationDocument, RotationStatement, TrustRecord } 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, getTrustedPartiesByAlias, getTrustRecordsByAlias, listTrustedParties, listTrustRecords, migrateLegacyTrustStore, migrateTrustStoreToEncrypted, TrustStoreIntegrityError, trustParty, trustSignedCard, untrustFingerprint, untrustParty, } 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,wBAAwB,EACxB,sBAAsB,EACtB,kBAAkB,EAClB,gBAAgB,EAChB,uBAAuB,EACvB,4BAA4B,EAC5B,wBAAwB,EACxB,UAAU,EACV,eAAe,EACf,kBAAkB,EAClB,YAAY,GACb,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;
|