@blamejs/core 0.8.43 → 0.8.49
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/CHANGELOG.md +92 -0
- package/README.md +10 -10
- package/index.js +52 -0
- package/lib/a2a.js +159 -34
- package/lib/acme.js +762 -0
- package/lib/ai-pref.js +166 -43
- package/lib/api-key.js +108 -47
- package/lib/api-snapshot.js +157 -40
- package/lib/app-shutdown.js +113 -77
- package/lib/archive.js +337 -40
- package/lib/arg-parser.js +697 -0
- package/lib/asyncapi.js +99 -55
- package/lib/atomic-file.js +465 -104
- package/lib/audit-chain.js +123 -34
- package/lib/audit-daily-review.js +389 -0
- package/lib/audit-sign.js +302 -56
- package/lib/audit-tools.js +412 -63
- package/lib/audit.js +656 -35
- package/lib/auth/jwt-external.js +17 -0
- package/lib/auth/oauth.js +7 -0
- package/lib/auth-bot-challenge.js +505 -0
- package/lib/auth-header.js +92 -25
- package/lib/backup/bundle.js +26 -0
- package/lib/backup/index.js +512 -89
- package/lib/backup/manifest.js +168 -7
- package/lib/break-glass.js +415 -39
- package/lib/budr.js +103 -30
- package/lib/bundler.js +86 -66
- package/lib/cache.js +192 -72
- package/lib/chain-writer.js +65 -40
- package/lib/circuit-breaker.js +56 -33
- package/lib/cli-helpers.js +106 -75
- package/lib/cli.js +6 -30
- package/lib/cloud-events.js +99 -32
- package/lib/cluster-storage.js +162 -37
- package/lib/cluster.js +340 -49
- package/lib/codepoint-class.js +66 -0
- package/lib/compliance.js +424 -24
- package/lib/config-drift.js +111 -46
- package/lib/config.js +94 -40
- package/lib/consent.js +165 -18
- package/lib/constants.js +1 -0
- package/lib/content-credentials.js +153 -48
- package/lib/cookies.js +154 -62
- package/lib/credential-hash.js +133 -61
- package/lib/crypto-field.js +702 -18
- package/lib/crypto-hpke.js +256 -0
- package/lib/crypto.js +744 -22
- package/lib/csv.js +178 -35
- package/lib/daemon.js +456 -0
- package/lib/dark-patterns.js +186 -55
- package/lib/db-query.js +79 -2
- package/lib/db.js +1431 -60
- package/lib/ddl-change-control.js +523 -0
- package/lib/deprecate.js +195 -40
- package/lib/dev.js +82 -39
- package/lib/dora.js +67 -48
- package/lib/dr-runbook.js +368 -0
- package/lib/dsr.js +142 -11
- package/lib/dual-control.js +91 -56
- package/lib/events.js +120 -41
- package/lib/external-db-migrate.js +192 -2
- package/lib/external-db.js +795 -50
- package/lib/fapi2.js +122 -1
- package/lib/fda-21cfr11.js +395 -0
- package/lib/fdx.js +132 -2
- package/lib/file-type.js +87 -0
- package/lib/file-upload.js +93 -0
- package/lib/flag.js +82 -20
- package/lib/forms.js +132 -29
- package/lib/framework-error.js +169 -0
- package/lib/framework-schema.js +163 -35
- package/lib/gate-contract.js +849 -175
- package/lib/graphql-federation.js +68 -7
- package/lib/guard-all.js +172 -55
- package/lib/guard-archive.js +286 -124
- package/lib/guard-auth.js +194 -21
- package/lib/guard-cidr.js +190 -28
- package/lib/guard-csv.js +397 -51
- package/lib/guard-domain.js +213 -91
- package/lib/guard-email.js +236 -29
- package/lib/guard-filename.js +307 -75
- package/lib/guard-graphql.js +263 -30
- package/lib/guard-html.js +310 -116
- package/lib/guard-image.js +243 -30
- package/lib/guard-json.js +260 -54
- package/lib/guard-jsonpath.js +235 -23
- package/lib/guard-jwt.js +284 -30
- package/lib/guard-markdown.js +204 -22
- package/lib/guard-mime.js +190 -26
- package/lib/guard-oauth.js +277 -28
- package/lib/guard-pdf.js +251 -27
- package/lib/guard-regex.js +226 -18
- package/lib/guard-shell.js +229 -26
- package/lib/guard-svg.js +177 -10
- package/lib/guard-template.js +232 -21
- package/lib/guard-time.js +195 -29
- package/lib/guard-uuid.js +189 -30
- package/lib/guard-xml.js +259 -36
- package/lib/guard-yaml.js +241 -44
- package/lib/honeytoken.js +63 -27
- package/lib/html-balance.js +83 -0
- package/lib/http-client.js +486 -59
- package/lib/http-message-signature.js +582 -0
- package/lib/i18n.js +102 -49
- package/lib/iab-mspa.js +112 -32
- package/lib/iab-tcf.js +107 -2
- package/lib/inbox.js +90 -52
- package/lib/keychain.js +865 -0
- package/lib/legal-hold.js +374 -0
- package/lib/local-db-thin.js +320 -0
- package/lib/log-stream.js +281 -51
- package/lib/log.js +184 -86
- package/lib/mail-bounce.js +107 -62
- package/lib/mail.js +295 -58
- package/lib/mcp.js +108 -27
- package/lib/metrics.js +98 -89
- package/lib/middleware/age-gate.js +36 -0
- package/lib/middleware/ai-act-disclosure.js +37 -0
- package/lib/middleware/api-encrypt.js +45 -0
- package/lib/middleware/assetlinks.js +40 -0
- package/lib/middleware/asyncapi-serve.js +35 -0
- package/lib/middleware/attach-user.js +40 -0
- package/lib/middleware/bearer-auth.js +40 -0
- package/lib/middleware/body-parser.js +230 -0
- package/lib/middleware/bot-disclose.js +34 -0
- package/lib/middleware/bot-guard.js +39 -0
- package/lib/middleware/compression.js +37 -0
- package/lib/middleware/cookies.js +32 -0
- package/lib/middleware/cors.js +40 -0
- package/lib/middleware/csp-nonce.js +40 -0
- package/lib/middleware/csp-report.js +34 -0
- package/lib/middleware/csrf-protect.js +43 -0
- package/lib/middleware/daily-byte-quota.js +53 -85
- package/lib/middleware/db-role-for.js +40 -0
- package/lib/middleware/dpop.js +40 -0
- package/lib/middleware/error-handler.js +37 -14
- package/lib/middleware/fetch-metadata.js +39 -0
- package/lib/middleware/flag-context.js +34 -0
- package/lib/middleware/gpc.js +33 -0
- package/lib/middleware/headers.js +35 -0
- package/lib/middleware/health.js +46 -0
- package/lib/middleware/host-allowlist.js +30 -0
- package/lib/middleware/network-allowlist.js +38 -0
- package/lib/middleware/openapi-serve.js +34 -0
- package/lib/middleware/rate-limit.js +160 -18
- package/lib/middleware/request-id.js +36 -18
- package/lib/middleware/request-log.js +37 -0
- package/lib/middleware/require-aal.js +29 -0
- package/lib/middleware/require-auth.js +32 -0
- package/lib/middleware/require-bound-key.js +41 -0
- package/lib/middleware/require-content-type.js +32 -0
- package/lib/middleware/require-methods.js +27 -0
- package/lib/middleware/require-mtls.js +33 -0
- package/lib/middleware/require-step-up.js +37 -0
- package/lib/middleware/security-headers.js +44 -0
- package/lib/middleware/security-txt.js +38 -0
- package/lib/middleware/span-http-server.js +37 -0
- package/lib/middleware/sse.js +36 -0
- package/lib/middleware/trace-log-correlation.js +33 -0
- package/lib/middleware/trace-propagate.js +32 -0
- package/lib/middleware/tus-upload.js +90 -0
- package/lib/middleware/web-app-manifest.js +53 -0
- package/lib/mtls-ca.js +100 -70
- package/lib/network-byte-quota.js +308 -0
- package/lib/network-heartbeat.js +135 -0
- package/lib/network-tls.js +534 -4
- package/lib/network.js +103 -0
- package/lib/notify.js +114 -43
- package/lib/ntp-check.js +192 -51
- package/lib/observability.js +145 -47
- package/lib/openapi.js +90 -44
- package/lib/outbox.js +99 -1
- package/lib/pagination.js +168 -86
- package/lib/parsers/index.js +16 -5
- package/lib/permissions.js +93 -40
- package/lib/pqc-agent.js +84 -8
- package/lib/pqc-software.js +94 -60
- package/lib/process-spawn.js +95 -21
- package/lib/pubsub.js +96 -66
- package/lib/queue.js +375 -54
- package/lib/redact.js +793 -21
- package/lib/render.js +139 -47
- package/lib/request-helpers.js +485 -121
- package/lib/restore-bundle.js +142 -39
- package/lib/restore-rollback.js +136 -45
- package/lib/retention.js +178 -50
- package/lib/retry.js +116 -33
- package/lib/router.js +475 -23
- package/lib/safe-async.js +543 -94
- package/lib/safe-buffer.js +337 -41
- package/lib/safe-json.js +467 -62
- package/lib/safe-jsonpath.js +285 -0
- package/lib/safe-schema.js +631 -87
- package/lib/safe-sql.js +221 -59
- package/lib/safe-url.js +278 -46
- package/lib/sandbox-worker.js +135 -0
- package/lib/sandbox.js +358 -0
- package/lib/scheduler.js +135 -70
- package/lib/self-update.js +647 -0
- package/lib/session-device-binding.js +431 -0
- package/lib/session.js +259 -49
- package/lib/slug.js +138 -26
- package/lib/ssrf-guard.js +316 -56
- package/lib/storage.js +433 -70
- package/lib/subject.js +405 -23
- package/lib/template.js +148 -8
- package/lib/tenant-quota.js +545 -0
- package/lib/testing.js +440 -53
- package/lib/time.js +291 -23
- package/lib/tls-exporter.js +239 -0
- package/lib/tracing.js +90 -74
- package/lib/uuid.js +97 -22
- package/lib/vault/index.js +284 -22
- package/lib/vault/seal-pem-file.js +66 -0
- package/lib/watcher.js +368 -0
- package/lib/webhook.js +196 -63
- package/lib/websocket.js +393 -68
- package/lib/wiki-concepts.js +338 -0
- package/lib/worker-pool.js +464 -0
- package/package.json +3 -3
- package/sbom.cyclonedx.json +7 -7
package/lib/audit-sign.js
CHANGED
|
@@ -1,60 +1,62 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
/**
|
|
3
|
-
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
11
|
-
*
|
|
12
|
-
*
|
|
13
|
-
*
|
|
14
|
-
*
|
|
15
|
-
*
|
|
16
|
-
*
|
|
17
|
-
*
|
|
18
|
-
*
|
|
19
|
-
*
|
|
20
|
-
*
|
|
21
|
-
*
|
|
22
|
-
*
|
|
23
|
-
*
|
|
24
|
-
*
|
|
25
|
-
*
|
|
26
|
-
*
|
|
27
|
-
*
|
|
28
|
-
*
|
|
29
|
-
*
|
|
30
|
-
*
|
|
31
|
-
*
|
|
32
|
-
*
|
|
33
|
-
*
|
|
34
|
-
*
|
|
35
|
-
*
|
|
36
|
-
*
|
|
37
|
-
*
|
|
38
|
-
*
|
|
39
|
-
*
|
|
40
|
-
*
|
|
41
|
-
*
|
|
42
|
-
*
|
|
43
|
-
*
|
|
44
|
-
*
|
|
45
|
-
*
|
|
46
|
-
*
|
|
47
|
-
*
|
|
48
|
-
*
|
|
49
|
-
*
|
|
50
|
-
*
|
|
51
|
-
*
|
|
52
|
-
*
|
|
53
|
-
*
|
|
54
|
-
*
|
|
55
|
-
*
|
|
56
|
-
*
|
|
57
|
-
*
|
|
3
|
+
* @module b.auditSign
|
|
4
|
+
* @nav Crypto
|
|
5
|
+
* @title Audit Signing
|
|
6
|
+
*
|
|
7
|
+
* @intro
|
|
8
|
+
* SLH-DSA-SHAKE-256f post-quantum signature for audit-chain
|
|
9
|
+
* checkpoints. Wrapped vs plaintext on-disk modes, key derivation
|
|
10
|
+
* from an operator passphrase, periodic checkpoint sign / verify,
|
|
11
|
+
* multiple-key support so a key rotation doesn't strand history.
|
|
12
|
+
*
|
|
13
|
+
* Algorithm: SLH-DSA-SHAKE-256f (FIPS 205) by default. ML-DSA-87
|
|
14
|
+
* (FIPS 204 Category 5) and ML-DSA-65 (FIPS 204 Category 3, ~192-bit
|
|
15
|
+
* symmetric security, smaller signatures + faster verify than 87)
|
|
16
|
+
* ship as opt-in alternatives for throughput-sensitive deployments.
|
|
17
|
+
* SLH-DSA-SHAKE-256f is hash-only — its security depends solely on
|
|
18
|
+
* the underlying hash function, with no lattice / module-hardness
|
|
19
|
+
* assumptions — and matches the framework's SHAKE256 KDF + SHA3-512
|
|
20
|
+
* hash family. Audit checkpoints are long-lived integrity
|
|
21
|
+
* attestations (must verify for the data retention period — years
|
|
22
|
+
* for HIPAA / SOX), so the conservative-PQC posture carries more
|
|
23
|
+
* weight here than the smaller ML-DSA signatures (~5 KB at 87,
|
|
24
|
+
* ~3.3 KB at 65) and faster sign (~0.6 ms vs 76 ms).
|
|
25
|
+
*
|
|
26
|
+
* The algorithm is recorded in the on-disk key file's `algorithm`
|
|
27
|
+
* field. The framework refuses to load a key file that lacks it.
|
|
28
|
+
* Operators upgrading the algorithm rotate their audit-signing key
|
|
29
|
+
* via `b.auditSign.rotateSigningKey({ algorithm })`.
|
|
30
|
+
*
|
|
31
|
+
* Design:
|
|
32
|
+
* - Different keypair from the vault encryption keys. Compromise
|
|
33
|
+
* of the vault DOES NOT let an attacker forge audit checkpoints.
|
|
34
|
+
* - Stored at <dataDir>/audit-sign.key.sealed (default 'wrapped'
|
|
35
|
+
* mode) or <dataDir>/audit-sign.key (opt-out 'plaintext' mode
|
|
36
|
+
* with warning).
|
|
37
|
+
* - Wrapped under its OWN passphrase, sourced via:
|
|
38
|
+
* BLAMEJS_AUDIT_SIGNING_PASSPHRASE (env)
|
|
39
|
+
* BLAMEJS_AUDIT_SIGNING_PASSPHRASE_FILE (file)
|
|
40
|
+
* BLAMEJS_AUDIT_SIGNING_PASSPHRASE_SOURCE (auto|env|file|stdin)
|
|
41
|
+
* Intentionally distinct from BLAMEJS_VAULT_PASSPHRASE so
|
|
42
|
+
* operator-error reuse of the same passphrase is explicit.
|
|
43
|
+
* - First-run generates the keypair automatically.
|
|
44
|
+
*
|
|
45
|
+
* Threat model:
|
|
46
|
+
* - Vault key compromised + DB write access: attacker can read
|
|
47
|
+
* sealed values + rewrite audit_log rows + recompute per-row
|
|
48
|
+
* chain hashes. They CANNOT forge new audit_checkpoint rows —
|
|
49
|
+
* each checkpoint requires the audit-signing private key.
|
|
50
|
+
* - Audit signing key compromised: attacker can forge new
|
|
51
|
+
* checkpoints but cannot read sealed values. Existing
|
|
52
|
+
* checkpoints still anchor history that pre-dated the compromise
|
|
53
|
+
* (operator should rotate signing key on detection).
|
|
54
|
+
* - Both compromised: framework cannot defend against this — the
|
|
55
|
+
* operator's physical / administrative controls (HIPAA §164.310,
|
|
56
|
+
* GDPR Art. 32(1)(d)) cover this case.
|
|
57
|
+
*
|
|
58
|
+
* @card
|
|
59
|
+
* SLH-DSA-SHAKE-256f post-quantum signature for audit-chain checkpoints.
|
|
58
60
|
*/
|
|
59
61
|
var fs = require("fs");
|
|
60
62
|
var path = require("path");
|
|
@@ -82,7 +84,13 @@ var _err = AuditSignError.factory;
|
|
|
82
84
|
// that lacks it. The legacy implicit-default-to-ml-dsa-87 fallback was
|
|
83
85
|
// removed as part of the pre-v1 compat-shim sweep.
|
|
84
86
|
var DEFAULT_SIGNING_ALG = "slh-dsa-shake-256f";
|
|
85
|
-
|
|
87
|
+
// ml-dsa-65 (FIPS 204 Category 3, ~192-bit symmetric security) is opt-
|
|
88
|
+
// in alongside ml-dsa-87 — same code path (both auto-detected by
|
|
89
|
+
// node:crypto from the PEM), smaller signatures (~3.3 KB vs ~5 KB at
|
|
90
|
+
// 87 / ~29.5 KB at SLH-DSA-SHAKE-256f), faster verify. Operators with
|
|
91
|
+
// throughput-sensitive checkpoint streams or audit-feed shippers
|
|
92
|
+
// elect ml-dsa-65 explicitly via opts.algorithm.
|
|
93
|
+
var SUPPORTED_SIGNING_ALGS = Object.freeze(["slh-dsa-shake-256f", "ml-dsa-87", "ml-dsa-65"]);
|
|
86
94
|
|
|
87
95
|
var SIGNING_KEY_SCHEMA = {
|
|
88
96
|
type: "object",
|
|
@@ -136,6 +144,37 @@ function _getPassphrase(promptText) {
|
|
|
136
144
|
// files take their algorithm from the file itself, ignoring this.
|
|
137
145
|
var pendingNewKeyAlg = null;
|
|
138
146
|
|
|
147
|
+
/**
|
|
148
|
+
* @primitive b.auditSign.init
|
|
149
|
+
* @signature b.auditSign.init(opts)
|
|
150
|
+
* @since 0.1.0
|
|
151
|
+
* @status stable
|
|
152
|
+
* @compliance hipaa, pci-dss, gdpr, soc2, sox-404
|
|
153
|
+
* @related b.auditSign.sign, b.auditSign.verify, b.auditSign.rotateSigningKey
|
|
154
|
+
*
|
|
155
|
+
* Boot the audit-signing keypair. Called once during `b.db.init()`;
|
|
156
|
+
* later calls are no-ops. First run generates a fresh PQC keypair and
|
|
157
|
+
* either seals it under an operator passphrase ('wrapped' mode,
|
|
158
|
+
* default) or writes it plaintext at 0600 ('plaintext' mode, opt-out
|
|
159
|
+
* with stderr warning). Subsequent boots load the existing key file
|
|
160
|
+
* and refuse if both wrapped + plaintext copies exist on disk
|
|
161
|
+
* (KEY_FILE_CONFLICT) or the on-disk mode disagrees with `opts.mode`
|
|
162
|
+
* (MODE_MISMATCH).
|
|
163
|
+
*
|
|
164
|
+
* @opts
|
|
165
|
+
* dataDir: string, // required — directory holding the key file
|
|
166
|
+
* mode: "wrapped" | "plaintext", // default "wrapped"
|
|
167
|
+
* algorithm: "slh-dsa-shake-256f" | "ml-dsa-87" | "ml-dsa-65" // default "slh-dsa-shake-256f"; only consulted when generating a fresh key
|
|
168
|
+
*
|
|
169
|
+
* @example
|
|
170
|
+
* await b.auditSign.init({
|
|
171
|
+
* dataDir: "/var/lib/blamejs/data",
|
|
172
|
+
* mode: "wrapped",
|
|
173
|
+
* algorithm: "slh-dsa-shake-256f",
|
|
174
|
+
* });
|
|
175
|
+
* b.auditSign.getMode(); // → "wrapped"
|
|
176
|
+
* b.auditSign.getAlgorithm(); // → "slh-dsa-shake-256f"
|
|
177
|
+
*/
|
|
139
178
|
async function init(opts) {
|
|
140
179
|
if (initialized) return;
|
|
141
180
|
if (!opts || !opts.dataDir) {
|
|
@@ -307,12 +346,66 @@ function _requireInit() {
|
|
|
307
346
|
}
|
|
308
347
|
}
|
|
309
348
|
|
|
349
|
+
/**
|
|
350
|
+
* @primitive b.auditSign.sign
|
|
351
|
+
* @signature b.auditSign.sign(payload)
|
|
352
|
+
* @since 0.1.0
|
|
353
|
+
* @status stable
|
|
354
|
+
* @compliance hipaa, pci-dss, gdpr, soc2, sox-404
|
|
355
|
+
* @related b.auditSign.verify, b.audit.checkpoint
|
|
356
|
+
*
|
|
357
|
+
* Sign a payload (Buffer or string) with the in-memory PQC private
|
|
358
|
+
* key. Returns the raw signature bytes as a Buffer. Throws if `init()`
|
|
359
|
+
* has not been awaited. Used by `b.audit.checkpoint()` to anchor the
|
|
360
|
+
* chain tip; operators normally don't call it directly.
|
|
361
|
+
*
|
|
362
|
+
* @example
|
|
363
|
+
* await b.auditSign.init({ dataDir: "/var/lib/blamejs/data" });
|
|
364
|
+
*
|
|
365
|
+
* // Sign a chain checkpoint payload (the audit module passes the
|
|
366
|
+
* // chain tip's row hash + monotonic counter as canonical bytes).
|
|
367
|
+
* var tip = { rowHash: "9f4e2c3a", counter: 1042 };
|
|
368
|
+
* var payload = Buffer.from(JSON.stringify(tip), "utf8");
|
|
369
|
+
* var signature = b.auditSign.sign(payload);
|
|
370
|
+
* // → <Buffer ...> roughly 29.5 KB for SLH-DSA-SHAKE-256f
|
|
371
|
+
*/
|
|
310
372
|
function sign(payload) {
|
|
311
373
|
_requireInit();
|
|
312
374
|
var buf = Buffer.isBuffer(payload) ? payload : Buffer.from(String(payload), "utf8");
|
|
313
375
|
return nodeCrypto.sign(null, buf, keys.privateKey);
|
|
314
376
|
}
|
|
315
377
|
|
|
378
|
+
/**
|
|
379
|
+
* @primitive b.auditSign.verify
|
|
380
|
+
* @signature b.auditSign.verify(payload, signature, publicKeyPem)
|
|
381
|
+
* @since 0.1.0
|
|
382
|
+
* @status stable
|
|
383
|
+
* @compliance hipaa, pci-dss, gdpr, soc2, sox-404
|
|
384
|
+
* @related b.auditSign.sign, b.audit.verifyCheckpoints
|
|
385
|
+
*
|
|
386
|
+
* Verify a signature against the supplied (or current) public key.
|
|
387
|
+
* Returns `true` when the signature is valid, `false` otherwise; never
|
|
388
|
+
* throws on a forgery — callers branch on the boolean. The third
|
|
389
|
+
* argument lets verification use a HISTORICAL key (read from
|
|
390
|
+
* `audit-sign.key.sealed.history-*`) so a checkpoint signed years
|
|
391
|
+
* earlier still verifies after rotation.
|
|
392
|
+
*
|
|
393
|
+
* @example
|
|
394
|
+
* await b.auditSign.init({ dataDir: "/var/lib/blamejs/data" });
|
|
395
|
+
*
|
|
396
|
+
* // Re-walk every checkpoint to confirm chain integrity.
|
|
397
|
+
* var tip = { rowHash: "9f4e2c3a", counter: 1042 };
|
|
398
|
+
* var payload = Buffer.from(JSON.stringify(tip), "utf8");
|
|
399
|
+
* var signature = b.auditSign.sign(payload);
|
|
400
|
+
*
|
|
401
|
+
* var ok = b.auditSign.verify(payload, signature);
|
|
402
|
+
* // → true
|
|
403
|
+
*
|
|
404
|
+
* // A historical checkpoint signed under an old key:
|
|
405
|
+
* var oldPubPem = "-----BEGIN PUBLIC KEY-----\nMII...\n-----END PUBLIC KEY-----";
|
|
406
|
+
* b.auditSign.verify(payload, signature, oldPubPem);
|
|
407
|
+
* // → true (when payload + signature were produced under that key)
|
|
408
|
+
*/
|
|
316
409
|
function verify(payload, signature, publicKeyPem) {
|
|
317
410
|
_requireInit();
|
|
318
411
|
var buf = Buffer.isBuffer(payload) ? payload : Buffer.from(String(payload), "utf8");
|
|
@@ -321,9 +414,80 @@ function verify(payload, signature, publicKeyPem) {
|
|
|
321
414
|
return nodeCrypto.verify(null, buf, pub, sigBuf);
|
|
322
415
|
}
|
|
323
416
|
|
|
417
|
+
/**
|
|
418
|
+
* @primitive b.auditSign.getPublicKey
|
|
419
|
+
* @signature b.auditSign.getPublicKey()
|
|
420
|
+
* @since 0.1.0
|
|
421
|
+
* @status stable
|
|
422
|
+
* @related b.auditSign.getPublicKeyFingerprint, b.auditSign.verify
|
|
423
|
+
*
|
|
424
|
+
* Return the in-memory public key as a SPKI PEM string. Operators
|
|
425
|
+
* publish this so external auditors can verify checkpoint signatures
|
|
426
|
+
* without holding any private material.
|
|
427
|
+
*
|
|
428
|
+
* @example
|
|
429
|
+
* await b.auditSign.init({ dataDir: "/var/lib/blamejs/data" });
|
|
430
|
+
* var pem = b.auditSign.getPublicKey();
|
|
431
|
+
* // → "-----BEGIN PUBLIC KEY-----\nMII...\n-----END PUBLIC KEY-----\n"
|
|
432
|
+
*/
|
|
324
433
|
function getPublicKey() { _requireInit(); return keys.publicKey; }
|
|
434
|
+
|
|
435
|
+
/**
|
|
436
|
+
* @primitive b.auditSign.getPublicKeyFingerprint
|
|
437
|
+
* @signature b.auditSign.getPublicKeyFingerprint()
|
|
438
|
+
* @since 0.1.0
|
|
439
|
+
* @status stable
|
|
440
|
+
* @related b.auditSign.getPublicKey, b.auditSign.rotateSigningKey
|
|
441
|
+
*
|
|
442
|
+
* Return the SHA3-512 fingerprint of the public key as a lowercase
|
|
443
|
+
* hex string. Stable across boots for the same keypair; a different
|
|
444
|
+
* fingerprint after `rotateSigningKey()` is the signal that the
|
|
445
|
+
* rotation actually changed material.
|
|
446
|
+
*
|
|
447
|
+
* @example
|
|
448
|
+
* await b.auditSign.init({ dataDir: "/var/lib/blamejs/data" });
|
|
449
|
+
* var fp = b.auditSign.getPublicKeyFingerprint();
|
|
450
|
+
* // → "9f4e2c3a..." (128 hex chars, SHA3-512)
|
|
451
|
+
*/
|
|
325
452
|
function getPublicKeyFingerprint() { _requireInit(); return keys.fingerprint; }
|
|
453
|
+
|
|
454
|
+
/**
|
|
455
|
+
* @primitive b.auditSign.getMode
|
|
456
|
+
* @signature b.auditSign.getMode()
|
|
457
|
+
* @since 0.1.0
|
|
458
|
+
* @status stable
|
|
459
|
+
* @related b.auditSign.init
|
|
460
|
+
*
|
|
461
|
+
* Return the on-disk storage mode chosen at `init()` — `"wrapped"`
|
|
462
|
+
* (passphrase-sealed, default) or `"plaintext"` (0600 file, opt-out).
|
|
463
|
+
* Returns `null` before `init()` runs.
|
|
464
|
+
*
|
|
465
|
+
* @example
|
|
466
|
+
* await b.auditSign.init({ dataDir: "/var/lib/blamejs/data" });
|
|
467
|
+
* b.auditSign.getMode();
|
|
468
|
+
* // → "wrapped"
|
|
469
|
+
*/
|
|
326
470
|
function getMode() { return currentMode; }
|
|
471
|
+
|
|
472
|
+
/**
|
|
473
|
+
* @primitive b.auditSign.getAlgorithm
|
|
474
|
+
* @signature b.auditSign.getAlgorithm()
|
|
475
|
+
* @since 0.7.0
|
|
476
|
+
* @status stable
|
|
477
|
+
* @related b.auditSign.init, b.auditSign.rotateSigningKey
|
|
478
|
+
*
|
|
479
|
+
* Return the algorithm of the currently-loaded keypair —
|
|
480
|
+
* `"slh-dsa-shake-256f"`, `"ml-dsa-87"`, or `"ml-dsa-65"`. Read from
|
|
481
|
+
* the on-disk key file, not from the operator's `init()` opts (the
|
|
482
|
+
* file's algorithm wins so a key generated under one alg keeps
|
|
483
|
+
* verifying under that alg even when a later boot passes a different
|
|
484
|
+
* default).
|
|
485
|
+
*
|
|
486
|
+
* @example
|
|
487
|
+
* await b.auditSign.init({ dataDir: "/var/lib/blamejs/data" });
|
|
488
|
+
* b.auditSign.getAlgorithm();
|
|
489
|
+
* // → "slh-dsa-shake-256f"
|
|
490
|
+
*/
|
|
327
491
|
function getAlgorithm() { _requireInit(); return keys.algorithm; }
|
|
328
492
|
|
|
329
493
|
// Re-sign every payload in the operator-supplied iterable using the
|
|
@@ -339,6 +503,45 @@ function getAlgorithm() { _requireInit(); return keys.algorithm; }
|
|
|
339
503
|
// internal key-history. The caller persists the new signature in
|
|
340
504
|
// place — this primitive returns the new bytes without touching
|
|
341
505
|
// storage.
|
|
506
|
+
/**
|
|
507
|
+
* @primitive b.auditSign.reSignAll
|
|
508
|
+
* @signature b.auditSign.reSignAll(iter, opts)
|
|
509
|
+
* @since 0.7.0
|
|
510
|
+
* @status stable
|
|
511
|
+
* @compliance hipaa, pci-dss, gdpr, soc2, sox-404
|
|
512
|
+
* @related b.auditSign.rotateSigningKey, b.auditSign.sign
|
|
513
|
+
*
|
|
514
|
+
* Re-sign every payload in `iter` under the CURRENT in-memory key.
|
|
515
|
+
* Each iteration yields `{ id, payload, signature, oldPublicKeyPem }`
|
|
516
|
+
* — payloads whose old signature fails to verify under
|
|
517
|
+
* `oldPublicKeyPem` are skipped (already tampered or never signed
|
|
518
|
+
* under that key) rather than aborting the whole walk. Returns
|
|
519
|
+
* `{ reSigned, skipped, errors }`. The caller (typically the audit
|
|
520
|
+
* module's checkpoint store) persists the new bytes; this primitive
|
|
521
|
+
* does not touch storage.
|
|
522
|
+
*
|
|
523
|
+
* @opts
|
|
524
|
+
* onProgress: function (entry), // called with { id, newSignature } per re-sign; errors in the hook are drop-silent
|
|
525
|
+
*
|
|
526
|
+
* @example
|
|
527
|
+
* await b.auditSign.init({ dataDir: "/var/lib/blamejs/data" });
|
|
528
|
+
*
|
|
529
|
+
* async function* allCheckpoints() {
|
|
530
|
+
* yield {
|
|
531
|
+
* id: 1,
|
|
532
|
+
* payload: Buffer.from("{\"counter\":1}", "utf8"),
|
|
533
|
+
* signature: Buffer.from("00", "hex"),
|
|
534
|
+
* oldPublicKeyPem: b.auditSign.getPublicKey(),
|
|
535
|
+
* };
|
|
536
|
+
* }
|
|
537
|
+
*
|
|
538
|
+
* var summary = await b.auditSign.reSignAll(allCheckpoints(), {
|
|
539
|
+
* onProgress: function (entry) {
|
|
540
|
+
* // persist entry.newSignature against entry.id atomically
|
|
541
|
+
* },
|
|
542
|
+
* });
|
|
543
|
+
* // → { reSigned: 1, skipped: 0, errors: 0 }
|
|
544
|
+
*/
|
|
342
545
|
async function reSignAll(iter, opts) {
|
|
343
546
|
_requireInit();
|
|
344
547
|
opts = opts || {};
|
|
@@ -382,6 +585,49 @@ async function reSignAll(iter, opts) {
|
|
|
382
585
|
// 2. Call rotateSigningKey() — gets new keys live
|
|
383
586
|
// 3. Walk checkpoints through reSignAll()
|
|
384
587
|
// 4. Write back the new signatures atomically
|
|
588
|
+
/**
|
|
589
|
+
* @primitive b.auditSign.rotateSigningKey
|
|
590
|
+
* @signature b.auditSign.rotateSigningKey(opts)
|
|
591
|
+
* @since 0.7.0
|
|
592
|
+
* @status stable
|
|
593
|
+
* @compliance hipaa, pci-dss, gdpr, soc2, sox-404
|
|
594
|
+
* @related b.auditSign.reSignAll, b.auditSign.init
|
|
595
|
+
*
|
|
596
|
+
* Generate (or accept) a fresh keypair, copy the existing sealed /
|
|
597
|
+
* plaintext key file to a timestamped `*.history-<iso>-<fp>` path, and
|
|
598
|
+
* persist the new key to disk through the same wrap path as boot. The
|
|
599
|
+
* in-memory swap happens last so a write failure leaves the framework
|
|
600
|
+
* with the OLD key still in memory + on disk. Refuses (`ROTATE_NOOP`)
|
|
601
|
+
* when the new keypair has the same fingerprint as the current one.
|
|
602
|
+
* Operators rotating the audit-signing key in production typically:
|
|
603
|
+
* read existing checkpoints, call `rotateSigningKey()`, walk the
|
|
604
|
+
* checkpoints through `reSignAll()`, then write the new signatures
|
|
605
|
+
* back atomically. Returns metadata about the rotation including the
|
|
606
|
+
* `historyPath` so external tools can verify pre-rotation checkpoints
|
|
607
|
+
* later.
|
|
608
|
+
*
|
|
609
|
+
* @opts
|
|
610
|
+
* privateKeyPem: string, // BYO keypair (pair with publicKeyPem); when omitted the framework generates fresh material
|
|
611
|
+
* publicKeyPem: string,
|
|
612
|
+
* algorithm: "slh-dsa-shake-256f" | "ml-dsa-87" | "ml-dsa-65" // defaults to the current keypair's algorithm
|
|
613
|
+
*
|
|
614
|
+
* @example
|
|
615
|
+
* await b.auditSign.init({ dataDir: "/var/lib/blamejs/data" });
|
|
616
|
+
*
|
|
617
|
+
* // Annual rotation — same algorithm, framework-generated material:
|
|
618
|
+
* var result = await b.auditSign.rotateSigningKey();
|
|
619
|
+
* // → {
|
|
620
|
+
* // previousFingerprint: "9f4e...",
|
|
621
|
+
* // newFingerprint: "3a7c...",
|
|
622
|
+
* // algorithm: "slh-dsa-shake-256f",
|
|
623
|
+
* // rotatedAt: "2026-05-09T12:00:00.000Z",
|
|
624
|
+
* // historyPath: "/var/lib/blamejs/data/audit-sign.key.sealed.history-2026-05-09T12-00-00-000Z-9f4e2c3aabbccdd0",
|
|
625
|
+
* // ...
|
|
626
|
+
* // }
|
|
627
|
+
*
|
|
628
|
+
* // Algorithm upgrade — same call, with explicit `algorithm`:
|
|
629
|
+
* await b.auditSign.rotateSigningKey({ algorithm: "ml-dsa-65" });
|
|
630
|
+
*/
|
|
385
631
|
async function rotateSigningKey(rotOpts) {
|
|
386
632
|
_requireInit();
|
|
387
633
|
rotOpts = rotOpts || {};
|