@blamejs/core 0.15.12 → 0.15.14
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 +4 -0
- package/index.js +2 -0
- package/lib/a2a-tasks.js +45 -29
- package/lib/acme.js +6 -5
- package/lib/agent-event-bus.js +18 -4
- package/lib/agent-idempotency.js +7 -6
- package/lib/agent-orchestrator.js +2 -5
- package/lib/agent-saga.js +3 -5
- package/lib/agent-snapshot.js +32 -2
- package/lib/agent-tenant.js +2 -5
- package/lib/ai-adverse-decision.js +2 -15
- package/lib/ai-aedt-bias-audit.js +2 -1
- package/lib/ai-capability.js +1 -6
- package/lib/ai-content-detect.js +1 -3
- package/lib/ai-dp.js +1 -5
- package/lib/ai-frontier-protocol.js +1 -1
- package/lib/ai-input.js +2 -2
- package/lib/ai-model-manifest.js +1 -1
- package/lib/ai-output.js +16 -7
- package/lib/ai-pref.js +3 -8
- package/lib/ai-quota.js +3 -14
- package/lib/api-key.js +37 -28
- package/lib/api-snapshot.js +4 -1
- package/lib/app-shutdown.js +7 -1
- package/lib/archive-adapters.js +2 -4
- package/lib/archive-entry-policy.js +32 -0
- package/lib/archive-gz.js +9 -0
- package/lib/archive-read.js +14 -24
- package/lib/archive-tar-read.js +56 -24
- package/lib/archive.js +6 -12
- package/lib/arg-parser.js +7 -6
- package/lib/asn1-der.js +70 -22
- package/lib/asyncapi-traits.js +2 -6
- package/lib/atomic-file.js +312 -33
- package/lib/audit-chain.js +183 -54
- package/lib/audit-daily-review.js +36 -16
- package/lib/audit-emit.js +82 -0
- package/lib/audit-sign.js +258 -0
- package/lib/audit-tools.js +108 -23
- package/lib/audit.js +91 -2
- package/lib/auth/access-lock.js +7 -29
- package/lib/auth/bot-challenge.js +1 -1
- package/lib/auth/ciba.js +43 -4
- package/lib/auth/dpop.js +29 -36
- package/lib/auth/fido-mds3.js +14 -16
- package/lib/auth/jwt-external.js +26 -8
- package/lib/auth/jwt.js +15 -17
- package/lib/auth/lockout.js +24 -27
- package/lib/auth/oauth.js +67 -45
- package/lib/auth/oid4vci.js +55 -32
- package/lib/auth/oid4vp.js +3 -2
- package/lib/auth/openid-federation.js +6 -6
- package/lib/auth/passkey.js +4 -4
- package/lib/auth/password.js +8 -2
- package/lib/auth/saml.js +70 -40
- package/lib/auth/sd-jwt-vc-holder.js +2 -14
- package/lib/auth/sd-jwt-vc-issuer.js +2 -14
- package/lib/auth/sd-jwt-vc.js +25 -5
- package/lib/auth/status-list.js +21 -9
- package/lib/auth/step-up.js +15 -13
- package/lib/auth-bot-challenge.js +27 -39
- package/lib/backup/bundle.js +18 -20
- package/lib/backup/crypto.js +23 -8
- package/lib/backup/index.js +55 -67
- package/lib/backup/manifest.js +7 -1
- package/lib/bounded-map.js +112 -1
- package/lib/breach-deadline.js +1 -11
- package/lib/break-glass.js +41 -22
- package/lib/cache.js +7 -18
- package/lib/cbor.js +36 -12
- package/lib/cdn-cache-control.js +15 -12
- package/lib/cert.js +8 -13
- package/lib/chain-writer.js +162 -47
- package/lib/cli.js +10 -2
- package/lib/client-hints.js +7 -9
- package/lib/cloud-events.js +43 -33
- package/lib/cluster-storage.js +9 -41
- package/lib/cms-codec.js +2 -1
- package/lib/codepoint-class.js +84 -1
- package/lib/compliance-ai-act-logging.js +1 -6
- package/lib/compliance-ai-act-transparency.js +2 -4
- package/lib/compliance-eaa.js +2 -12
- package/lib/compliance-sanctions-fetcher.js +21 -28
- package/lib/compliance-sanctions.js +11 -21
- package/lib/compliance.js +3 -10
- package/lib/config-drift.js +24 -20
- package/lib/content-credentials.js +11 -8
- package/lib/content-digest.js +21 -12
- package/lib/cookies.js +15 -13
- package/lib/cose.js +39 -11
- package/lib/cra-report.js +1 -11
- package/lib/crdt.js +2 -1
- package/lib/crypto-field.js +34 -33
- package/lib/crypto.js +138 -7
- package/lib/csp.js +239 -3
- package/lib/daemon.js +46 -42
- package/lib/data-act.js +46 -13
- package/lib/db-file-lifecycle.js +14 -6
- package/lib/db-query.js +75 -49
- package/lib/db.js +66 -27
- package/lib/dbsc.js +5 -6
- package/lib/ddl-change-control.js +18 -14
- package/lib/deprecate.js +4 -5
- package/lib/did.js +6 -2
- package/lib/dora.js +43 -13
- package/lib/dr-runbook.js +1 -1
- package/lib/dsa.js +2 -1
- package/lib/dsr.js +30 -20
- package/lib/early-hints.js +19 -0
- package/lib/eat.js +5 -1
- package/lib/external-db-migrate.js +21 -22
- package/lib/external-db.js +74 -30
- package/lib/fda-21cfr11.js +42 -13
- package/lib/fdx.js +22 -18
- package/lib/file-upload.js +80 -66
- package/lib/flag-evaluation-context.js +5 -8
- package/lib/flag-providers.js +6 -2
- package/lib/forms.js +1 -1
- package/lib/framework-error.js +12 -0
- package/lib/framework-schema.js +19 -0
- package/lib/fsm.js +7 -12
- package/lib/gate-contract.js +911 -39
- package/lib/gdpr-ropa.js +22 -22
- package/lib/graphql-federation.js +18 -5
- package/lib/guard-agent-registry.js +2 -1
- package/lib/guard-all.js +3 -2
- package/lib/guard-archive.js +9 -30
- package/lib/guard-auth.js +23 -80
- package/lib/guard-cidr.js +20 -96
- package/lib/guard-csv.js +135 -196
- package/lib/guard-domain.js +23 -106
- package/lib/guard-dsn.js +17 -14
- package/lib/guard-email.js +46 -53
- package/lib/guard-envelope.js +2 -2
- package/lib/guard-event-bus-topic.js +2 -1
- package/lib/guard-filename.js +12 -60
- package/lib/guard-graphql.js +28 -75
- package/lib/guard-html-wcag.js +15 -2
- package/lib/guard-html.js +74 -128
- package/lib/guard-idempotency-key.js +2 -1
- package/lib/guard-image.js +280 -77
- package/lib/guard-imap-command.js +9 -10
- package/lib/guard-jmap.js +1 -1
- package/lib/guard-json.js +101 -109
- package/lib/guard-jsonpath.js +20 -88
- package/lib/guard-jwt.js +32 -114
- package/lib/guard-list-id.js +2 -7
- package/lib/guard-list-unsubscribe.js +2 -7
- package/lib/guard-mail-compose.js +5 -6
- package/lib/guard-mail-move.js +3 -2
- package/lib/guard-mail-query.js +5 -3
- package/lib/guard-mail-sieve.js +6 -7
- package/lib/guard-managesieve-command.js +6 -5
- package/lib/guard-markdown.js +76 -110
- package/lib/guard-message-id.js +5 -6
- package/lib/guard-mime.js +20 -99
- package/lib/guard-oauth.js +19 -73
- package/lib/guard-pdf.js +65 -72
- package/lib/guard-pop3-command.js +13 -14
- package/lib/guard-posture-chain.js +2 -1
- package/lib/guard-regex.js +24 -99
- package/lib/guard-saga-config.js +2 -1
- package/lib/guard-shell.js +22 -87
- package/lib/guard-smtp-command.js +9 -12
- package/lib/guard-sql.js +15 -13
- package/lib/guard-stream-args.js +2 -1
- package/lib/guard-svg.js +103 -149
- package/lib/guard-template.js +23 -80
- package/lib/guard-tenant-id.js +2 -1
- package/lib/guard-text.js +592 -0
- package/lib/guard-time.js +27 -95
- package/lib/guard-uuid.js +21 -93
- package/lib/guard-xml.js +76 -106
- package/lib/guard-yaml.js +24 -60
- package/lib/html-balance.js +7 -3
- package/lib/http-client-cache.js +5 -12
- package/lib/http-client-cookie-jar.js +35 -16
- package/lib/http-client.js +233 -74
- package/lib/http-message-signature.js +3 -2
- package/lib/i18n-messageformat.js +5 -7
- package/lib/i18n.js +83 -26
- package/lib/iab-tcf.js +3 -2
- package/lib/importmap-integrity.js +41 -1
- package/lib/inbox.js +8 -8
- package/lib/incident-report.js +12 -27
- package/lib/ip-utils.js +49 -6
- package/lib/jobs.js +3 -2
- package/lib/json-patch.js +1 -1
- package/lib/json-path.js +24 -3
- package/lib/jtd.js +2 -2
- package/lib/keychain.js +6 -18
- package/lib/legal-hold.js +30 -23
- package/lib/log-stream-cloudwatch.js +44 -112
- package/lib/log-stream-otlp-grpc.js +17 -14
- package/lib/log-stream-otlp.js +16 -80
- package/lib/log-stream-webhook.js +20 -92
- package/lib/log.js +2 -2
- package/lib/mail-agent.js +2 -2
- package/lib/mail-arc-sign.js +8 -3
- package/lib/mail-arf.js +4 -3
- package/lib/mail-auth.js +43 -69
- package/lib/mail-bimi.js +35 -55
- package/lib/mail-bounce.js +3 -3
- package/lib/mail-crypto-pgp.js +3 -2
- package/lib/mail-crypto-smime.js +71 -6
- package/lib/mail-dav.js +8 -35
- package/lib/mail-deploy.js +15 -14
- package/lib/mail-dkim.js +19 -38
- package/lib/mail-greylist.js +17 -30
- package/lib/mail-helo.js +4 -7
- package/lib/mail-journal.js +13 -9
- package/lib/mail-mdn.js +12 -7
- package/lib/mail-rbl.js +7 -12
- package/lib/mail-scan.js +5 -7
- package/lib/mail-send-deliver.js +33 -20
- package/lib/mail-server-imap.js +32 -87
- package/lib/mail-server-jmap.js +39 -52
- package/lib/mail-server-managesieve.js +29 -83
- package/lib/mail-server-mx.js +33 -74
- package/lib/mail-server-net.js +177 -0
- package/lib/mail-server-pop3.js +30 -83
- package/lib/mail-server-rate-limit.js +30 -6
- package/lib/mail-server-registry.js +1 -1
- package/lib/mail-server-submission.js +34 -73
- package/lib/mail-server-tls.js +98 -2
- package/lib/mail-spam-score.js +9 -12
- package/lib/mail-store-fts.js +1 -1
- package/lib/mail-store.js +3 -2
- package/lib/mail.js +28 -13
- package/lib/markup-escape.js +31 -0
- package/lib/markup-tokenizer.js +78 -0
- package/lib/mcp-tool-registry.js +26 -23
- package/lib/mcp.js +7 -5
- package/lib/mdoc.js +36 -14
- package/lib/metrics.js +46 -33
- package/lib/middleware/age-gate.js +3 -23
- package/lib/middleware/api-encrypt.js +13 -24
- package/lib/middleware/assetlinks.js +2 -7
- package/lib/middleware/bearer-auth.js +2 -1
- package/lib/middleware/body-parser.js +41 -52
- package/lib/middleware/bot-disclose.js +7 -10
- package/lib/middleware/bot-guard.js +28 -28
- package/lib/middleware/clear-site-data.js +5 -1
- package/lib/middleware/compression.js +9 -0
- package/lib/middleware/cors.js +32 -23
- package/lib/middleware/csrf-protect.js +73 -23
- package/lib/middleware/daily-byte-quota.js +12 -30
- package/lib/middleware/deny-response.js +19 -1
- package/lib/middleware/fetch-metadata.js +35 -4
- package/lib/middleware/idempotency-key.js +4 -20
- package/lib/middleware/network-allowlist.js +61 -30
- package/lib/middleware/rate-limit.js +45 -44
- package/lib/middleware/scim-server.js +5 -3
- package/lib/middleware/security-headers.js +33 -7
- package/lib/middleware/security-txt.js +2 -6
- package/lib/middleware/speculation-rules.js +6 -3
- package/lib/middleware/tus-upload.js +14 -29
- package/lib/middleware/web-app-manifest.js +2 -7
- package/lib/migrations.js +4 -13
- package/lib/mime-parse.js +34 -17
- package/lib/module-loader.js +44 -0
- package/lib/money.js +105 -0
- package/lib/mtls-ca.js +125 -41
- package/lib/network-byte-quota.js +4 -18
- package/lib/network-dane.js +8 -6
- package/lib/network-dns-resolver.js +98 -7
- package/lib/network-dns.js +9 -2
- package/lib/network-dnssec.js +18 -17
- package/lib/network-heartbeat.js +3 -2
- package/lib/network-smtp-policy.js +52 -46
- package/lib/network-tls.js +67 -47
- package/lib/network-tsig.js +2 -2
- package/lib/nis2-report.js +2 -12
- package/lib/nist-crosswalk.js +1 -1
- package/lib/nonce-store.js +81 -3
- package/lib/ntp-check.js +28 -0
- package/lib/numeric-bounds.js +31 -8
- package/lib/object-store/azure-blob-bucket-ops.js +2 -6
- package/lib/object-store/azure-blob.js +6 -47
- package/lib/object-store/gcs-bucket-ops.js +4 -2
- package/lib/object-store/gcs.js +14 -75
- package/lib/object-store/http-put.js +2 -7
- package/lib/object-store/http-request.js +157 -0
- package/lib/object-store/local.js +37 -17
- package/lib/object-store/sigv4-bucket-ops.js +2 -1
- package/lib/object-store/sigv4.js +10 -79
- package/lib/observability-otlp-exporter.js +29 -29
- package/lib/observability.js +95 -0
- package/lib/openapi-paths-builder.js +7 -3
- package/lib/otel-export.js +7 -10
- package/lib/outbox.js +54 -41
- package/lib/pagination.js +11 -15
- package/lib/parsers/safe-env.js +5 -4
- package/lib/parsers/safe-ini.js +10 -4
- package/lib/parsers/safe-toml.js +11 -9
- package/lib/parsers/safe-xml.js +3 -3
- package/lib/parsers/safe-yaml.js +28 -9
- package/lib/pick.js +63 -5
- package/lib/pipl-cn.js +69 -59
- package/lib/privacy-pass.js +8 -6
- package/lib/problem-details.js +2 -5
- package/lib/pubsub.js +4 -8
- package/lib/queue-local.js +10 -3
- package/lib/redact.js +9 -4
- package/lib/render.js +4 -2
- package/lib/request-helpers.js +334 -29
- package/lib/resource-access-lock.js +3 -3
- package/lib/restore-bundle.js +46 -18
- package/lib/restore-rollback.js +10 -4
- package/lib/restore.js +24 -22
- package/lib/retention.js +23 -9
- package/lib/router.js +17 -4
- package/lib/safe-async.js +457 -0
- package/lib/safe-buffer.js +137 -6
- package/lib/safe-decompress.js +2 -1
- package/lib/safe-dns.js +2 -1
- package/lib/safe-ical.js +14 -28
- package/lib/safe-icap.js +1 -1
- package/lib/safe-json.js +51 -8
- package/lib/safe-jsonpath.js +6 -5
- package/lib/safe-mime.js +25 -29
- package/lib/safe-redirect.js +2 -5
- package/lib/safe-schema.js +7 -6
- package/lib/safe-sieve.js +1 -1
- package/lib/safe-sql.js +126 -0
- package/lib/safe-vcard.js +13 -27
- package/lib/sandbox-worker.js +15 -2
- package/lib/sandbox.js +4 -3
- package/lib/scheduler.js +22 -13
- package/lib/sec-cyber.js +82 -37
- package/lib/seeders.js +4 -11
- package/lib/self-update-standalone-verifier.js +16 -0
- package/lib/self-update.js +92 -69
- package/lib/session-device-binding.js +112 -66
- package/lib/session.js +219 -7
- package/lib/sql.js +228 -81
- package/lib/sse.js +6 -10
- package/lib/static.js +201 -111
- package/lib/storage.js +4 -2
- package/lib/structured-fields.js +275 -16
- package/lib/template.js +7 -5
- package/lib/tenant-quota.js +53 -38
- package/lib/tsa.js +16 -17
- package/lib/validate-opts.js +154 -8
- package/lib/vault/index.js +5 -0
- package/lib/vault/passphrase-ops.js +22 -26
- package/lib/vault/passphrase-source.js +8 -3
- package/lib/vault/rotate.js +13 -18
- package/lib/vault/seal-pem-file.js +34 -49
- package/lib/vault-aad.js +3 -2
- package/lib/vc.js +3 -8
- package/lib/vendor/MANIFEST.json +10 -10
- package/lib/vendor/public-suffix-list.dat +23 -10
- package/lib/vendor/public-suffix-list.data.js +5498 -5494
- package/lib/vex.js +35 -13
- package/lib/web-push-vapid.js +23 -20
- package/lib/webhook-dispatcher.js +706 -0
- package/lib/webhook.js +59 -19
- package/lib/websocket-channels.js +9 -7
- package/lib/websocket.js +8 -4
- package/lib/worm.js +3 -4
- package/lib/ws-client.js +65 -56
- package/lib/x509-chain.js +44 -0
- package/package.json +1 -1
- package/sbom.cdx.json +6 -6
package/lib/mtls-ca.js
CHANGED
|
@@ -58,6 +58,9 @@ var nodeCrypto = require("node:crypto");
|
|
|
58
58
|
var atomicFile = require("./atomic-file");
|
|
59
59
|
var C = require("./constants");
|
|
60
60
|
var lazyRequire = require("./lazy-require");
|
|
61
|
+
// Lazy — the SHA3-512 fingerprint surfaced from issuance must match the one
|
|
62
|
+
// the require-mtls gate pins (b.crypto.sha3Hash(certPem)).
|
|
63
|
+
var bCrypto = lazyRequire(function () { return require("./crypto"); });
|
|
61
64
|
var { boot } = require("./log");
|
|
62
65
|
var safeBuffer = require("./safe-buffer");
|
|
63
66
|
var safeJson = require("./safe-json");
|
|
@@ -193,7 +196,7 @@ function create(opts) {
|
|
|
193
196
|
opts = opts || {};
|
|
194
197
|
validateOpts(opts, [
|
|
195
198
|
"dataDir", "paths", "vault",
|
|
196
|
-
"caKeySealedMode", "generation", "engine",
|
|
199
|
+
"caKeySealedMode", "generation", "engine", "revocationStore",
|
|
197
200
|
], "b.mtlsCa");
|
|
198
201
|
validateOpts.requireNonEmptyString(opts.dataDir, "mtlsCa.create: opts.dataDir", MtlsCaError, "mtls-ca/no-datadir");
|
|
199
202
|
// Auto-create the dataDir with restrictive perms (CA keys live here).
|
|
@@ -208,7 +211,7 @@ function create(opts) {
|
|
|
208
211
|
var paths = _resolvePaths(opts.dataDir, opts.paths);
|
|
209
212
|
var vault = opts.vault || null;
|
|
210
213
|
var caKeySealedMode = (opts.caKeySealedMode || "required").toLowerCase();
|
|
211
|
-
if (!VALID_SEAL_MODES
|
|
214
|
+
if (!Object.prototype.hasOwnProperty.call(VALID_SEAL_MODES, caKeySealedMode)) {
|
|
212
215
|
throw new MtlsCaError("mtls-ca/bad-mode",
|
|
213
216
|
"caKeySealedMode must be 'required' or 'disabled' " +
|
|
214
217
|
"(legacy 'auto' was removed — it defaulted to plaintext-on-disk)");
|
|
@@ -243,7 +246,7 @@ function create(opts) {
|
|
|
243
246
|
current: generation,
|
|
244
247
|
};
|
|
245
248
|
}
|
|
246
|
-
var pem =
|
|
249
|
+
var pem = atomicFile.fdSafeReadSync(paths.caCert, { maxBytes: C.BYTES.mib(1) });
|
|
247
250
|
var gen = parseGeneration(pem);
|
|
248
251
|
return {
|
|
249
252
|
exists: true,
|
|
@@ -269,7 +272,9 @@ function create(opts) {
|
|
|
269
272
|
"CA_KEY_SEALED='required' but " + paths.caKeySealed + " does not exist");
|
|
270
273
|
}
|
|
271
274
|
_requireVault("sealed CA key load");
|
|
272
|
-
|
|
275
|
+
// Cap + fd-bound CA-private-key read. NO refuseSymlink: caKeySealed may be
|
|
276
|
+
// an operator-absolute path on a k8s/KMS secret volume that symlinks it.
|
|
277
|
+
var sealedBytes = atomicFile.fdSafeReadSync(paths.caKeySealed, { maxBytes: C.BYTES.kib(64), encoding: "utf8" }).trim();
|
|
273
278
|
var pem = vault.unseal(sealedBytes);
|
|
274
279
|
if (!pem) {
|
|
275
280
|
throw new MtlsCaError("mtls-ca/unseal-failed",
|
|
@@ -282,7 +287,9 @@ function create(opts) {
|
|
|
282
287
|
throw new MtlsCaError("mtls-ca/plain-required",
|
|
283
288
|
"caKeySealedMode='disabled' but " + paths.caKey + " does not exist");
|
|
284
289
|
}
|
|
285
|
-
|
|
290
|
+
// Cap + fd-bound plaintext CA-private-key read (disabled mode = dev opt-out).
|
|
291
|
+
// NO refuseSymlink (operator-absolute path may symlink).
|
|
292
|
+
return atomicFile.fdSafeReadSync(paths.caKey, { maxBytes: C.BYTES.kib(64) });
|
|
286
293
|
}
|
|
287
294
|
|
|
288
295
|
function loadCert() {
|
|
@@ -290,7 +297,7 @@ function create(opts) {
|
|
|
290
297
|
throw new MtlsCaError("mtls-ca/missing-cert",
|
|
291
298
|
"no CA cert on disk at " + paths.caCert);
|
|
292
299
|
}
|
|
293
|
-
return
|
|
300
|
+
return atomicFile.fdSafeReadSync(paths.caCert, { maxBytes: C.BYTES.mib(1) });
|
|
294
301
|
}
|
|
295
302
|
|
|
296
303
|
// Atomic commit: write .tmp + atomic rename for both key and cert.
|
|
@@ -370,6 +377,29 @@ function create(opts) {
|
|
|
370
377
|
return fresh;
|
|
371
378
|
}
|
|
372
379
|
|
|
380
|
+
// Recover the issued certificate's identity from its PEM so issuance and
|
|
381
|
+
// any later revocation/indexing share identifiers without a round-trip back
|
|
382
|
+
// through an X.509 parser. serialNumber is normalized to the same hex form
|
|
383
|
+
// revoke() stores; fingerprint is the SHA3-512 the require-mtls gate pins.
|
|
384
|
+
function _certIdentity(certPem) {
|
|
385
|
+
// serialNumber comes from an X.509 parse, which is best-effort: a custom
|
|
386
|
+
// engine (or a test double) may return a cert in a shape this Node build
|
|
387
|
+
// cannot parse as X.509. The fingerprint is a hash of the returned bytes,
|
|
388
|
+
// so it is always available and is what the require-mtls gate pins —
|
|
389
|
+
// revoke()/isRevoked() by fingerprint keep working even when the serial
|
|
390
|
+
// can't be recovered. Never let optional identity enrichment crash issuance.
|
|
391
|
+
var serialNumber = null;
|
|
392
|
+
try {
|
|
393
|
+
serialNumber = _normalizeSerial(new nodeCrypto.X509Certificate(certPem).serialNumber);
|
|
394
|
+
} catch (_e) {
|
|
395
|
+
serialNumber = null;
|
|
396
|
+
}
|
|
397
|
+
return {
|
|
398
|
+
serialNumber: serialNumber,
|
|
399
|
+
fingerprint: bCrypto().sha3Hash(certPem),
|
|
400
|
+
};
|
|
401
|
+
}
|
|
402
|
+
|
|
373
403
|
async function generateClientCert(opts2) {
|
|
374
404
|
opts2 = opts2 || {};
|
|
375
405
|
var ca = await initCA();
|
|
@@ -379,7 +409,10 @@ function create(opts) {
|
|
|
379
409
|
throw new MtlsCaError("mtls-ca/bad-engine-output",
|
|
380
410
|
"engine.signClientCert must return { cert, key, ca?, issuedAt?, expiresAt? }");
|
|
381
411
|
}
|
|
382
|
-
|
|
412
|
+
// Surface the issued serial + fingerprint so the caller can track/revoke
|
|
413
|
+
// the cert by the same identifiers without re-parsing the PEM.
|
|
414
|
+
var id = _certIdentity(result.cert);
|
|
415
|
+
return Object.assign({}, result, { serialNumber: id.serialNumber, fingerprint: id.fingerprint });
|
|
383
416
|
}
|
|
384
417
|
|
|
385
418
|
async function generateClientP12(opts2) {
|
|
@@ -395,32 +428,63 @@ function create(opts) {
|
|
|
395
428
|
throw new MtlsCaError("mtls-ca/bad-engine-output",
|
|
396
429
|
"engine.packageP12 must return { p12: Buffer, certPem, issuedAt, expiresAt }");
|
|
397
430
|
}
|
|
431
|
+
if (typeof result.certPem === "string") {
|
|
432
|
+
var id12 = _certIdentity(result.certPem);
|
|
433
|
+
return Object.assign({}, result, { serialNumber: id12.serialNumber, fingerprint: id12.fingerprint });
|
|
434
|
+
}
|
|
398
435
|
return result;
|
|
399
436
|
}
|
|
400
437
|
|
|
401
438
|
// ---- Revocation registry + CRL ----
|
|
402
439
|
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
440
|
+
// Revocation entries are read + written through a store so the registry can
|
|
441
|
+
// live somewhere other than the default plaintext revocations.json — e.g. an
|
|
442
|
+
// operator-supplied encrypted / clustered store (the bring-your-own-store
|
|
443
|
+
// precedent b.queue's config.db set). Contract (sync):
|
|
444
|
+
// list() -> array of revocation entries
|
|
445
|
+
// add(entry) -> append one entry (the caller has already deduped)
|
|
446
|
+
function _defaultFileStore() {
|
|
447
|
+
function _list() {
|
|
448
|
+
if (!nodeFs.existsSync(paths.revocations)) return [];
|
|
449
|
+
try {
|
|
450
|
+
// safeJson.parse caps depth + size + protects against
|
|
451
|
+
// proto-pollution; a tampered or truncated file shouldn't be able to
|
|
452
|
+
// corrupt the rotator process.
|
|
453
|
+
var json = safeJson.parse(atomicFile.fdSafeReadSync(paths.revocations, { maxBytes: C.BYTES.mib(16), encoding: "utf8" }),
|
|
454
|
+
{ maxBytes: C.BYTES.mib(16) });
|
|
455
|
+
return (json && Array.isArray(json.revocations)) ? json.revocations : [];
|
|
456
|
+
} catch (e) {
|
|
457
|
+
throw new MtlsCaError("mtls-ca/revocation-corrupt",
|
|
458
|
+
"could not parse " + paths.revocations + ": " + ((e && e.message) || String(e)));
|
|
459
|
+
}
|
|
418
460
|
}
|
|
461
|
+
return {
|
|
462
|
+
list: _list,
|
|
463
|
+
add: function (entry) {
|
|
464
|
+
var entries = _list();
|
|
465
|
+
entries.push(entry);
|
|
466
|
+
atomicFile.writeSync(paths.revocations,
|
|
467
|
+
JSON.stringify({ revocations: entries }, null, 2) + "\n", { mode: 0o600 });
|
|
468
|
+
},
|
|
469
|
+
};
|
|
419
470
|
}
|
|
471
|
+
var revocationStore = opts.revocationStore || _defaultFileStore();
|
|
472
|
+
validateOpts.requireMethods(revocationStore, ["list", "add"],
|
|
473
|
+
"opts.revocationStore", MtlsCaError, "mtls-ca/bad-revocation-store");
|
|
420
474
|
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
475
|
+
// A fingerprint is the SHA3-512 hex the require-mtls gate pins. Normalize it
|
|
476
|
+
// like a serial (strip 0x / separators / whitespace, lowercase, hex-validate)
|
|
477
|
+
// so a consumer can revoke by the same value the gate compares against.
|
|
478
|
+
function _normalizeFingerprint(fp) {
|
|
479
|
+
if (!fp || typeof fp !== "string") {
|
|
480
|
+
throw new MtlsCaError("mtls-ca/bad-fingerprint", "fingerprint must be a non-empty string");
|
|
481
|
+
}
|
|
482
|
+
var stripped = fp.replace(/^0x/i, "").replace(/[:\-\s]/g, "");
|
|
483
|
+
if (!safeBuffer.isHex(stripped)) {
|
|
484
|
+
throw new MtlsCaError("mtls-ca/bad-fingerprint",
|
|
485
|
+
"fingerprint contains non-hex characters: " + JSON.stringify(fp));
|
|
486
|
+
}
|
|
487
|
+
return stripped.toLowerCase();
|
|
424
488
|
}
|
|
425
489
|
|
|
426
490
|
function _normalizeSerial(s) {
|
|
@@ -466,46 +530,66 @@ function create(opts) {
|
|
|
466
530
|
"aACompromise": 10,
|
|
467
531
|
};
|
|
468
532
|
|
|
469
|
-
function revoke(
|
|
470
|
-
|
|
533
|
+
function revoke(idOrOpts, opts3) {
|
|
534
|
+
// Accept either revoke(serialString, { reason, fingerprint }) — the
|
|
535
|
+
// backward-compatible serial-keyed form — or revoke({ serial?,
|
|
536
|
+
// fingerprint?, reason? }). The require-mtls gate denies by fingerprint,
|
|
537
|
+
// so a fingerprint-indexed consumer can revoke by the same value it pins
|
|
538
|
+
// on; serial-keyed behavior stays the default. At least one key required.
|
|
539
|
+
var spec = (idOrOpts && typeof idOrOpts === "object") ? idOrOpts : null;
|
|
471
540
|
opts3 = opts3 || {};
|
|
472
|
-
var
|
|
541
|
+
var serialIn = spec ? spec.serial : idOrOpts;
|
|
542
|
+
var fingerprintIn = spec ? spec.fingerprint : opts3.fingerprint;
|
|
543
|
+
var reasonName = (spec ? spec.reason : opts3.reason) || "unspecified";
|
|
544
|
+
|
|
545
|
+
var serial = (serialIn !== undefined && serialIn !== null) ? _normalizeSerial(serialIn) : null;
|
|
546
|
+
var fingerprint = (fingerprintIn !== undefined && fingerprintIn !== null)
|
|
547
|
+
? _normalizeFingerprint(fingerprintIn) : null;
|
|
548
|
+
if (!serial && !fingerprint) {
|
|
549
|
+
throw new MtlsCaError("mtls-ca/no-revocation-key",
|
|
550
|
+
"revoke requires a serial number or a fingerprint " +
|
|
551
|
+
"(revoke(serial, opts) or revoke({ serial, fingerprint }))");
|
|
552
|
+
}
|
|
473
553
|
var reasonCode = CRL_REASON_BY_NAME[reasonName];
|
|
474
554
|
if (reasonCode === undefined) {
|
|
475
555
|
throw new MtlsCaError("mtls-ca/bad-reason",
|
|
476
556
|
"revoke: unknown reason '" + reasonName + "' (valid: " +
|
|
477
557
|
Object.keys(CRL_REASON_BY_NAME).join(", ") + ")");
|
|
478
558
|
}
|
|
479
|
-
var
|
|
480
|
-
|
|
481
|
-
return r.serialNumber === serial;
|
|
559
|
+
var existing = revocationStore.list().find(function (r) {
|
|
560
|
+
return (serial && r.serialNumber === serial) || (fingerprint && r.fingerprint === fingerprint);
|
|
482
561
|
});
|
|
483
562
|
if (existing) {
|
|
484
|
-
// Idempotent — repeated revoke() of the same serial doesn't
|
|
563
|
+
// Idempotent — repeated revoke() of the same serial/fingerprint doesn't
|
|
485
564
|
// shift the revokedAt timestamp.
|
|
486
565
|
return existing;
|
|
487
566
|
}
|
|
488
567
|
var entry = {
|
|
489
568
|
serialNumber: serial,
|
|
569
|
+
fingerprint: fingerprint,
|
|
490
570
|
reason: reasonName,
|
|
491
571
|
reasonCode: reasonCode,
|
|
492
572
|
revokedAt: Date.now(),
|
|
493
573
|
};
|
|
494
|
-
|
|
495
|
-
_saveRevocations(state);
|
|
574
|
+
revocationStore.add(entry);
|
|
496
575
|
return entry;
|
|
497
576
|
}
|
|
498
577
|
|
|
499
|
-
function isRevoked(
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
578
|
+
function isRevoked(serialOrFingerprint) {
|
|
579
|
+
// Accept a serial number OR a SHA3-512 fingerprint — both are hex, so one
|
|
580
|
+
// normalized form is matched against either key each entry carries.
|
|
581
|
+
if (!serialOrFingerprint || typeof serialOrFingerprint !== "string") {
|
|
582
|
+
throw new MtlsCaError("mtls-ca/bad-revocation-key",
|
|
583
|
+
"isRevoked requires a serial number or a fingerprint (hex string)");
|
|
584
|
+
}
|
|
585
|
+
var norm = _normalizeFingerprint(serialOrFingerprint);
|
|
586
|
+
return revocationStore.list().some(function (r) {
|
|
587
|
+
return r.serialNumber === norm || r.fingerprint === norm;
|
|
504
588
|
});
|
|
505
589
|
}
|
|
506
590
|
|
|
507
591
|
function getRevocations() {
|
|
508
|
-
return
|
|
592
|
+
return revocationStore.list().slice();
|
|
509
593
|
}
|
|
510
594
|
|
|
511
595
|
// Generate a signed X.509 CRL covering every entry in the registry.
|
|
@@ -521,7 +605,7 @@ function create(opts) {
|
|
|
521
605
|
"framework's bundled CA engine, which supports it");
|
|
522
606
|
}
|
|
523
607
|
var ca = await initCA();
|
|
524
|
-
var revocations =
|
|
608
|
+
var revocations = revocationStore.list();
|
|
525
609
|
var nowMs = Date.now();
|
|
526
610
|
var thisUpdate = opts3.thisUpdate || new Date(nowMs);
|
|
527
611
|
var nextUpdate = opts3.nextUpdate ||
|
|
@@ -39,6 +39,7 @@
|
|
|
39
39
|
*/
|
|
40
40
|
|
|
41
41
|
var C = require("./constants");
|
|
42
|
+
var boundedMap = require("./bounded-map");
|
|
42
43
|
var defineClass = require("./framework-error").defineClass;
|
|
43
44
|
var lazyRequire = require("./lazy-require");
|
|
44
45
|
var validateOpts = require("./validate-opts");
|
|
@@ -79,9 +80,7 @@ function _slideAndSum(entry, nowHour) {
|
|
|
79
80
|
function _memoryBackend() {
|
|
80
81
|
var store = new Map();
|
|
81
82
|
function _get(key) {
|
|
82
|
-
|
|
83
|
-
if (!entry) { entry = _newEntry(); store.set(key, entry); }
|
|
84
|
-
return entry;
|
|
83
|
+
return boundedMap.getOrInsert(store, key, function () { return _newEntry(); });
|
|
85
84
|
}
|
|
86
85
|
return {
|
|
87
86
|
async total(key, nowMs) {
|
|
@@ -172,27 +171,14 @@ function create(opts) {
|
|
|
172
171
|
validateOpts(opts, ["bytesPerDay", "cache", "audit", "now"], "network.byteQuota");
|
|
173
172
|
_requirePositiveBytes("bytesPerDay", opts.bytesPerDay);
|
|
174
173
|
var bytesPerDay = opts.bytesPerDay;
|
|
175
|
-
var auditOn = opts.audit !== false;
|
|
176
174
|
var now = typeof opts.now === "function" ? opts.now : function () { return Date.now(); };
|
|
177
175
|
var backend = opts.cache && typeof opts.cache.get === "function"
|
|
178
176
|
? _cacheBackend(opts.cache)
|
|
179
177
|
: _memoryBackend();
|
|
180
178
|
|
|
181
|
-
|
|
182
|
-
if (!auditOn) return;
|
|
183
|
-
try {
|
|
184
|
-
audit().safeEmit({
|
|
185
|
-
action: "network.byte_quota." + action,
|
|
186
|
-
outcome: outcome,
|
|
187
|
-
metadata: metadata || {},
|
|
188
|
-
});
|
|
189
|
-
} catch (_e) { /* drop-silent — audit is best-effort */ }
|
|
190
|
-
}
|
|
179
|
+
var _emitAudit = audit().namespaced("network.byte_quota", opts.audit);
|
|
191
180
|
|
|
192
|
-
|
|
193
|
-
try { observability().safeEvent("network.byte_quota." + verb, n || 1, labels || {}); }
|
|
194
|
-
catch (_e) { /* drop-silent */ }
|
|
195
|
-
}
|
|
181
|
+
var _emitMetric = observability().namespaced("network.byte_quota");
|
|
196
182
|
|
|
197
183
|
// check(key, bytes) — preflight without mutation. Returns
|
|
198
184
|
// { allowed, total, remaining, quota, retryAfterSec, degraded }
|
package/lib/network-dane.js
CHANGED
|
@@ -31,6 +31,7 @@
|
|
|
31
31
|
|
|
32
32
|
var nodeCrypto = require("node:crypto");
|
|
33
33
|
var bCrypto = require("./crypto");
|
|
34
|
+
var safeBuffer = require("./safe-buffer");
|
|
34
35
|
var validateOpts = require("./validate-opts");
|
|
35
36
|
var { defineClass } = require("./framework-error");
|
|
36
37
|
|
|
@@ -44,12 +45,13 @@ var SELECTORS = { 0: "Cert", 1: "SPKI" };
|
|
|
44
45
|
// refused rather than guessed.
|
|
45
46
|
var MATCHING = { 0: null, 1: "sha256", 2: "sha512" };
|
|
46
47
|
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
48
|
+
var _bytes = safeBuffer.makeByteCoercer({
|
|
49
|
+
errorClass: DaneError,
|
|
50
|
+
typeCode: "dane/bad-bytes",
|
|
51
|
+
messagePrefix: "dane: ",
|
|
52
|
+
messageSuffix: " must be a Buffer / Uint8Array / hex string",
|
|
53
|
+
encoding: "hex",
|
|
54
|
+
});
|
|
53
55
|
|
|
54
56
|
function _selectedData(x509, selector) {
|
|
55
57
|
if (selector === 0) return Buffer.from(x509.raw); // full certificate DER
|
|
@@ -179,7 +179,7 @@ var QTYPE_BY_NAME = Object.freeze({
|
|
|
179
179
|
function create(opts) {
|
|
180
180
|
opts = opts || {};
|
|
181
181
|
var profile = opts.profile || (opts.posture && safeDns.compliancePosture(opts.posture)) || DEFAULT_PROFILE;
|
|
182
|
-
if (!safeDns.PROFILES
|
|
182
|
+
if (!Object.prototype.hasOwnProperty.call(safeDns.PROFILES, profile)) {
|
|
183
183
|
throw new ResolverError("resolver/bad-profile",
|
|
184
184
|
"create: unknown profile '" + profile + "'");
|
|
185
185
|
}
|
|
@@ -188,7 +188,9 @@ function create(opts) {
|
|
|
188
188
|
var serveStale = opts.serveStale === false ? 0 :
|
|
189
189
|
typeof opts.serveStale === "number" ? opts.serveStale : DEFAULT_STALE_WINDOW;
|
|
190
190
|
var transport = opts.transport || _defaultTransport();
|
|
191
|
-
|
|
191
|
+
// Audit goes to the operator-supplied sink (opts.audit) when present, else the
|
|
192
|
+
// framework chain — b.audit.namespaced no-ops if the sink has no safeEmit.
|
|
193
|
+
var _baseAudit = audit().namespaced("network.dns.resolver", { sink: opts.audit });
|
|
192
194
|
|
|
193
195
|
if (typeof transport.lookup !== "function") {
|
|
194
196
|
throw new ResolverError("resolver/bad-transport",
|
|
@@ -238,11 +240,7 @@ function create(opts) {
|
|
|
238
240
|
}
|
|
239
241
|
|
|
240
242
|
function _safeEmit(action, metadata) {
|
|
241
|
-
|
|
242
|
-
if (auditImpl && typeof auditImpl.safeEmit === "function") {
|
|
243
|
-
auditImpl.safeEmit({ action: "network.dns.resolver." + action, outcome: "success", metadata: metadata });
|
|
244
|
-
}
|
|
245
|
-
} catch (_e) { /* audit drop-silent per validation tier policy */ }
|
|
243
|
+
_baseAudit(action, "success", metadata);
|
|
246
244
|
}
|
|
247
245
|
|
|
248
246
|
async function query(name, type, qopts) {
|
|
@@ -526,8 +524,101 @@ function _minTtl(rrs) {
|
|
|
526
524
|
return min === Infinity ? 0 : min;
|
|
527
525
|
}
|
|
528
526
|
|
|
527
|
+
// _sharedTxtResolver — one process-wide validating resolver (with its TTL
|
|
528
|
+
// cache) shared by every email-auth policy TXT lookup, instead of each module
|
|
529
|
+
// constructing its own (or reaching for plaintext system DNS).
|
|
530
|
+
var _sharedResolver = null;
|
|
531
|
+
function _sharedTxtResolver() {
|
|
532
|
+
if (!_sharedResolver) _sharedResolver = create();
|
|
533
|
+
return _sharedResolver;
|
|
534
|
+
}
|
|
535
|
+
|
|
536
|
+
/**
|
|
537
|
+
* @primitive b.network.dns.resolver.resolveTxt
|
|
538
|
+
* @signature b.network.dns.resolver.resolveTxt(qname, dnsLookup?, resolver?)
|
|
539
|
+
* @since 0.15.13
|
|
540
|
+
* @status stable
|
|
541
|
+
* @related b.network.dns.resolver.safeResolveTxt, b.network.dns.resolver.create
|
|
542
|
+
*
|
|
543
|
+
* Resolve TXT records for `qname` via the operator's `dnsLookup(qname, "TXT")`
|
|
544
|
+
* override when supplied, otherwise the framework's shared validating
|
|
545
|
+
* (DoH / DoT / system, DNSSEC-checked) resolver — normalized to the
|
|
546
|
+
* `string[][]` shape `dnsPromises.resolveTxt` returns. Throws an `ENODATA`
|
|
547
|
+
* Error when no TXT records exist.
|
|
548
|
+
*
|
|
549
|
+
* This is the secure DNS path for every email-auth policy lookup (DMARC / DKIM /
|
|
550
|
+
* ARC / BIMI / MTA-STS / TLS-RPT). Plaintext system DNS (`dnsPromises.resolveTxt`)
|
|
551
|
+
* is spoofable — DNS cache-poisoning / Kaminsky-class — and must NOT be used for
|
|
552
|
+
* records a downgrade or redirect would exploit.
|
|
553
|
+
*
|
|
554
|
+
* Pass `resolver` (a `b.network.dns.resolver.create()` instance) to reshape TXT
|
|
555
|
+
* records off a caller-owned resolver instead of the shared one — the mail-auth
|
|
556
|
+
* and mail-dkim modules use this so their TXT lookups share the same resolver
|
|
557
|
+
* (and cache) they use for A / MX / PTR, instead of each re-rolling the reshape.
|
|
558
|
+
*
|
|
559
|
+
* @example
|
|
560
|
+
* var rrs = await b.network.dns.resolver.resolveTxt("_dmarc.example.com");
|
|
561
|
+
* // → [ ["v=DMARC1; p=reject"] ]
|
|
562
|
+
*/
|
|
563
|
+
async function resolveTxt(qname, dnsLookup, resolver) {
|
|
564
|
+
if (dnsLookup) return dnsLookup(qname, "TXT");
|
|
565
|
+
var r = await (resolver || _sharedTxtResolver()).queryTxt(qname);
|
|
566
|
+
var out = [];
|
|
567
|
+
for (var i = 0; i < r.rrs.length; i += 1) {
|
|
568
|
+
var rr = r.rrs[i];
|
|
569
|
+
if (rr && rr.type === 16) { // IANA DNS qtype TXT
|
|
570
|
+
out.push(Array.isArray(rr.decoded) ? rr.decoded : [String(rr.decoded)]);
|
|
571
|
+
}
|
|
572
|
+
}
|
|
573
|
+
if (out.length === 0) {
|
|
574
|
+
var err = new Error("no TXT records for " + qname);
|
|
575
|
+
err.code = "ENODATA";
|
|
576
|
+
throw err;
|
|
577
|
+
}
|
|
578
|
+
return out;
|
|
579
|
+
}
|
|
580
|
+
|
|
581
|
+
/**
|
|
582
|
+
* @primitive b.network.dns.resolver.safeResolveTxt
|
|
583
|
+
* @signature b.network.dns.resolver.safeResolveTxt(qname, opts?)
|
|
584
|
+
* @since 0.15.13
|
|
585
|
+
* @status stable
|
|
586
|
+
* @related b.network.dns.resolver.resolveTxt
|
|
587
|
+
*
|
|
588
|
+
* `resolveTxt` wrapped with the email-auth policy-fetch convention: a domain
|
|
589
|
+
* with no record (ENOTFOUND / ENODATA) returns `null` (absence is not an
|
|
590
|
+
* error); any other failure throws the caller's typed error via
|
|
591
|
+
* `errorFactory(code, message)` so each protocol keeps its own error class.
|
|
592
|
+
* The same secure resolver path as `resolveTxt` — never plaintext system DNS.
|
|
593
|
+
*
|
|
594
|
+
* @opts
|
|
595
|
+
* dnsLookup: function, // operator override (qname, "TXT") => string[][]
|
|
596
|
+
* errorFactory: function, // (code, message) => Error, thrown on non-absence failures
|
|
597
|
+
* code: string, // error code passed to errorFactory
|
|
598
|
+
*
|
|
599
|
+
* @example
|
|
600
|
+
* var rrs = await b.network.dns.resolver.safeResolveTxt("_mta-sts.example.com", {
|
|
601
|
+
* errorFactory: function (code, msg) { return new SmtpPolicyError(code, msg); },
|
|
602
|
+
* code: "smtp/mta-sts-txt-lookup-failed",
|
|
603
|
+
* });
|
|
604
|
+
* if (rrs === null) return null; // no MTA-STS record published
|
|
605
|
+
*/
|
|
606
|
+
async function safeResolveTxt(qname, opts) {
|
|
607
|
+
opts = opts || {};
|
|
608
|
+
try {
|
|
609
|
+
return await resolveTxt(qname, opts.dnsLookup);
|
|
610
|
+
} catch (e) {
|
|
611
|
+
if (e && (e.code === "ENOTFOUND" || e.code === "ENODATA")) return null;
|
|
612
|
+
var msg = "TXT lookup for " + qname + " failed: " + ((e && e.message) || String(e));
|
|
613
|
+
if (typeof opts.errorFactory === "function") throw opts.errorFactory(opts.code, msg);
|
|
614
|
+
throw e;
|
|
615
|
+
}
|
|
616
|
+
}
|
|
617
|
+
|
|
529
618
|
module.exports = {
|
|
530
619
|
create: create,
|
|
620
|
+
resolveTxt: resolveTxt,
|
|
621
|
+
safeResolveTxt: safeResolveTxt,
|
|
531
622
|
ResolverError: ResolverError,
|
|
532
623
|
QTYPE_BY_NAME: QTYPE_BY_NAME,
|
|
533
624
|
};
|
package/lib/network-dns.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
|
|
3
3
|
var dns = require("node:dns");
|
|
4
|
+
var numericBounds = require("./numeric-bounds");
|
|
4
5
|
var net = require("node:net");
|
|
5
6
|
var https = require("node:https");
|
|
6
7
|
var nodeTls = require("node:tls");
|
|
@@ -347,10 +348,16 @@ function _decodeDnsAnswer(buf, qtype) {
|
|
|
347
348
|
for (var a = 0; a < ancount; a++) {
|
|
348
349
|
_skipDnsName(buf, state);
|
|
349
350
|
var off = state.off;
|
|
351
|
+
// Bound the RR fixed header (10 bytes: type+class+ttl+rdlen) and the rdata
|
|
352
|
+
// before indexing — ancount comes from the (untrusted upstream) header, so a
|
|
353
|
+
// truncated reply would otherwise over-read into a raw RangeError. Mirrors
|
|
354
|
+
// the hardened _decodeDnsAnswerRaw guards.
|
|
355
|
+
if (off + 10 > buf.length) throw new DnsError("dns/bad-reply", "answer record truncated");
|
|
350
356
|
var rtype = buf.readUInt16BE(off); off += 2;
|
|
351
357
|
off += 2;
|
|
352
358
|
off += 4;
|
|
353
359
|
var rdlen = buf.readUInt16BE(off); off += 2;
|
|
360
|
+
if (off + rdlen > buf.length) throw new DnsError("dns/bad-reply", "answer rdata truncated");
|
|
354
361
|
if (rtype === qtype && qtype === 1 && rdlen === 4) {
|
|
355
362
|
addrs.push(buf[off] + "." + buf[off + 1] + "." + buf[off + 2] + "." + buf[off + 3]);
|
|
356
363
|
} else if (rtype === qtype && qtype === DNS_QTYPE_AAAA && rdlen === IPV6_ADDR_BYTES) {
|
|
@@ -1851,7 +1858,7 @@ var DS_DIGEST_TYPES = Object.freeze({
|
|
|
1851
1858
|
});
|
|
1852
1859
|
|
|
1853
1860
|
function classifyDnskeyAlgorithm(algorithm) {
|
|
1854
|
-
if (
|
|
1861
|
+
if (!numericBounds.isFiniteInt(algorithm)) {
|
|
1855
1862
|
return null;
|
|
1856
1863
|
}
|
|
1857
1864
|
var row = DNSKEY_ALGORITHMS[algorithm];
|
|
@@ -1874,7 +1881,7 @@ function classifyDnskeyAlgorithm(algorithm) {
|
|
|
1874
1881
|
}
|
|
1875
1882
|
|
|
1876
1883
|
function classifyDsDigestType(digestType) {
|
|
1877
|
-
if (
|
|
1884
|
+
if (!numericBounds.isFiniteInt(digestType)) {
|
|
1878
1885
|
return null;
|
|
1879
1886
|
}
|
|
1880
1887
|
var row = DS_DIGEST_TYPES[digestType];
|
package/lib/network-dnssec.js
CHANGED
|
@@ -45,7 +45,9 @@
|
|
|
45
45
|
*/
|
|
46
46
|
|
|
47
47
|
var nodeCrypto = require("node:crypto");
|
|
48
|
+
var numericBounds = require("./numeric-bounds");
|
|
48
49
|
var bCrypto = require("./crypto");
|
|
50
|
+
var safeBuffer = require("./safe-buffer");
|
|
49
51
|
var validateOpts = require("./validate-opts");
|
|
50
52
|
var { defineClass } = require("./framework-error");
|
|
51
53
|
|
|
@@ -79,11 +81,14 @@ var TYPE_NUM = {
|
|
|
79
81
|
SMIMEA: 53, CDS: 59, CDNSKEY: 60, OPENPGPKEY: 61, CAA: 257, HINFO: 13,
|
|
80
82
|
};
|
|
81
83
|
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
84
|
+
// DNSSEC wire data is bytes, never text (allowString:false).
|
|
85
|
+
var _bytes = safeBuffer.makeByteCoercer({
|
|
86
|
+
errorClass: DnssecError,
|
|
87
|
+
typeCode: "dnssec/bad-bytes",
|
|
88
|
+
messagePrefix: "dnssec: ",
|
|
89
|
+
messageSuffix: " must be a Buffer",
|
|
90
|
+
allowString: false,
|
|
91
|
+
});
|
|
87
92
|
|
|
88
93
|
// Canonical wire form of a domain name (RFC 4034 §6.2): each label
|
|
89
94
|
// length-prefixed, ASCII lowercased, terminated by the root label.
|
|
@@ -157,8 +162,11 @@ function _dnskeyToKey(algId, publicKey) {
|
|
|
157
162
|
return _jwkKey({ kty: "OKP", crv: "Ed25519", x: pk.toString("base64url") });
|
|
158
163
|
}
|
|
159
164
|
function _jwkKey(jwk) {
|
|
160
|
-
|
|
161
|
-
|
|
165
|
+
return bCrypto.importPublicJwk(jwk, {
|
|
166
|
+
errorClass: DnssecError,
|
|
167
|
+
code: "dnssec/bad-key",
|
|
168
|
+
messagePrefix: "dnssec: could not import DNSKEY: ",
|
|
169
|
+
});
|
|
162
170
|
}
|
|
163
171
|
|
|
164
172
|
/**
|
|
@@ -284,15 +292,8 @@ function verifyRrset(opts) {
|
|
|
284
292
|
}
|
|
285
293
|
|
|
286
294
|
// Validity window (fail closed on a bad opts.at).
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
if (!(opts.at instanceof Date) || !isFinite(opts.at.getTime())) {
|
|
290
|
-
throw new DnssecError("dnssec/bad-at", "dnssec.verifyRrset: opts.at must be a valid Date");
|
|
291
|
-
}
|
|
292
|
-
atMs = opts.at.getTime();
|
|
293
|
-
} else {
|
|
294
|
-
atMs = Date.now();
|
|
295
|
-
}
|
|
295
|
+
validateOpts.optionalDate(opts.at, "dnssec.verifyRrset: opts.at", DnssecError, "dnssec/bad-at");
|
|
296
|
+
var atMs = (opts.at !== undefined && opts.at !== null) ? opts.at.getTime() : Date.now();
|
|
296
297
|
var nowSec = Math.floor(atMs / 1000);
|
|
297
298
|
if (nowSec < (rrsig.inception >>> 0)) throw new DnssecError("dnssec/not-yet-valid", "dnssec.verifyRrset: RRSIG inception is in the future");
|
|
298
299
|
if (nowSec > (rrsig.expiration >>> 0)) throw new DnssecError("dnssec/expired", "dnssec.verifyRrset: RRSIG has expired");
|
|
@@ -515,7 +516,7 @@ function nsec3Hash(name, opts) {
|
|
|
515
516
|
validateOpts(opts, ["salt", "iterations"], "dnssec.nsec3Hash");
|
|
516
517
|
var salt = _bytes(opts.salt, "salt");
|
|
517
518
|
var iters = opts.iterations;
|
|
518
|
-
if (
|
|
519
|
+
if (!numericBounds.isNonNegativeFiniteInt(iters)) {
|
|
519
520
|
throw new DnssecError("dnssec/bad-iterations", "dnssec.nsec3Hash: iterations must be a non-negative integer");
|
|
520
521
|
}
|
|
521
522
|
return _nsec3HashWire(_canonicalName(name), salt, iters);
|
package/lib/network-heartbeat.js
CHANGED
|
@@ -5,6 +5,7 @@ var net = require("node:net");
|
|
|
5
5
|
var C = require("./constants");
|
|
6
6
|
var validateOpts = require("./validate-opts");
|
|
7
7
|
var lazyRequire = require("./lazy-require");
|
|
8
|
+
var boundedMap = require("./bounded-map");
|
|
8
9
|
var { defineClass } = require("./framework-error");
|
|
9
10
|
|
|
10
11
|
var HeartbeatError = defineClass("HeartbeatError", { alwaysPermanent: true });
|
|
@@ -198,9 +199,9 @@ function start(opts) {
|
|
|
198
199
|
var started = [];
|
|
199
200
|
for (var j = 0; j < opts.targets.length; j++) {
|
|
200
201
|
var t = opts.targets[j];
|
|
201
|
-
|
|
202
|
+
boundedMap.requireAbsent(TARGETS, t.name, function () {
|
|
202
203
|
throw new HeartbeatError("heartbeat/duplicate", "heartbeat target '" + t.name + "' already started");
|
|
203
|
-
}
|
|
204
|
+
});
|
|
204
205
|
var entry = {
|
|
205
206
|
target: t,
|
|
206
207
|
intervalMs: t.intervalMs || DEFAULT_INTERVAL_MS,
|