@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/crypto.js
CHANGED
|
@@ -1,32 +1,71 @@
|
|
|
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
|
-
*
|
|
3
|
+
* @module b.crypto
|
|
4
|
+
* @featured true
|
|
5
|
+
* @nav Crypto
|
|
6
|
+
* @title Crypto
|
|
7
|
+
*
|
|
8
|
+
* @intro
|
|
9
|
+
* The framework's PQC-first cryptography surface. Every default is
|
|
10
|
+
* post-quantum-aware: ML-KEM-1024 + ECDH P-384 hybrid for key
|
|
11
|
+
* encapsulation (FIPS 203 + classical defense-in-depth), XChaCha20-
|
|
12
|
+
* Poly1305 for authenticated symmetric encryption (24-byte nonce —
|
|
13
|
+
* no nonce-reuse risk under high volume), SHAKE256 as the KDF
|
|
14
|
+
* (FIPS 202 XOF — arbitrary output length), SHA3-512 for hashing,
|
|
15
|
+
* HMAC-SHA3-512 for keyed integrity, and ML-DSA-87 / SLH-DSA-SHAKE-
|
|
16
|
+
* 256f for signatures (auto-detected from the key PEM). Argon2id
|
|
17
|
+
* passphrase stretching lives in `b.vaultWrap`, not here.
|
|
18
|
+
*
|
|
19
|
+
* Envelope wire format (length-prefixed, self-describing):
|
|
20
|
+
*
|
|
21
|
+
* byte 0 : ENVELOPE_MAGIC
|
|
22
|
+
* byte 1 : KEM ID (ML_KEM_1024 / ML_KEM_1024_P384 / ML_KEM_768_X25519)
|
|
23
|
+
* byte 2 : CIPHER ID (XCHACHA20_POLY1305)
|
|
24
|
+
* byte 3 : KDF ID (SHAKE256)
|
|
25
|
+
* ... : KEM ciphertext, ephemeral ECDH pubkey, nonce, AEAD ciphertext
|
|
26
|
+
*
|
|
27
|
+
* The four-byte header is bound as AEAD AAD so an algorithm-
|
|
28
|
+
* substitution attack (a tampered byte-1 KEM ID, byte-2 cipher ID,
|
|
29
|
+
* etc.) fails Poly1305 verification. Old envelopes decrypt under the
|
|
30
|
+
* IDs written into their header; new writes use the active suite.
|
|
31
|
+
* The KDF additionally absorbs a NIST SP 800-56C r2 §4.1 FixedInfo
|
|
32
|
+
* suite-binding label so a key derived under one suite is not
|
|
33
|
+
* silently usable under another.
|
|
34
|
+
*
|
|
35
|
+
* Three KEM hybrids ship: ML-KEM-1024 KEM-only (legacy single-
|
|
36
|
+
* component), ML-KEM-1024 + ECDH P-384 (framework default), and
|
|
37
|
+
* ML-KEM-768 + X25519 (IETF / Cloudflare / Chrome TLS 1.3 codepoint
|
|
38
|
+
* 0x11EC — smaller payload, wider browser interop).
|
|
39
|
+
*
|
|
40
|
+
* SHA-1 / SHA-256 / AES-GCM / classical-only ECDH are intentionally
|
|
41
|
+
* absent from the public surface. Operators who genuinely need them
|
|
42
|
+
* call `node:crypto` directly so the choice surfaces in their code.
|
|
43
|
+
*
|
|
44
|
+
* @card
|
|
45
|
+
* The framework's PQC-first cryptography surface.
|
|
25
46
|
*/
|
|
26
47
|
var nodeCrypto = require("crypto");
|
|
48
|
+
var nodeFs = require("fs");
|
|
49
|
+
var { pipeline } = require("stream/promises");
|
|
27
50
|
var { xchacha20poly1305 } = require("./vendor/noble-ciphers.cjs");
|
|
28
51
|
var C = require("./constants");
|
|
29
52
|
|
|
53
|
+
// Streaming-hash algorithm allowlist. Mirrors the framework's PQC-
|
|
54
|
+
// first crypto policy: SHA3 / SHAKE family is the default surface;
|
|
55
|
+
// SHA-512 is permitted for legitimate interop (signing artifacts that
|
|
56
|
+
// downstream verifiers compute as SHA-512). MD5 / SHA-1 / SHA-256 are
|
|
57
|
+
// not on the list — operators who genuinely need them call
|
|
58
|
+
// node:crypto directly so the choice surfaces in their code.
|
|
59
|
+
var STREAM_HASH_ALGORITHMS = Object.freeze({
|
|
60
|
+
"sha3-256": { algorithm: "sha3-256", needsOutputLength: false },
|
|
61
|
+
"sha3-384": { algorithm: "sha3-384", needsOutputLength: false },
|
|
62
|
+
"sha3-512": { algorithm: "sha3-512", needsOutputLength: false },
|
|
63
|
+
"sha512": { algorithm: "sha512", needsOutputLength: false },
|
|
64
|
+
"shake256": { algorithm: "shake256", needsOutputLength: true },
|
|
65
|
+
});
|
|
66
|
+
var STREAM_HASH_DEFAULT = "sha3-512";
|
|
67
|
+
var SHAKE256_DEFAULT_LEN = 64;
|
|
68
|
+
|
|
30
69
|
// ===========================================================
|
|
31
70
|
// Core primitives — everything else is built from these
|
|
32
71
|
// ===========================================================
|
|
@@ -40,6 +79,74 @@ function hmac(key, data, algorithm) {
|
|
|
40
79
|
return nodeCrypto.createHmac(algorithm, key).update(data).digest("hex");
|
|
41
80
|
}
|
|
42
81
|
|
|
82
|
+
/**
|
|
83
|
+
* @primitive b.crypto.hashStream
|
|
84
|
+
* @signature b.crypto.hashStream(readable, algorithm)
|
|
85
|
+
* @since 0.5.0
|
|
86
|
+
* @related b.crypto.hashFile, b.crypto.sha3Hash
|
|
87
|
+
*
|
|
88
|
+
* Streams a Readable through `createHash(algorithm)` and resolves with
|
|
89
|
+
* the raw digest Buffer. Default algorithm is SHA3-512. Algorithm is
|
|
90
|
+
* validated against the allowlist (sha3-256 / sha3-384 / sha3-512 /
|
|
91
|
+
* sha512 / shake256) so a typo or weak choice throws at config time
|
|
92
|
+
* rather than producing a digest under a surprise algorithm. Read-
|
|
93
|
+
* only — no audit emit.
|
|
94
|
+
*
|
|
95
|
+
* @example
|
|
96
|
+
* var fs = require("fs");
|
|
97
|
+
* var stream = fs.createReadStream("/etc/hosts");
|
|
98
|
+
* b.crypto.hashStream(stream, "sha3-512").then(function (digest) {
|
|
99
|
+
* digest.toString("hex");
|
|
100
|
+
* // → "abcd0123...e8f9" (128 hex chars, SHA3-512 = 64 bytes)
|
|
101
|
+
* });
|
|
102
|
+
*/
|
|
103
|
+
function hashStream(readable, algorithm) {
|
|
104
|
+
var alg = (algorithm || STREAM_HASH_DEFAULT).toLowerCase();
|
|
105
|
+
var entry = STREAM_HASH_ALGORITHMS[alg];
|
|
106
|
+
if (!entry) {
|
|
107
|
+
return Promise.reject(new TypeError(
|
|
108
|
+
"crypto.hashStream: unsupported algorithm '" + algorithm +
|
|
109
|
+
"' (allowed: " + Object.keys(STREAM_HASH_ALGORITHMS).join(", ") + ")"
|
|
110
|
+
));
|
|
111
|
+
}
|
|
112
|
+
if (!readable || typeof readable.pipe !== "function") {
|
|
113
|
+
return Promise.reject(new TypeError(
|
|
114
|
+
"crypto.hashStream: readable must be a Readable stream"
|
|
115
|
+
));
|
|
116
|
+
}
|
|
117
|
+
var hashOpts = entry.needsOutputLength ? { outputLength: SHAKE256_DEFAULT_LEN } : undefined;
|
|
118
|
+
var digester = nodeCrypto.createHash(entry.algorithm, hashOpts);
|
|
119
|
+
return pipeline(readable, digester).then(function () {
|
|
120
|
+
return digester.digest();
|
|
121
|
+
});
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
/**
|
|
125
|
+
* @primitive b.crypto.hashFile
|
|
126
|
+
* @signature b.crypto.hashFile(filePath, algorithm)
|
|
127
|
+
* @since 0.5.0
|
|
128
|
+
* @related b.crypto.hashStream, b.crypto.sha3Hash
|
|
129
|
+
*
|
|
130
|
+
* Opens `filePath` as a Readable and streams it through `hashStream`.
|
|
131
|
+
* Resolves with the raw digest Buffer. Default algorithm is SHA3-512.
|
|
132
|
+
* Read-only — no audit emit; the path is operator-supplied and the
|
|
133
|
+
* digest is the only observable side-effect.
|
|
134
|
+
*
|
|
135
|
+
* @example
|
|
136
|
+
* b.crypto.hashFile("/etc/hosts", "sha3-256").then(function (digest) {
|
|
137
|
+
* digest.toString("hex");
|
|
138
|
+
* // → "0123abcd...ef89" (64 hex chars, SHA3-256 = 32 bytes)
|
|
139
|
+
* });
|
|
140
|
+
*/
|
|
141
|
+
function hashFile(filePath, algorithm) {
|
|
142
|
+
if (typeof filePath !== "string" || filePath.length === 0) {
|
|
143
|
+
return Promise.reject(new TypeError(
|
|
144
|
+
"crypto.hashFile: path must be a non-empty string"
|
|
145
|
+
));
|
|
146
|
+
}
|
|
147
|
+
return hashStream(nodeFs.createReadStream(filePath), algorithm);
|
|
148
|
+
}
|
|
149
|
+
|
|
43
150
|
function random(byteLength) {
|
|
44
151
|
var n = byteLength || 32;
|
|
45
152
|
// SHAKE256 over OS-RNG bytes. The OS RNG (nodeCrypto.randomBytes) is
|
|
@@ -65,6 +172,25 @@ function generateKeyPair(algorithm, options) {
|
|
|
65
172
|
return { publicKey: pair.publicKey, privateKey: pair.privateKey };
|
|
66
173
|
}
|
|
67
174
|
|
|
175
|
+
/**
|
|
176
|
+
* @primitive b.crypto.timingSafeEqual
|
|
177
|
+
* @signature b.crypto.timingSafeEqual(a, b)
|
|
178
|
+
* @since 0.1.0
|
|
179
|
+
* @related b.crypto.hmacSha3
|
|
180
|
+
*
|
|
181
|
+
* Constant-time equality comparison. Coerces non-Buffer inputs via
|
|
182
|
+
* `Buffer.from(String(...))`, returns `false` immediately when lengths
|
|
183
|
+
* differ (length itself is not a secret), then routes equal-length
|
|
184
|
+
* inputs through `crypto.timingSafeEqual`. Use when comparing HMAC
|
|
185
|
+
* digests, session tokens, password-reset codes, or any
|
|
186
|
+
* attacker-influenced value where a timing oracle would leak bits.
|
|
187
|
+
*
|
|
188
|
+
* @example
|
|
189
|
+
* var expected = b.crypto.hmacSha3("server-key", "payload");
|
|
190
|
+
* var supplied = "ab12...e9"; // from request header / body
|
|
191
|
+
* var ok = b.crypto.timingSafeEqual(supplied, expected);
|
|
192
|
+
* // → true when bytes match, false otherwise (no early exit on mismatch)
|
|
193
|
+
*/
|
|
68
194
|
function timingSafeEqual(a, b) {
|
|
69
195
|
var bufA = Buffer.isBuffer(a) ? a : Buffer.from(String(a));
|
|
70
196
|
var bufB = Buffer.isBuffer(b) ? b : Buffer.from(String(b));
|
|
@@ -77,7 +203,39 @@ function timingSafeEqual(a, b) {
|
|
|
77
203
|
// ===========================================================
|
|
78
204
|
|
|
79
205
|
// ---- Hashing ----
|
|
206
|
+
/**
|
|
207
|
+
* @primitive b.crypto.sha3Hash
|
|
208
|
+
* @signature b.crypto.sha3Hash(data)
|
|
209
|
+
* @since 0.1.0
|
|
210
|
+
* @related b.crypto.hmacSha3, b.crypto.kdf, b.crypto.hashFile
|
|
211
|
+
*
|
|
212
|
+
* Returns the lowercase-hex SHA3-512 digest of the input. SHA3-512 is
|
|
213
|
+
* the framework's default hash — collision-resistant, sponge-based,
|
|
214
|
+
* and PQC-aligned (no quantum speedup beyond Grover's). Suitable for
|
|
215
|
+
* content fingerprints, integrity checks, derived-column inputs, and
|
|
216
|
+
* Merkle-tree leaves.
|
|
217
|
+
*
|
|
218
|
+
* @example
|
|
219
|
+
* var digest = b.crypto.sha3Hash("hello world");
|
|
220
|
+
* // → "75d527c368f2efe848ecf6b073a36767800805e9eef2b1857d5f984f036eb6df..."
|
|
221
|
+
*/
|
|
80
222
|
function sha3Hash(data) { return hash(data, "sha3-512").toString("hex"); }
|
|
223
|
+
|
|
224
|
+
/**
|
|
225
|
+
* @primitive b.crypto.hmacSha3
|
|
226
|
+
* @signature b.crypto.hmacSha3(key, data)
|
|
227
|
+
* @since 0.1.0
|
|
228
|
+
* @related b.crypto.sha3Hash, b.crypto.timingSafeEqual
|
|
229
|
+
*
|
|
230
|
+
* Returns the lowercase-hex HMAC-SHA3-512 of `data` keyed by `key`.
|
|
231
|
+
* Use for keyed integrity checks (webhook signatures, request
|
|
232
|
+
* authentication tags, audit-chain links). Pair with
|
|
233
|
+
* `b.crypto.timingSafeEqual` when comparing supplied vs computed tags.
|
|
234
|
+
*
|
|
235
|
+
* @example
|
|
236
|
+
* var tag = b.crypto.hmacSha3("shared-secret", "POST /webhook|123");
|
|
237
|
+
* // → "8f1c...d4e2" (128 hex chars, HMAC-SHA3-512 = 64 bytes)
|
|
238
|
+
*/
|
|
81
239
|
function hmacSha3(key, data) { return hmac(key, data, "sha3-512"); }
|
|
82
240
|
|
|
83
241
|
// (SHA-1 is intentionally NOT exported from b.crypto. The framework's
|
|
@@ -89,8 +247,133 @@ function hmacSha3(key, data) { return hmac(key, data, "sha3-512"); }
|
|
|
89
247
|
// framework spent every other line keeping out.)
|
|
90
248
|
|
|
91
249
|
// ---- KDF ----
|
|
250
|
+
/**
|
|
251
|
+
* @primitive b.crypto.kdf
|
|
252
|
+
* @signature b.crypto.kdf(input, outputLength)
|
|
253
|
+
* @since 0.1.0
|
|
254
|
+
* @related b.crypto.sha3Hash, b.crypto.generateBytes
|
|
255
|
+
*
|
|
256
|
+
* SHAKE256-based key derivation. Returns a Buffer of exactly
|
|
257
|
+
* `outputLength` bytes derived from `input`. SHAKE256 is an XOF
|
|
258
|
+
* (extendable-output function) — arbitrary output length without the
|
|
259
|
+
* truncation pitfalls of fixed-width SHA3 + slice. Used internally
|
|
260
|
+
* for envelope symmetric-key derivation; operators reach for it when
|
|
261
|
+
* they need application-specific subkeys with explicit length.
|
|
262
|
+
*
|
|
263
|
+
* @example
|
|
264
|
+
* var seed = Buffer.from("master-secret|session-42", "utf8");
|
|
265
|
+
* var subkey = b.crypto.kdf(seed, 32);
|
|
266
|
+
* subkey.length;
|
|
267
|
+
* // → 32 (32-byte XChaCha20 key)
|
|
268
|
+
*/
|
|
92
269
|
function kdf(input, outputLength) { return hash(input, "shake256", outputLength); }
|
|
93
270
|
|
|
271
|
+
// ---- App-namespaced indexable hash (for derived-hash columns) ----
|
|
272
|
+
//
|
|
273
|
+
// b.crypto.namespaceHash(prefix, value) → hex-encoded SHA3-512 of
|
|
274
|
+
// `prefix + ":" + value`. Operators wire this into derived-hash
|
|
275
|
+
// columns (emailHash, certFpHash, externalIdHash) where the goal is
|
|
276
|
+
// indexed exact-match lookup, NOT credential storage. Returns hex —
|
|
277
|
+
// not the envelope-versioned base64 b.credentialHash.hash returns —
|
|
278
|
+
// because hex strings are stable, indexable column values across
|
|
279
|
+
// every database backend the framework supports.
|
|
280
|
+
//
|
|
281
|
+
// Why a separate primitive vs `b.crypto.sha3Hash(prefix + ":" + value)`:
|
|
282
|
+
// - Centralized prefix-shape validation (NUL/CR/LF rejection,
|
|
283
|
+
// length bound) — operator can't accidentally smuggle a
|
|
284
|
+
// framework-derived prefix through a user-controlled value.
|
|
285
|
+
// - Clear name documents the intent ("indexable namespace hash"
|
|
286
|
+
// vs "raw content digest"), so callers are less likely to
|
|
287
|
+
// reach for the credential-storage primitive when they want a
|
|
288
|
+
// read-only lookup hash.
|
|
289
|
+
//
|
|
290
|
+
// Read-only / deterministic — no audit emit (the input is operator-
|
|
291
|
+
// supplied; the digest is the only observable side-effect, returned
|
|
292
|
+
// to the caller). NUL / CR / LF in `prefix` are refused so an
|
|
293
|
+
// operator can't smuggle a control sequence into framework or
|
|
294
|
+
// downstream tooling that consumes the audit log; the bound on
|
|
295
|
+
// `prefix` length prevents oversized namespace separators (the
|
|
296
|
+
// framework's HASH_PREFIX entries are <= 16 bytes).
|
|
297
|
+
var NAMESPACE_HASH_PREFIX_MAX_BYTES = 64;
|
|
298
|
+
|
|
299
|
+
/**
|
|
300
|
+
* @primitive b.crypto.namespaceHash
|
|
301
|
+
* @signature b.crypto.namespaceHash(prefix, value, opts)
|
|
302
|
+
* @since 0.6.0
|
|
303
|
+
* @related b.crypto.sha3Hash, b.credentialHash.hash
|
|
304
|
+
*
|
|
305
|
+
* App-namespaced indexable SHA3-512 hash for derived-hash columns
|
|
306
|
+
* (emailHash, certFpHash, externalIdHash). Returns lowercase hex —
|
|
307
|
+
* stable, indexable column values across every supported database.
|
|
308
|
+
* Centralizes prefix-shape validation: NUL / CR / LF in `prefix` are
|
|
309
|
+
* refused outright, and `prefix` is bounded to 64 UTF-8 bytes so an
|
|
310
|
+
* operator can't smuggle log-injection or oversized labels into
|
|
311
|
+
* derived-column inputs. Use when the goal is exact-match lookup,
|
|
312
|
+
* NOT credential storage — for password-style storage use
|
|
313
|
+
* `b.credentialHash.hash`.
|
|
314
|
+
*
|
|
315
|
+
* @opts
|
|
316
|
+
* reserved: object, // accepted but ignored — reserved for a future algorithm-selection knob
|
|
317
|
+
*
|
|
318
|
+
* @example
|
|
319
|
+
* var emailHash = b.crypto.namespaceHash("email", "alice@example.com");
|
|
320
|
+
* // → "1f3a...c08d" (128 hex chars, SHA3-512 of "email:alice@example.com")
|
|
321
|
+
*
|
|
322
|
+
* var certFpHash = b.crypto.namespaceHash("cert-fp", Buffer.from([1, 2, 3, 4]));
|
|
323
|
+
* // Buffer/Uint8Array values are coerced to UTF-8 string before hashing.
|
|
324
|
+
*/
|
|
325
|
+
function namespaceHash(prefix, value, opts) {
|
|
326
|
+
// opts reserved for future extension (algorithm selection); current
|
|
327
|
+
// surface is fixed to SHA3-512 — no operator demand for SHAKE256
|
|
328
|
+
// variable-length output here, since the indexed column shape is
|
|
329
|
+
// fixed-width hex.
|
|
330
|
+
if (opts && typeof opts !== "object") {
|
|
331
|
+
throw new TypeError("crypto.namespaceHash: opts must be a plain object when provided");
|
|
332
|
+
}
|
|
333
|
+
if (typeof prefix !== "string") {
|
|
334
|
+
throw new TypeError("crypto.namespaceHash: prefix must be a string");
|
|
335
|
+
}
|
|
336
|
+
if (prefix.length === 0) {
|
|
337
|
+
throw new TypeError("crypto.namespaceHash: prefix must be non-empty");
|
|
338
|
+
}
|
|
339
|
+
// Byte-length bound — operator's prefix is the namespace label and
|
|
340
|
+
// shouldn't bloat the hash input. Use Buffer.byteLength so multi-
|
|
341
|
+
// byte UTF-8 prefixes can't slip through a code-unit-only check.
|
|
342
|
+
if (Buffer.byteLength(prefix, "utf8") > NAMESPACE_HASH_PREFIX_MAX_BYTES) {
|
|
343
|
+
throw new TypeError(
|
|
344
|
+
"crypto.namespaceHash: prefix exceeds " + NAMESPACE_HASH_PREFIX_MAX_BYTES +
|
|
345
|
+
" bytes (UTF-8); operator-derived prefixes should be short labels"
|
|
346
|
+
);
|
|
347
|
+
}
|
|
348
|
+
// NUL / CR / LF in prefix — refuse outright. NUL truncates in many
|
|
349
|
+
// C-string consumers (audit-log path, downstream DB tooling); CR/LF
|
|
350
|
+
// smuggles log-injection patterns into anything that renders the
|
|
351
|
+
// prefix verbatim.
|
|
352
|
+
// eslint-disable-next-line no-control-regex
|
|
353
|
+
if (/[\u0000\r\n]/.test(prefix)) {
|
|
354
|
+
throw new TypeError(
|
|
355
|
+
"crypto.namespaceHash: prefix contains NUL / CR / LF — refuse"
|
|
356
|
+
);
|
|
357
|
+
}
|
|
358
|
+
// value is the operator-supplied content. Coerce Buffer/Uint8Array
|
|
359
|
+
// to utf-8 string for concatenation; reject anything else so the
|
|
360
|
+
// caller surfaces the type error explicitly rather than silently
|
|
361
|
+
// hashing `[object Object]`.
|
|
362
|
+
var valueStr;
|
|
363
|
+
if (typeof value === "string") {
|
|
364
|
+
valueStr = value;
|
|
365
|
+
} else if (Buffer.isBuffer(value)) {
|
|
366
|
+
valueStr = value.toString("utf8");
|
|
367
|
+
} else if (value instanceof Uint8Array) {
|
|
368
|
+
valueStr = Buffer.from(value).toString("utf8");
|
|
369
|
+
} else {
|
|
370
|
+
throw new TypeError(
|
|
371
|
+
"crypto.namespaceHash: value must be a string, Buffer, or Uint8Array"
|
|
372
|
+
);
|
|
373
|
+
}
|
|
374
|
+
return hash(prefix + ":" + valueStr, "sha3-512").toString("hex");
|
|
375
|
+
}
|
|
376
|
+
|
|
94
377
|
// _suiteFixedInfo — NIST SP 800-56C r2 §4.1 OtherInfo / RFC 9180
|
|
95
378
|
// (HPKE) §5.1 suite_id binding. Returns the byte string that the KDF
|
|
96
379
|
// MUST absorb alongside the shared-secret(s) so a key derived under
|
|
@@ -105,7 +388,48 @@ function _suiteFixedInfo(kemId, cipherId, kdfId) {
|
|
|
105
388
|
}
|
|
106
389
|
|
|
107
390
|
// ---- Random ----
|
|
391
|
+
/**
|
|
392
|
+
* @primitive b.crypto.generateBytes
|
|
393
|
+
* @signature b.crypto.generateBytes(byteLength)
|
|
394
|
+
* @since 0.1.0
|
|
395
|
+
* @related b.crypto.generateToken, b.uuid.v4
|
|
396
|
+
*
|
|
397
|
+
* Cryptographically secure random Buffer of length `byteLength`
|
|
398
|
+
* (default 32). The bytes are SHAKE256(OS-RNG bytes) — defense-in-
|
|
399
|
+
* depth over `crypto.randomBytes` so a hypothetical OS-RNG weakness
|
|
400
|
+
* is not directly observable downstream. Use for session IDs, KDF
|
|
401
|
+
* salts, AEAD nonces, anything requiring unpredictable bytes.
|
|
402
|
+
*
|
|
403
|
+
* @example
|
|
404
|
+
* var sessionId = b.crypto.generateBytes(16).toString("hex");
|
|
405
|
+
* // → "5b8f2a4c7d1e9f0b3c6a8d2e4f7c1b5d" (32 hex chars, 16 random bytes)
|
|
406
|
+
*
|
|
407
|
+
* var nonce = b.crypto.generateBytes(24); // XChaCha20-Poly1305 nonce
|
|
408
|
+
* nonce.length;
|
|
409
|
+
* // → 24
|
|
410
|
+
*/
|
|
108
411
|
function generateBytes(byteLength) { return Buffer.from(random(byteLength)); }
|
|
412
|
+
|
|
413
|
+
/**
|
|
414
|
+
* @primitive b.crypto.generateToken
|
|
415
|
+
* @signature b.crypto.generateToken(byteLength)
|
|
416
|
+
* @since 0.1.0
|
|
417
|
+
* @related b.crypto.generateBytes, b.uuid.v4
|
|
418
|
+
*
|
|
419
|
+
* Hex-encoded random token. Same entropy source as `generateBytes`
|
|
420
|
+
* (SHAKE256 over OS-RNG bytes) but returned as a lowercase hex string
|
|
421
|
+
* — convenient for HTTP headers, URL parameters, log fields, or any
|
|
422
|
+
* context where a Buffer would need to be encoded anyway. Default
|
|
423
|
+
* `byteLength` is 32 (64 hex chars, ~256 bits of entropy).
|
|
424
|
+
*
|
|
425
|
+
* @example
|
|
426
|
+
* var token = b.crypto.generateToken();
|
|
427
|
+
* token.length;
|
|
428
|
+
* // → 64 (32 bytes hex-encoded)
|
|
429
|
+
*
|
|
430
|
+
* var shortId = b.crypto.generateToken(8);
|
|
431
|
+
* // → "a3f9...b1" (16 hex chars, 8 random bytes)
|
|
432
|
+
*/
|
|
109
433
|
function generateToken(byteLength) { return random(byteLength || 32).toString("hex"); }
|
|
110
434
|
|
|
111
435
|
// ---- Subresource Integrity (W3C SRI 1.0) ----
|
|
@@ -127,6 +451,32 @@ function generateToken(byteLength) { return random(byteLength || 32).toString("h
|
|
|
127
451
|
// → "sha384-X1... sha384-X2..." (per W3C §3.3 multi-integrity)
|
|
128
452
|
var SRI_ALGORITHMS = { "sha256": "sha256", "sha384": "sha384", "sha512": "sha512" };
|
|
129
453
|
|
|
454
|
+
/**
|
|
455
|
+
* @primitive b.crypto.sri
|
|
456
|
+
* @signature b.crypto.sri(content, opts)
|
|
457
|
+
* @since 0.5.0
|
|
458
|
+
* @related b.staticServe
|
|
459
|
+
*
|
|
460
|
+
* Computes a W3C Subresource Integrity 1.0 attribute string —
|
|
461
|
+
* `sha###-base64` — that operators paste into `<script integrity>` or
|
|
462
|
+
* `<link integrity>` tags. Defends against CDN compromise and ISP
|
|
463
|
+
* MITM injection: the browser refuses to load the resource when its
|
|
464
|
+
* computed hash diverges from the integrity attribute. SRI 1.0 §3.2
|
|
465
|
+
* supports sha256 / sha384 / sha512; sha384 is the recommended
|
|
466
|
+
* default (collision margin without sha512's 64-byte overhead). Pass
|
|
467
|
+
* an array of contents to emit multiple integrity tokens space-
|
|
468
|
+
* separated per §3.3 (browser picks the strongest it recognizes).
|
|
469
|
+
*
|
|
470
|
+
* @opts
|
|
471
|
+
* algorithm: string, // "sha256" | "sha384" | "sha512" — default "sha384"
|
|
472
|
+
*
|
|
473
|
+
* @example
|
|
474
|
+
* var attr = b.crypto.sri(Buffer.from("alert(1);", "utf8"), { algorithm: "sha384" });
|
|
475
|
+
* // → "sha384-pNdyOuHIPKgRPnYJTBxEEEZcJj1qHxJzNheCuHGRy3Cm0UpVbcnruIvMRIs5VcDb"
|
|
476
|
+
*
|
|
477
|
+
* var multi = b.crypto.sri(["payload-a", "payload-b"], { algorithm: "sha512" });
|
|
478
|
+
* // → "sha512-... sha512-..." (two tokens, space-separated)
|
|
479
|
+
*/
|
|
130
480
|
function sri(content, opts) {
|
|
131
481
|
opts = opts || {};
|
|
132
482
|
var algorithm = (opts.algorithm || "sha384").toLowerCase();
|
|
@@ -149,6 +499,31 @@ function sri(content, opts) {
|
|
|
149
499
|
}
|
|
150
500
|
|
|
151
501
|
// ---- Key generation ----
|
|
502
|
+
/**
|
|
503
|
+
* @primitive b.crypto.generateEncryptionKeyPair
|
|
504
|
+
* @signature b.crypto.generateEncryptionKeyPair()
|
|
505
|
+
* @since 0.1.0
|
|
506
|
+
* @related b.crypto.encrypt, b.crypto.decrypt, b.crypto.generateMlkem768X25519KeyPair
|
|
507
|
+
*
|
|
508
|
+
* Generates a hybrid recipient keypair for `b.crypto.encrypt`:
|
|
509
|
+
* ML-KEM-1024 (FIPS 203 PQC KEM) plus ECDH P-384 (classical defense-
|
|
510
|
+
* in-depth). Returns `{ publicKey, privateKey, ecPublicKey,
|
|
511
|
+
* ecPrivateKey }` — all four PEMs. Persist the private halves in
|
|
512
|
+
* sealed storage; publish the public halves to recipients. The
|
|
513
|
+
* framework default for at-rest envelopes and api-encrypt strategies.
|
|
514
|
+
*
|
|
515
|
+
* @example
|
|
516
|
+
* var pair = b.crypto.generateEncryptionKeyPair();
|
|
517
|
+
* var sealed = b.crypto.encrypt("secret payload", {
|
|
518
|
+
* publicKey: pair.publicKey,
|
|
519
|
+
* ecPublicKey: pair.ecPublicKey,
|
|
520
|
+
* });
|
|
521
|
+
* var roundTrip = b.crypto.decrypt(sealed, {
|
|
522
|
+
* privateKey: pair.privateKey,
|
|
523
|
+
* ecPrivateKey: pair.ecPrivateKey,
|
|
524
|
+
* });
|
|
525
|
+
* // → "secret payload"
|
|
526
|
+
*/
|
|
152
527
|
function generateEncryptionKeyPair() {
|
|
153
528
|
var mlkem = generateKeyPair("ml-kem-1024");
|
|
154
529
|
var ec = generateKeyPair("ec", { namedCurve: "P-384" });
|
|
@@ -160,15 +535,76 @@ function generateEncryptionKeyPair() {
|
|
|
160
535
|
};
|
|
161
536
|
}
|
|
162
537
|
|
|
538
|
+
/**
|
|
539
|
+
* @primitive b.crypto.generateSigningKeyPair
|
|
540
|
+
* @signature b.crypto.generateSigningKeyPair(algorithm)
|
|
541
|
+
* @since 0.1.0
|
|
542
|
+
* @related b.crypto.sign, b.crypto.verify
|
|
543
|
+
*
|
|
544
|
+
* Generates a PQC signature keypair. Default algorithm is `ml-dsa-87`
|
|
545
|
+
* (FIPS 204 — lattice-based, fast verify); pass `slh-dsa-shake-256f`
|
|
546
|
+
* for hash-based signatures (larger, slower, but minimal cryptographic
|
|
547
|
+
* assumptions — useful for long-lived audit-chain keys). Returns
|
|
548
|
+
* `{ publicKey, privateKey }` PEMs. The signing primitives auto-
|
|
549
|
+
* detect the algorithm from the key PEM, so callers don't need to
|
|
550
|
+
* pass it explicitly to `sign` / `verify`.
|
|
551
|
+
*
|
|
552
|
+
* @example
|
|
553
|
+
* var pair = b.crypto.generateSigningKeyPair();
|
|
554
|
+
* var sig = b.crypto.sign("audit:row=42|action=delete", pair.privateKey);
|
|
555
|
+
* var ok = b.crypto.verify("audit:row=42|action=delete", sig, pair.publicKey);
|
|
556
|
+
* // → true
|
|
557
|
+
*
|
|
558
|
+
* // Hash-based alternative:
|
|
559
|
+
* var slh = b.crypto.generateSigningKeyPair("slh-dsa-shake-256f");
|
|
560
|
+
*/
|
|
163
561
|
function generateSigningKeyPair(algorithm) {
|
|
164
562
|
return generateKeyPair(algorithm || "ml-dsa-87");
|
|
165
563
|
}
|
|
166
564
|
|
|
167
565
|
// ---- Signatures (auto-detect algorithm from key PEM) ----
|
|
566
|
+
/**
|
|
567
|
+
* @primitive b.crypto.sign
|
|
568
|
+
* @signature b.crypto.sign(data, privateKeyPem)
|
|
569
|
+
* @since 0.1.0
|
|
570
|
+
* @related b.crypto.verify, b.crypto.generateSigningKeyPair
|
|
571
|
+
*
|
|
572
|
+
* Produces a PQC signature over `data`. Algorithm is auto-detected
|
|
573
|
+
* from the private-key PEM (ML-DSA-87 lattice / SLH-DSA-SHAKE-256f
|
|
574
|
+
* hash-based). Returns a Buffer. Pair with `b.crypto.verify` on the
|
|
575
|
+
* recipient side; use for audit-chain links, webhook tags,
|
|
576
|
+
* cross-service request signatures.
|
|
577
|
+
*
|
|
578
|
+
* @example
|
|
579
|
+
* var pair = b.crypto.generateSigningKeyPair();
|
|
580
|
+
* var sig = b.crypto.sign("payload-to-sign", pair.privateKey);
|
|
581
|
+
* sig.length > 0;
|
|
582
|
+
* // → true (ML-DSA-87 signature ~ 4627 bytes)
|
|
583
|
+
*/
|
|
168
584
|
function sign(data, privateKeyPem) {
|
|
169
585
|
return nodeCrypto.sign(null, Buffer.from(data), privateKeyPem);
|
|
170
586
|
}
|
|
171
587
|
|
|
588
|
+
/**
|
|
589
|
+
* @primitive b.crypto.verify
|
|
590
|
+
* @signature b.crypto.verify(data, signature, publicKeyPem)
|
|
591
|
+
* @since 0.1.0
|
|
592
|
+
* @related b.crypto.sign, b.crypto.generateSigningKeyPair
|
|
593
|
+
*
|
|
594
|
+
* Verifies a signature produced by `b.crypto.sign`. Returns `true` on
|
|
595
|
+
* a valid signature, `false` otherwise — never throws on a malformed
|
|
596
|
+
* signature, so operators don't need to wrap the call. Algorithm is
|
|
597
|
+
* auto-detected from the public-key PEM.
|
|
598
|
+
*
|
|
599
|
+
* @example
|
|
600
|
+
* var pair = b.crypto.generateSigningKeyPair();
|
|
601
|
+
* var sig = b.crypto.sign("hello", pair.privateKey);
|
|
602
|
+
* var ok = b.crypto.verify("hello", sig, pair.publicKey);
|
|
603
|
+
* // → true
|
|
604
|
+
*
|
|
605
|
+
* var tampered = b.crypto.verify("HELLO", sig, pair.publicKey);
|
|
606
|
+
* // → false (data mismatch)
|
|
607
|
+
*/
|
|
172
608
|
function verify(data, signature, publicKeyPem) {
|
|
173
609
|
return nodeCrypto.verify(null, Buffer.from(data), publicKeyPem, signature);
|
|
174
610
|
}
|
|
@@ -181,6 +617,38 @@ function verify(data, signature, publicKeyPem) {
|
|
|
181
617
|
var _hybridDisabledAuditEmitted = false;
|
|
182
618
|
|
|
183
619
|
// ---- Envelope encrypt (ML-KEM-1024 + P-384 ECDH hybrid + SHAKE256 + XChaCha20) ----
|
|
620
|
+
/**
|
|
621
|
+
* @primitive b.crypto.encrypt
|
|
622
|
+
* @signature b.crypto.encrypt(plaintext, publicKeys)
|
|
623
|
+
* @since 0.1.0
|
|
624
|
+
* @related b.crypto.decrypt, b.crypto.generateEncryptionKeyPair, b.crypto.encryptMlkem768X25519
|
|
625
|
+
*
|
|
626
|
+
* Seals `plaintext` into a base64 envelope under the recipient's
|
|
627
|
+
* keypair. Default suite is ML-KEM-1024 + ECDH P-384 hybrid (FIPS 203
|
|
628
|
+
* KEM with classical defense-in-depth) plus SHAKE256 KDF and
|
|
629
|
+
* XChaCha20-Poly1305 AEAD. The 4-byte envelope header (magic + KEM
|
|
630
|
+
* ID + cipher ID + KDF ID) is bound as AEAD AAD so an algorithm-
|
|
631
|
+
* substitution attack on the header fails Poly1305 verification.
|
|
632
|
+
* Pass `{ publicKey, ecPublicKey }` for the hybrid path; passing only
|
|
633
|
+
* an ML-KEM PEM falls back to KEM-only and emits a one-shot
|
|
634
|
+
* `system.crypto.hybrid_disabled` audit (operators wanting the silent
|
|
635
|
+
* KEM-only path call `encryptMlkem768X25519` or seal manually).
|
|
636
|
+
*
|
|
637
|
+
* @example
|
|
638
|
+
* var pair = b.crypto.generateEncryptionKeyPair();
|
|
639
|
+
* var sealed = b.crypto.encrypt("PHI: patient-42 dx=...", {
|
|
640
|
+
* publicKey: pair.publicKey,
|
|
641
|
+
* ecPublicKey: pair.ecPublicKey,
|
|
642
|
+
* });
|
|
643
|
+
* typeof sealed;
|
|
644
|
+
* // → "string" (base64 envelope)
|
|
645
|
+
*
|
|
646
|
+
* var plain = b.crypto.decrypt(sealed, {
|
|
647
|
+
* privateKey: pair.privateKey,
|
|
648
|
+
* ecPrivateKey: pair.ecPrivateKey,
|
|
649
|
+
* });
|
|
650
|
+
* // → "PHI: patient-42 dx=..."
|
|
651
|
+
*/
|
|
184
652
|
function encrypt(plaintext, publicKeys) {
|
|
185
653
|
var mlkemPubPem = typeof publicKeys === "string" ? publicKeys : publicKeys.publicKey;
|
|
186
654
|
var ecPubPem = typeof publicKeys === "string" ? null : publicKeys.ecPublicKey;
|
|
@@ -256,6 +724,33 @@ function encryptMlkemOnly(plaintext, publicKeyPem) {
|
|
|
256
724
|
}
|
|
257
725
|
|
|
258
726
|
// ---- Envelope decrypt (dispatches on envelope IDs, supports both KEM IDs) ----
|
|
727
|
+
/**
|
|
728
|
+
* @primitive b.crypto.decrypt
|
|
729
|
+
* @signature b.crypto.decrypt(ciphertext, privateKeys)
|
|
730
|
+
* @since 0.1.0
|
|
731
|
+
* @related b.crypto.encrypt, b.crypto.generateEncryptionKeyPair, b.crypto.decryptMlkem768X25519
|
|
732
|
+
*
|
|
733
|
+
* Opens a base64 envelope produced by `b.crypto.encrypt`. The
|
|
734
|
+
* envelope header is parsed first and the decrypt path dispatches by
|
|
735
|
+
* KEM ID — ML-KEM-1024 + P-384, ML-KEM-1024 KEM-only, or ML-KEM-768 +
|
|
736
|
+
* X25519 — so old envelopes continue to decrypt under whichever suite
|
|
737
|
+
* sealed them while new writes use the active suite. Throws on
|
|
738
|
+
* malformed magic, unsupported cipher / KDF, or Poly1305 tag failure.
|
|
739
|
+
* Pass `{ privateKey, ecPrivateKey }` for the default hybrid; the
|
|
740
|
+
* ML-KEM-768 + X25519 KEM ID also requires `x25519PrivateKey`.
|
|
741
|
+
*
|
|
742
|
+
* @example
|
|
743
|
+
* var pair = b.crypto.generateEncryptionKeyPair();
|
|
744
|
+
* var sealed = b.crypto.encrypt("session-token=abc123", {
|
|
745
|
+
* publicKey: pair.publicKey,
|
|
746
|
+
* ecPublicKey: pair.ecPublicKey,
|
|
747
|
+
* });
|
|
748
|
+
* var opened = b.crypto.decrypt(sealed, {
|
|
749
|
+
* privateKey: pair.privateKey,
|
|
750
|
+
* ecPrivateKey: pair.ecPrivateKey,
|
|
751
|
+
* });
|
|
752
|
+
* // → "session-token=abc123"
|
|
753
|
+
*/
|
|
259
754
|
function decrypt(ciphertext, privateKeys) {
|
|
260
755
|
var packed = Buffer.from(ciphertext, "base64");
|
|
261
756
|
if (packed[0] === 0xE1) { // allow:raw-byte-literal — legacy envelope magic
|
|
@@ -339,6 +834,32 @@ function decryptEnvelope(packed, privateKeys) {
|
|
|
339
834
|
// binding (b.breakGlass.encryptCell binds (table, rowId, column) so a
|
|
340
835
|
// ciphertext from row A literally cannot decrypt as row B even with
|
|
341
836
|
// the same key).
|
|
837
|
+
/**
|
|
838
|
+
* @primitive b.crypto.encryptPacked
|
|
839
|
+
* @signature b.crypto.encryptPacked(buffer, key, aad)
|
|
840
|
+
* @since 0.1.0
|
|
841
|
+
* @related b.crypto.decryptPacked, b.crypto.encrypt
|
|
842
|
+
*
|
|
843
|
+
* Symmetric (key-already-known) authenticated encryption. Returns a
|
|
844
|
+
* self-describing Buffer: 1-byte format ID + 24-byte XChaCha20-
|
|
845
|
+
* Poly1305 nonce + ciphertext+tag. Operators who already hold a
|
|
846
|
+
* symmetric key (sealed-storage cell encryption, break-glass row
|
|
847
|
+
* encryption) reach for this instead of the envelope variants. The
|
|
848
|
+
* optional `aad` (additional authenticated data) is mixed into the
|
|
849
|
+
* Poly1305 tag; encrypt-time and decrypt-time AAD must match exactly
|
|
850
|
+
* or decryption fails. Wire it for context-binding (e.g. `(table,
|
|
851
|
+
* rowId, column)` so a ciphertext from row A literally cannot decrypt
|
|
852
|
+
* as row B even with the same key).
|
|
853
|
+
*
|
|
854
|
+
* @example
|
|
855
|
+
* var key = b.crypto.generateBytes(32);
|
|
856
|
+
* var data = Buffer.from("row-42 column-ssn", "utf8");
|
|
857
|
+
* var aad = Buffer.from("patients|42|ssn", "utf8");
|
|
858
|
+
* var packed = b.crypto.encryptPacked(data, key, aad);
|
|
859
|
+
* var plain = b.crypto.decryptPacked(packed, key, aad);
|
|
860
|
+
* plain.toString("utf8");
|
|
861
|
+
* // → "row-42 column-ssn"
|
|
862
|
+
*/
|
|
342
863
|
function encryptPacked(buffer, key, aad) {
|
|
343
864
|
var nonce = random(C.BYTES.bytes(24));
|
|
344
865
|
var ct = xchacha20poly1305(key, nonce, aad ? Buffer.from(aad) : undefined).encrypt(buffer);
|
|
@@ -349,6 +870,26 @@ function encryptPacked(buffer, key, aad) {
|
|
|
349
870
|
]);
|
|
350
871
|
}
|
|
351
872
|
|
|
873
|
+
/**
|
|
874
|
+
* @primitive b.crypto.decryptPacked
|
|
875
|
+
* @signature b.crypto.decryptPacked(packed, key, aad)
|
|
876
|
+
* @since 0.1.0
|
|
877
|
+
* @related b.crypto.encryptPacked
|
|
878
|
+
*
|
|
879
|
+
* Inverse of `encryptPacked`. Reads the 1-byte format ID, extracts
|
|
880
|
+
* the 24-byte XChaCha20-Poly1305 nonce, and decrypts the trailing
|
|
881
|
+
* ciphertext under `key` + `aad`. Throws on unsupported format byte
|
|
882
|
+
* or AAD / tag mismatch — operators wrap when a graceful per-cell
|
|
883
|
+
* fallback is required.
|
|
884
|
+
*
|
|
885
|
+
* @example
|
|
886
|
+
* var key = b.crypto.generateBytes(32);
|
|
887
|
+
* var aad = Buffer.from("audit|2026-05-08", "utf8");
|
|
888
|
+
* var pkt = b.crypto.encryptPacked(Buffer.from("hello", "utf8"), key, aad);
|
|
889
|
+
* var open = b.crypto.decryptPacked(pkt, key, aad);
|
|
890
|
+
* open.toString("utf8");
|
|
891
|
+
* // → "hello"
|
|
892
|
+
*/
|
|
352
893
|
function decryptPacked(packed, key, aad) {
|
|
353
894
|
if (packed[0] !== C.FORMAT.XCHACHA20_POLY1305) {
|
|
354
895
|
throw new Error("Invalid packed format: unsupported version");
|
|
@@ -384,6 +925,32 @@ function decryptPacked(packed, key, aad) {
|
|
|
384
925
|
// x25519PrivateKey } — privateKey is the ML-KEM-768 PEM, NOT the
|
|
385
926
|
// default ML-KEM-1024.
|
|
386
927
|
|
|
928
|
+
/**
|
|
929
|
+
* @primitive b.crypto.generateMlkem768X25519KeyPair
|
|
930
|
+
* @signature b.crypto.generateMlkem768X25519KeyPair()
|
|
931
|
+
* @since 0.7.28
|
|
932
|
+
* @related b.crypto.encryptMlkem768X25519, b.crypto.decryptMlkem768X25519, b.crypto.generateEncryptionKeyPair
|
|
933
|
+
*
|
|
934
|
+
* Generates the IETF / Cloudflare / Chrome TLS 1.3 hybrid keypair
|
|
935
|
+
* (codepoint 0x11EC): ML-KEM-768 (FIPS 203) + X25519 (RFC 7748).
|
|
936
|
+
* Smaller payload than ML-KEM-1024 + P-384 (~1.1 KB vs ~1.6 KB) and
|
|
937
|
+
* wider interop with peers using the same hybrid (Cloudflare
|
|
938
|
+
* Workers, Chrome, browsers offering hybrid PQ key share). Returns
|
|
939
|
+
* `{ mlkemPublicKey, mlkemPrivateKey, x25519PublicKey,
|
|
940
|
+
* x25519PrivateKey }`.
|
|
941
|
+
*
|
|
942
|
+
* @example
|
|
943
|
+
* var pair = b.crypto.generateMlkem768X25519KeyPair();
|
|
944
|
+
* var sealed = b.crypto.encryptMlkem768X25519("interop payload", {
|
|
945
|
+
* mlkemPublicKey: pair.mlkemPublicKey,
|
|
946
|
+
* x25519PublicKey: pair.x25519PublicKey,
|
|
947
|
+
* });
|
|
948
|
+
* var plain = b.crypto.decryptMlkem768X25519(sealed, {
|
|
949
|
+
* privateKey: pair.mlkemPrivateKey,
|
|
950
|
+
* x25519PrivateKey: pair.x25519PrivateKey,
|
|
951
|
+
* });
|
|
952
|
+
* // → "interop payload"
|
|
953
|
+
*/
|
|
387
954
|
function generateMlkem768X25519KeyPair() {
|
|
388
955
|
var mlkem = generateKeyPair("ml-kem-768");
|
|
389
956
|
var x25519 = generateKeyPair("x25519");
|
|
@@ -395,6 +962,30 @@ function generateMlkem768X25519KeyPair() {
|
|
|
395
962
|
};
|
|
396
963
|
}
|
|
397
964
|
|
|
965
|
+
/**
|
|
966
|
+
* @primitive b.crypto.encryptMlkem768X25519
|
|
967
|
+
* @signature b.crypto.encryptMlkem768X25519(plaintext, recipient)
|
|
968
|
+
* @since 0.7.28
|
|
969
|
+
* @related b.crypto.decryptMlkem768X25519, b.crypto.encrypt, b.crypto.generateMlkem768X25519KeyPair
|
|
970
|
+
*
|
|
971
|
+
* Seals `plaintext` under the IETF / Cloudflare / Chrome TLS 1.3
|
|
972
|
+
* hybrid (ML-KEM-768 + X25519). Recipient shape is
|
|
973
|
+
* `{ mlkemPublicKey, x25519PublicKey }` — both PEMs. Same envelope
|
|
974
|
+
* wire format as the default hybrid; the KEM ID byte is
|
|
975
|
+
* `KEM_IDS.ML_KEM_768_X25519` so `b.crypto.decrypt` dispatches
|
|
976
|
+
* correctly on the receive side. Reach for this when the recipient
|
|
977
|
+
* publishes ML-KEM-768 + X25519 keys (TLS-1.3 codepoint 0x11EC peers,
|
|
978
|
+
* cross-stack interop with Cloudflare Workers or Chrome-side WebCrypto).
|
|
979
|
+
*
|
|
980
|
+
* @example
|
|
981
|
+
* var pair = b.crypto.generateMlkem768X25519KeyPair();
|
|
982
|
+
* var sealed = b.crypto.encryptMlkem768X25519("cross-stack message", {
|
|
983
|
+
* mlkemPublicKey: pair.mlkemPublicKey,
|
|
984
|
+
* x25519PublicKey: pair.x25519PublicKey,
|
|
985
|
+
* });
|
|
986
|
+
* typeof sealed;
|
|
987
|
+
* // → "string" (base64 envelope, ~1.1 KB for short plaintexts)
|
|
988
|
+
*/
|
|
398
989
|
function encryptMlkem768X25519(plaintext, recipient) {
|
|
399
990
|
if (!recipient || !recipient.mlkemPublicKey || !recipient.x25519PublicKey) {
|
|
400
991
|
throw new Error("encryptMlkem768X25519 requires { mlkemPublicKey, x25519PublicKey }");
|
|
@@ -440,6 +1031,31 @@ function encryptMlkem768X25519(plaintext, recipient) {
|
|
|
440
1031
|
//
|
|
441
1032
|
// recipient: { privateKey, x25519PrivateKey } — operator's keys
|
|
442
1033
|
// ciphertext: base64 envelope from encryptMlkem768X25519
|
|
1034
|
+
/**
|
|
1035
|
+
* @primitive b.crypto.decryptMlkem768X25519
|
|
1036
|
+
* @signature b.crypto.decryptMlkem768X25519(ciphertext, recipient)
|
|
1037
|
+
* @since 0.7.28
|
|
1038
|
+
* @related b.crypto.encryptMlkem768X25519, b.crypto.decrypt
|
|
1039
|
+
*
|
|
1040
|
+
* Symmetric named-pair to `encryptMlkem768X25519`. Rejects any
|
|
1041
|
+
* envelope whose KEM ID byte is not `ML_KEM_768_X25519` so an
|
|
1042
|
+
* operator who calls this with a ciphertext sealed under a different
|
|
1043
|
+
* algorithm gets a clear error rather than the generic dispatch path.
|
|
1044
|
+
* Recipient shape is `{ privateKey, x25519PrivateKey }` — `privateKey`
|
|
1045
|
+
* is the ML-KEM-768 PEM, NOT the framework default ML-KEM-1024.
|
|
1046
|
+
*
|
|
1047
|
+
* @example
|
|
1048
|
+
* var pair = b.crypto.generateMlkem768X25519KeyPair();
|
|
1049
|
+
* var sealed = b.crypto.encryptMlkem768X25519("interop", {
|
|
1050
|
+
* mlkemPublicKey: pair.mlkemPublicKey,
|
|
1051
|
+
* x25519PublicKey: pair.x25519PublicKey,
|
|
1052
|
+
* });
|
|
1053
|
+
* var plain = b.crypto.decryptMlkem768X25519(sealed, {
|
|
1054
|
+
* privateKey: pair.mlkemPrivateKey,
|
|
1055
|
+
* x25519PrivateKey: pair.x25519PrivateKey,
|
|
1056
|
+
* });
|
|
1057
|
+
* // → "interop"
|
|
1058
|
+
*/
|
|
443
1059
|
function decryptMlkem768X25519(ciphertext, recipient) {
|
|
444
1060
|
if (!recipient || typeof recipient !== "object" ||
|
|
445
1061
|
!recipient.privateKey || !recipient.x25519PrivateKey) {
|
|
@@ -501,6 +1117,36 @@ function _extractEcdhP384FromCert(certDer) {
|
|
|
501
1117
|
// peerCertDer: Buffer | Uint8Array, // peer's TLS cert (DER)
|
|
502
1118
|
// peerKemPubkey: string, // peer's ML-KEM-1024 pubkey PEM
|
|
503
1119
|
// });
|
|
1120
|
+
/**
|
|
1121
|
+
* @primitive b.crypto.encryptEnvelopeAsCertPeer
|
|
1122
|
+
* @signature b.crypto.encryptEnvelopeAsCertPeer(plaintext, opts)
|
|
1123
|
+
* @since 0.7.0
|
|
1124
|
+
* @related b.crypto.decryptEnvelopeAsCertPeer, b.crypto.encrypt
|
|
1125
|
+
*
|
|
1126
|
+
* Produces an envelope sealed to a peer identified by their TLS cert
|
|
1127
|
+
* (P-384 ECDH half) plus a peer-supplied ML-KEM-1024 pubkey. The wire
|
|
1128
|
+
* format is identical to `b.crypto.encrypt` — only the input keys
|
|
1129
|
+
* differ. Use for sealed-storage records with peer recipients,
|
|
1130
|
+
* cross-service messages between cert-identified peers without a
|
|
1131
|
+
* shared framework keypair, or audit-log entries tagged with peer
|
|
1132
|
+
* recipients. The cert must carry an ECDH P-384 SubjectPublicKeyInfo
|
|
1133
|
+
* — anything else throws `crypto/cert-key-not-ecdh-p384`.
|
|
1134
|
+
*
|
|
1135
|
+
* @opts
|
|
1136
|
+
* peerCertDer: Buffer, // peer's TLS cert as DER bytes (Buffer or Uint8Array)
|
|
1137
|
+
* peerKemPubkey: string, // peer's ML-KEM-1024 pubkey PEM (non-empty string)
|
|
1138
|
+
*
|
|
1139
|
+
* @example
|
|
1140
|
+
* var fs = require("fs");
|
|
1141
|
+
* var peerCertDer = fs.readFileSync("/etc/ssl/peer.cert.der");
|
|
1142
|
+
* var peerKemPubkey = fs.readFileSync("/etc/ssl/peer.mlkem.pem", "utf8");
|
|
1143
|
+
* var sealed = b.crypto.encryptEnvelopeAsCertPeer("cross-peer payload", {
|
|
1144
|
+
* peerCertDer: peerCertDer,
|
|
1145
|
+
* peerKemPubkey: peerKemPubkey,
|
|
1146
|
+
* });
|
|
1147
|
+
* typeof sealed;
|
|
1148
|
+
* // → "string" (base64 envelope)
|
|
1149
|
+
*/
|
|
504
1150
|
function encryptEnvelopeAsCertPeer(plaintext, opts) {
|
|
505
1151
|
if (!opts || typeof opts !== "object") {
|
|
506
1152
|
throw new Error("encryptEnvelopeAsCertPeer: opts object required");
|
|
@@ -534,6 +1180,35 @@ function encryptEnvelopeAsCertPeer(plaintext, opts) {
|
|
|
534
1180
|
// certPrivateKey: KeyObject | string, // this operator's cert P-384 priv
|
|
535
1181
|
// kemSecret: string, // this operator's ML-KEM-1024 priv PEM
|
|
536
1182
|
// });
|
|
1183
|
+
/**
|
|
1184
|
+
* @primitive b.crypto.decryptEnvelopeAsCertPeer
|
|
1185
|
+
* @signature b.crypto.decryptEnvelopeAsCertPeer(envelope, opts)
|
|
1186
|
+
* @since 0.7.0
|
|
1187
|
+
* @related b.crypto.encryptEnvelopeAsCertPeer, b.crypto.decrypt
|
|
1188
|
+
*
|
|
1189
|
+
* Decrypts an envelope sealed to this operator's TLS cert ECDH-pubkey
|
|
1190
|
+
* + ML-KEM-1024 pubkey. `certPrivateKey` accepts either a node:crypto
|
|
1191
|
+
* `KeyObject` (ECDH P-384, namedCurve secp384r1) or its PEM-encoded
|
|
1192
|
+
* pkcs8 string; `kemSecret` is always the ML-KEM-1024 PEM. A non-
|
|
1193
|
+
* P-384 cert key throws `crypto/cert-key-not-ecdh-p384`. Mirror of
|
|
1194
|
+
* `encryptEnvelopeAsCertPeer` for the receive side.
|
|
1195
|
+
*
|
|
1196
|
+
* @opts
|
|
1197
|
+
* certPrivateKey: object, // KeyObject or PEM string — ECDH P-384 priv (secp384r1)
|
|
1198
|
+
* kemSecret: string, // operator's ML-KEM-1024 priv PEM (non-empty)
|
|
1199
|
+
*
|
|
1200
|
+
* @example
|
|
1201
|
+
* var fs = require("fs");
|
|
1202
|
+
* var ourCertPriv = fs.readFileSync("/etc/ssl/our.cert.key.pem", "utf8");
|
|
1203
|
+
* var ourKemSecret = fs.readFileSync("/etc/ssl/our.mlkem.priv.pem", "utf8");
|
|
1204
|
+
* var sealed = "AaECA..."; // base64 envelope received from peer
|
|
1205
|
+
* var plain = b.crypto.decryptEnvelopeAsCertPeer(sealed, {
|
|
1206
|
+
* certPrivateKey: ourCertPriv,
|
|
1207
|
+
* kemSecret: ourKemSecret,
|
|
1208
|
+
* });
|
|
1209
|
+
* typeof plain;
|
|
1210
|
+
* // → "string"
|
|
1211
|
+
*/
|
|
537
1212
|
function decryptEnvelopeAsCertPeer(envelope, opts) {
|
|
538
1213
|
if (!opts || typeof opts !== "object") {
|
|
539
1214
|
throw new Error("decryptEnvelopeAsCertPeer: opts object required");
|
|
@@ -608,6 +1283,30 @@ function _pemToDer(pemOrDer) {
|
|
|
608
1283
|
}
|
|
609
1284
|
return Buffer.from(match[1].replace(/\s+/g, ""), "base64");
|
|
610
1285
|
}
|
|
1286
|
+
/**
|
|
1287
|
+
* @primitive b.crypto.hashCertFingerprint
|
|
1288
|
+
* @signature b.crypto.hashCertFingerprint(pemOrDer)
|
|
1289
|
+
* @since 0.7.0
|
|
1290
|
+
* @related b.crypto.isCertRevoked, b.crypto.sha3Hash
|
|
1291
|
+
*
|
|
1292
|
+
* Computes a stable SHA3-512 fingerprint of an X.509 certificate.
|
|
1293
|
+
* Accepts either DER bytes (Buffer) or a PEM string (BEGIN/END
|
|
1294
|
+
* envelope is stripped, base64 body decoded). Returns
|
|
1295
|
+
* `{ hex, colon }` so callers can compare against either rendering
|
|
1296
|
+
* style — lowercase hex (concise, log-friendly) or uppercase
|
|
1297
|
+
* colon-separated hex (matches `openssl x509 -fingerprint` output
|
|
1298
|
+
* shape). Use for peer-cert pinning, mTLS bootstrap allowlists,
|
|
1299
|
+
* webhook verification, certificate-transparency cross-checks.
|
|
1300
|
+
*
|
|
1301
|
+
* @example
|
|
1302
|
+
* var fs = require("fs");
|
|
1303
|
+
* var pem = fs.readFileSync("/etc/ssl/peer.cert.pem", "utf8");
|
|
1304
|
+
* var fp = b.crypto.hashCertFingerprint(pem);
|
|
1305
|
+
* fp.hex.length;
|
|
1306
|
+
* // → 128 (SHA3-512 = 64 bytes hex-encoded)
|
|
1307
|
+
* fp.colon.split(":").length;
|
|
1308
|
+
* // → 64 (one byte per group)
|
|
1309
|
+
*/
|
|
611
1310
|
function hashCertFingerprint(pemOrDer) {
|
|
612
1311
|
var der = _pemToDer(pemOrDer);
|
|
613
1312
|
var digest = hash(der, "sha3-512");
|
|
@@ -622,6 +1321,26 @@ function hashCertFingerprint(pemOrDer) {
|
|
|
622
1321
|
// fingerprints. Allowlist entries may be the colon form, the lower-
|
|
623
1322
|
// case hex form, or both — every comparison runs through
|
|
624
1323
|
// timingSafeEqual to avoid leaking which entry matched.
|
|
1324
|
+
/**
|
|
1325
|
+
* @primitive b.crypto.isCertRevoked
|
|
1326
|
+
* @signature b.crypto.isCertRevoked(pemOrDer, denyList)
|
|
1327
|
+
* @since 0.7.0
|
|
1328
|
+
* @related b.crypto.hashCertFingerprint, b.crypto.timingSafeEqual
|
|
1329
|
+
*
|
|
1330
|
+
* Returns `true` when the cert's SHA3-512 fingerprint matches any
|
|
1331
|
+
* entry in `denyList`. `denyList` entries may be the colon-separated
|
|
1332
|
+
* uppercase hex form, the lowercase hex form, or both — every
|
|
1333
|
+
* comparison runs through `crypto.timingSafeEqual` so the answer
|
|
1334
|
+
* doesn't leak which entry matched. Use for cert-transparency-style
|
|
1335
|
+
* deny lists, revoked-peer sweeps, or compromised-CA blocking.
|
|
1336
|
+
*
|
|
1337
|
+
* @example
|
|
1338
|
+
* var fs = require("fs");
|
|
1339
|
+
* var pem = fs.readFileSync("/etc/ssl/peer.cert.pem", "utf8");
|
|
1340
|
+
* var deny = ["DEADBEEF:CAFEBABE:1234:5678:..."];
|
|
1341
|
+
* var revoked = b.crypto.isCertRevoked(pem, deny);
|
|
1342
|
+
* // → false (when the fingerprint is not in the deny list)
|
|
1343
|
+
*/
|
|
625
1344
|
function isCertRevoked(pemOrDer, denyList) {
|
|
626
1345
|
if (!Array.isArray(denyList)) {
|
|
627
1346
|
throw new TypeError("crypto.isCertRevoked: denyList must be an array of fingerprint strings");
|
|
@@ -654,6 +1373,9 @@ module.exports = {
|
|
|
654
1373
|
// Hashing
|
|
655
1374
|
sha3Hash: sha3Hash,
|
|
656
1375
|
hmacSha3: hmacSha3,
|
|
1376
|
+
hashFile: hashFile,
|
|
1377
|
+
hashStream: hashStream,
|
|
1378
|
+
namespaceHash: namespaceHash,
|
|
657
1379
|
kdf: kdf,
|
|
658
1380
|
// Comparison
|
|
659
1381
|
timingSafeEqual: timingSafeEqual,
|