@blamejs/core 0.18.12 → 0.18.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 CHANGED
@@ -8,6 +8,10 @@ upgrading across more than a few patches at a time.
8
8
 
9
9
  ## v0.18.x
10
10
 
11
+ - v0.18.14 (2026-08-06) — **Broader offline test coverage across the error-page renderer, the request body parser, and the safe-YAML parser.** Error, edge, and fallback branches of b.errorPage, b.middleware.bodyParser, and b.parsers.yaml are now under explicit test — b.errorPage gains its first dedicated test suite. No behaviour changes: exercising every path confirmed each already behaves per its documented contract. One unreachable duplicate of the multipart parser's per-part reset — dead code that was never called — was removed. The vendored @noble/curves and @blamejs/pki bundles are refreshed. **Changed:** *Error- and edge-path tests for the error-page renderer, request body parser, and safe-YAML parser* — b.errorPage (classification of every recognized error class and the generic-500 fallback, JSON-vs-HTML content negotiation, the development-vs-production render difference, sensitive-header redaction, the write-response fallbacks when the response object lacks writeHead, and the audit record's redaction, outcome, stack-only-for-5xx, and trusted-proxy actor-IP invariants), b.middleware.bodyParser (RFC 5987 filename* decoding, multipart part-header and body framing split across socket reads, disk-mode temp-directory creation failure, repeated-field aggregation, and the request-stream-error refusal), and b.parsers.yaml (numeric-literal overflow falling back to the string form, block- and flow-collection edges, merge-key and duplicate-key refusals, block-scalar header parsing, and the prototype-pollution key guard across every decode path) now have explicit tests for their boundary branches. Alongside the tests, an unreachable duplicate of the multipart parser's per-part reset — a helper that was never called, its logic already performed inline where each part finalizes — was removed. Behaviour is unchanged. · *Vendored @noble/curves refreshed to 2.3.0* — The vendored @noble/curves bundle behind b.crypto.oprf is refreshed from 2.2.0 to 2.3.0. Upstream reworked the oblivious-pseudorandom-function batch path to a shared-doubling multi-scalar multiplication — applied only to values that are public by construction (hash-derived transcript weights, wire-decoded points, and proof scalars) — and now rejects a non-function rng option outright. The verified outputs are unchanged; no blamejs public API changes. · *Vendored @blamejs/pki refreshed to 0.3.33* — The vendored @blamejs/pki bundle behind the mutual-TLS CA engine is refreshed from 0.3.32 to 0.3.33, which adds the compact certificate-policies value form for C509 (CBOR-encoded) certificates and the accompanying certificate-policy and policy-qualifier identifiers to that toolkit's OID registry. The CA engine uses only the toolkit's X.509, PKCS#12, and CRL surfaces, so there is no behaviour change to the certificate-authority surface — this is a supply-chain currency refresh.
12
+
13
+ - v0.18.13 (2026-08-06) — **`b.db.collection` no longer corrupts a BLOB overflow column (its bytes were spread as numeric keys, destroying the JSON payload); broader offline test coverage across db-collection, TLS, external-db residency, notify, and BIMI.** A schemaless collection with a BLOB overflow column read that column back as a byte array. Because a byte array is a JavaScript object, the collection treated it as an already-parsed overflow object and spread its bytes as numeric keys on read, on update, and on insert — silently destroying the stored JSON payload. The overflow value is now decoded as JSON at every one of those points. The release also closes offline test-coverage gaps and adds an explicit test that the external-database residency boundary is enforced. **Changed:** *Broader offline test coverage and an explicit external-database residency-enforcement test* — b.db.collection (BLOB overflow round-trips, introspection-error wrapping, overflow-as-jsonColumn), b.network.tls.connectWithEch (the connect success, timeout, unreachable-peer, and synchronous-throw paths driven against local servers), b.externalDb (the boot-time data-residency check now has an explicit test that a personal-data backend outside the declared region is refused and compliant backends are admitted), b.notify (the default HTTP client is bound at send time), and b.mail.bimi (a certificate chain deeper than the maximum is rejected) now have tests for these boundary paths. **Fixed:** *b.db.collection no longer corrupts a BLOB overflow column* — A collection configured with an overflow column (unknown fields folded into a JSON column) reads that column back as a byte array when the column is declared BLOB. A byte array's typeof is "object", so the collection took it to be an already-parsed overflow object and spread its raw bytes as numeric-index keys — on read (surfacing { "0": …, "1": … } onto the row), on a $set/$unset update (rewriting the column to the byte spread and destroying the original keys), and on an insert that supplied a Buffer overflow value. The overflow value is now decoded from its bytes as JSON at all three points, so the stored payload survives; a BLOB holding non-JSON bytes falls back to an empty object instead of corrupting, matching the existing non-JSON-string behaviour.
14
+
11
15
  - v0.18.12 (2026-08-05) — **Certificate Transparency SCT verification now works for real certificates (it previously rejected every embedded SCT); b.network.tls.ct.verifyScts / requireScts now require the issuing CA; broader Redis-client and TLS-verification test coverage.** b.network.tls.ct.verifyScts reconstructed the wrong RFC 6962 signed input for the SCTs embedded in a certificate — it built an x509_entry over the bare TBSCertificate instead of the precert_entry (issuer_key_hash + TBSCertificate) the log actually signed, and separately read the SCT's signature algorithm from a field the parser does not emit. Either alone made verification impossible: verifyScts and requireScts rejected every real embedded SCT. Verification now builds the RFC 6962 §3.2 precert_entry and requires the issuing CA so it can bind issuer_key_hash. This is a fail-closed fix — nothing invalid was ever trusted — but SCT enforcement now needs the issuer supplied. **Changed:** *b.network.tls.ct.verifyScts and requireScts now require the issuing CA* — Because an embedded SCT binds the issuing CA's key, verifying one needs that key. Pass the issuer to verifyScts as issuerCertDer (the issuer certificate DER), issuerSpkiDer (its SubjectPublicKeyInfo DER), or issuerKeyHash (the precomputed 32-byte SHA-256). Without it, verifyScts returns { ok: false, reason: "issuer-key-required" } and does not verify. requireScts derives the issuer from the peer's certificate chain automatically and accepts the same options; supply one explicitly only when the issuer is not present in the chain. verifyInclusion takes the same issuer options for embedded (precert_entry) leaves. · *Vendored @blamejs/pki refreshed to 0.3.32* — The vendored @blamejs/pki bundle behind the mutual-TLS CA engine is refreshed to 0.3.32, which adds C509 (CBOR-encoded certificate) extended-key-usage and extension registries (RFC 7633 TLS Feature, RFC 4556 PKINIT, and SSH/CMC/Bundle-Security purposes). No blamejs public API changes. · *Error- and edge-path tests for the Redis client and TLS-verification primitives* — b.redisClient (RESP framing and protocol-error edges, split replies, AUTH / SELECT on connect, push-message demux, connect/command timeouts, socket-error and close teardown) and b.network.tls (OCSP and Certificate-Transparency parse and verification branches, ECH config framing, and PKIX host/SAN matching) now have explicit tests for their boundary branches, driven through local fake servers and crafted certificates. **Fixed:** *Certificate Transparency SCT verification now works for certificate-embedded SCTs* — For an SCT embedded in a certificate's X.509v3 extension, the log computed its signature over the RFC 6962 §3.2 precert_entry — issuer_key_hash (SHA-256 of the issuing CA's SubjectPublicKeyInfo) followed by the TBSCertificate with the SCT extension removed. b.network.tls.ct.verifyScts instead reconstructed an x509_entry over just the stripped TBSCertificate, with no issuer binding, so the reconstructed input never matched what the log signed and no real embedded SCT could verify. It additionally read the SCT's signature-algorithm from a field name the parser does not emit, so the algorithm cross-check failed before the signature was even reached. verifyScts now builds the precert_entry and reads the parsed algorithm field, so a valid SCT verifies and a genuine mismatch is still refused. b.network.tls.ct.verifyInclusion builds the RFC 9162 §4.6 leaf the same way. The prior behaviour failed closed (it rejected rather than accepted), so no invalid certificate was ever trusted. **Security:** *SCT verification now binds each log key to the SCT's log ID* — An SCT's log ID is the SHA-256 of the log's public key (RFC 6962 §3.2). b.network.tls.ct.verifyScts now recomputes that hash from the configured key and refuses the SCT (reason "log-id-key-mismatch") when it does not equal the log ID the SCT names, before checking the signature. This prevents a misconfigured logKeys map from letting a key impersonate an approved log. · *SCT verification counts distinct logs, refuses future timestamps, and validates the minimum* — verifyScts now counts SCTs from distinct logs, so duplicate SCTs from a single log can no longer satisfy a "minimum 2 SCTs" diversity policy (verifiedCount reflects distinct logs). It refuses an SCT whose timestamp is in the future (RFC 6962 §5.2; opts.now / opts.clockSkewMs override the clock and the default five-minute skew). A minScts that is not a positive integer is rejected at the entry point rather than silently accepting an unverified certificate. Parsing an SCT extension from a peer certificate now fails closed on malformed DER — it returns no SCT instead of throwing, so a hostile certificate cannot raise an uncaught error out of verifyScts, parseScts, or the requireScts predicate.
12
16
 
13
17
  - v0.18.11 (2026-08-03) — **A static file that is a symlink at the final path component now fails closed with a clean 404 instead of an uncaught error; broader DNS, field-crypto, and static-server coverage.** The static server refuses a symlink at the final path component (its O_NOFOLLOW TOCTOU defense) — but that refusal now falls back to a clean 404 / NOT_FOUND rather than propagating an uncaught error that hung the request or surfaced a raw ELOOP from staticServe.integrity(). Alongside the fix, error and edge branches of b.network.dns, b.cryptoField, and b.staticServe come under explicit test. **Changed:** *Error- and edge-path tests for the DNS, field-crypto, and static-server primitives* — b.network.dns (DoH oversized-body handling, DoT mid-query connection reset and socket reuse, cache expiry, system-resolver error paths), b.cryptoField (tampered / relocated / downgraded cell fail-closed nulling, key-row AAD and rowId resolution, unseal rate-cap configuration, on-read upgrade branches), and b.staticServe (integrity and confinement edge cases, idle-timeout teardown, the final-component symlink refusal above) now have explicit tests for their boundary branches. An unused internal date-formatting helper was removed. **Fixed:** *A final-component symlink (or a mid-hash read error) on a static file fails closed with a clean 404* — When computing a served file's SRI and ETag, the static server opens it with O_NOFOLLOW so a symlink swapped in at the final path component after the in-root check cannot be followed (a TOCTOU / CWE-22 / CWE-367 defense). On POSIX that open raises ELOOP for a symlinked target — the common atomic-deploy pattern where a stable name links to a hashed file (latest.js pointing at app.<hash>.js). That error, and any read error mid-hash, previously propagated uncaught: the middleware request hung with no response, and staticServe.integrity() threw a raw ELOOP instead of its documented NOT_FOUND. It now falls back exactly as a read error does — the middleware returns 404 (falls through) and integrity() throws NOT_FOUND. The symlink is still not served; the refusal is just clean. Windows is unaffected (O_NOFOLLOW is a no-op there).
package/NOTICE CHANGED
@@ -26,7 +26,7 @@ Used for: XChaCha20-Poly1305 authenticated encryption (lib/crypto.js,
26
26
  Thank you to Paul Miller for the audited noble-ciphers suite.
27
27
  --------------------------------------------------------------------------------
28
28
  Component: @noble/curves
29
- Version: 2.2.0
29
+ Version: 2.3.0
30
30
  Source: https://github.com/paulmillr/noble-curves
31
31
  License: MIT
32
32
  Copyright: Copyright (c) 2022 Paul Miller (https://paulmillr.com)
@@ -60,7 +60,7 @@ Used for: WebAuthn / passkey registration + authentication response
60
60
  server.cjs).
61
61
  --------------------------------------------------------------------------------
62
62
  Component: @blamejs/pki
63
- Version: 0.3.32
63
+ Version: 0.3.33
64
64
  Source: https://github.com/blamejs/pki
65
65
  License: Apache-2.0
66
66
  Copyright: Copyright (c) blamejs contributors
@@ -216,6 +216,22 @@ function _splitUpdateOperators(update) {
216
216
  * users.find({ dept: "eng" });
217
217
  * // → JSON_EXTRACT(data, '$.dept') = 'eng'
218
218
  */
219
+
220
+ // Decode a BLOB overflow value as JSON. A BLOB column reads back as a
221
+ // Uint8Array/Buffer (typeof "object"); its bytes are JSON text and must be
222
+ // parsed — Object.assign over the raw view would spread the bytes as numeric
223
+ // keys and destroy the payload. Returns the parsed plain object, or null when
224
+ // the bytes are not a JSON object (unparseable / a non-object / an array) —
225
+ // mirroring the string branches: the read path leaves an un-decodable value
226
+ // on the row as-is, write/update coalesce a null to {}. Callers reach this
227
+ // only after an ArrayBuffer.isView guard.
228
+ function _blobToObject(view) {
229
+ try {
230
+ var parsed = safeJson.parse(Buffer.from(view.buffer, view.byteOffset, view.byteLength).toString("utf8"));
231
+ return (parsed && typeof parsed === "object" && !Array.isArray(parsed)) ? parsed : null;
232
+ } catch (_e) { return null; }
233
+ }
234
+
219
235
  function collection(name, opts) {
220
236
  validateOpts.requireNonEmptyString(name, "collection(name): name", TypeError, "db-collection/bad-name");
221
237
  opts = opts || {};
@@ -297,10 +313,14 @@ function collection(name, opts) {
297
313
  });
298
314
  if (hasExtras) {
299
315
  var existing = null;
300
- if (typeof writeDoc[overflow] === "object" && writeDoc[overflow] !== null && !Array.isArray(writeDoc[overflow])) {
316
+ if (ArrayBuffer.isView(writeDoc[overflow])) { // BLOB/Buffer overflow value -> decode as JSON, don't spread bytes
317
+ existing = _blobToObject(writeDoc[overflow]);
318
+ } else if (typeof writeDoc[overflow] === "object" && writeDoc[overflow] !== null && !Array.isArray(writeDoc[overflow])) {
301
319
  existing = writeDoc[overflow];
302
320
  }
303
321
  writeDoc[overflow] = JSON.stringify(Object.assign({}, existing || {}, extras));
322
+ } else if (writeDoc[overflow] !== undefined && writeDoc[overflow] !== null && ArrayBuffer.isView(writeDoc[overflow])) {
323
+ writeDoc[overflow] = JSON.stringify(_blobToObject(writeDoc[overflow]) || {});
304
324
  } else if (writeDoc[overflow] !== undefined && writeDoc[overflow] !== null && typeof writeDoc[overflow] === "object") {
305
325
  writeDoc[overflow] = JSON.stringify(writeDoc[overflow]);
306
326
  }
@@ -321,6 +341,8 @@ function collection(name, opts) {
321
341
  var extra = null;
322
342
  if (typeof out[overflow] === "string" && out[overflow].length > 0) {
323
343
  try { extra = safeJson.parse(out[overflow]); } catch (_e) { extra = null; }
344
+ } else if (ArrayBuffer.isView(out[overflow])) { // BLOB/Buffer overflow -> decode JSON, don't spread bytes onto the row
345
+ extra = _blobToObject(out[overflow]);
324
346
  } else if (typeof out[overflow] === "object" && !Array.isArray(out[overflow])) {
325
347
  extra = out[overflow];
326
348
  }
@@ -537,6 +559,8 @@ function collection(name, opts) {
537
559
  if (rows[ri][overflow] !== undefined && rows[ri][overflow] !== null) {
538
560
  if (typeof rows[ri][overflow] === "string" && rows[ri][overflow].length > 0) {
539
561
  try { existing = safeJson.parse(rows[ri][overflow]) || {}; } catch (_e) { existing = {}; }
562
+ } else if (ArrayBuffer.isView(rows[ri][overflow])) { // BLOB/Buffer overflow -> decode JSON before the read-modify-write
563
+ existing = _blobToObject(rows[ri][overflow]) || {};
540
564
  } else if (typeof rows[ri][overflow] === "object") {
541
565
  existing = rows[ri][overflow] || {};
542
566
  }
@@ -839,21 +839,6 @@ async function _parseMultipart(req, opts, ctParams) {
839
839
  // boundary) but never written to disk.
840
840
  var currentEffectiveLimit = 0; // per-field-or-global cap; recomputed at part start.
841
841
 
842
- function _resetCurrent() {
843
- currentHeaders = null;
844
- currentField = null;
845
- currentFilename = null;
846
- currentMime = null;
847
- currentTmpPath = null;
848
- if (currentFd !== null) { try { nodeFs.closeSync(currentFd); } catch (_e) { /* fd already closed */ } currentFd = null; }
849
- currentSize = 0;
850
- currentHash = null;
851
- currentBuf = null;
852
- currentIsFile = false;
853
- currentDiscarded = false;
854
- currentEffectiveLimit = 0;
855
- }
856
-
857
842
  function _emitRejection(field, filename, mimeType, code, message) {
858
843
  filesRejected.push({
859
844
  field: field,
@@ -18,10 +18,10 @@
18
18
  "hashes": {
19
19
  "server": "sha256:2b30a26f728c5349f4c4b47834f862a4f77393b1224fc12b22abe3ce2cfab78f"
20
20
  },
21
- "refreshedAt": "2026-08-06T00:01:44.784Z"
21
+ "refreshedAt": "2026-08-06T21:56:25.536Z"
22
22
  },
23
23
  "@noble/curves": {
24
- "version": "2.2.0",
24
+ "version": "2.3.0",
25
25
  "license": "MIT",
26
26
  "author": "Paul Miller",
27
27
  "source": "https://github.com/paulmillr/noble-curves",
@@ -35,12 +35,12 @@
35
35
  "server": "lib/vendor/noble-curves.cjs"
36
36
  },
37
37
  "bundler": "esbuild --format=cjs --platform=node",
38
- "bundledAt": "2026-07-13T00:00:00Z",
39
- "cpe": "cpe:2.3:a:paulmillr:noble-curves:2.2.0:*:*:*:*:node.js:*:*",
38
+ "bundledAt": "2026-08-06T00:00:00Z",
39
+ "cpe": "cpe:2.3:a:paulmillr:noble-curves:2.3.0:*:*:*:*:node.js:*:*",
40
40
  "hashes": {
41
- "server": "sha256:2880c288b1285ef51d356d057bee6f0c8a00de36638cf47b47617e8c1faf10d5"
41
+ "server": "sha256:b5fe88d1ea780d0581dee6145d666f89d46fc9531b5db35db2e5b16627840890"
42
42
  },
43
- "refreshedAt": "2026-08-06T00:01:44.784Z"
43
+ "refreshedAt": "2026-08-06T21:56:25.536Z"
44
44
  },
45
45
  "@noble/post-quantum": {
46
46
  "version": "0.6.1",
@@ -71,7 +71,7 @@
71
71
  "hashes": {
72
72
  "server": "sha256:f9c94094b3c10fe73dac5343289da582454ea6053494fab2bf66099d9103d6c3"
73
73
  },
74
- "refreshedAt": "2026-08-06T00:01:44.784Z"
74
+ "refreshedAt": "2026-08-06T21:56:25.536Z"
75
75
  },
76
76
  "@simplewebauthn/server": {
77
77
  "version": "13.3.2",
@@ -94,7 +94,7 @@
94
94
  "hashes": {
95
95
  "server": "sha256:e83195dc9f189385da9c856ef38843f4466f93ea8f3d7fc2efcb1e1b18da6f20"
96
96
  },
97
- "refreshedAt": "2026-08-06T00:01:44.784Z"
97
+ "refreshedAt": "2026-08-06T21:56:25.536Z"
98
98
  },
99
99
  "SecLists-common-passwords-top-10000": {
100
100
  "version": "10k-most-common (master)",
@@ -114,7 +114,7 @@
114
114
  },
115
115
  "runtime_artifact": "lib/vendor/common-passwords-top-10000.data.js",
116
116
  "integrity_layers": "sha256 + sha3-512 + SLH-DSA-SHAKE-256f signature + in-payload canary (where applicable)",
117
- "refreshedAt": "2026-08-06T00:01:44.784Z"
117
+ "refreshedAt": "2026-08-06T21:56:25.536Z"
118
118
  },
119
119
  "bimi-trust-anchors": {
120
120
  "version": "operator-managed",
@@ -139,7 +139,7 @@
139
139
  },
140
140
  "runtime_artifact": "lib/vendor/bimi-trust-anchors.data.js",
141
141
  "integrity_layers": "sha256 + sha3-512 + SLH-DSA-SHAKE-256f signature + in-payload canary (where applicable)",
142
- "refreshedAt": "2026-08-06T00:01:44.784Z"
142
+ "refreshedAt": "2026-08-06T21:56:25.536Z"
143
143
  },
144
144
  "publicsuffix-list": {
145
145
  "version": "master",
@@ -159,10 +159,10 @@
159
159
  },
160
160
  "runtime_artifact": "lib/vendor/public-suffix-list.data.js",
161
161
  "integrity_layers": "sha256 + sha3-512 + SLH-DSA-SHAKE-256f signature + in-payload canary (where applicable)",
162
- "refreshedAt": "2026-08-06T00:01:44.784Z"
162
+ "refreshedAt": "2026-08-06T21:56:25.536Z"
163
163
  },
164
164
  "@blamejs/pki": {
165
- "version": "0.3.32",
165
+ "version": "0.3.33",
166
166
  "license": "Apache-2.0",
167
167
  "author": "blamejs",
168
168
  "source": "https://github.com/blamejs/pki",
@@ -182,12 +182,12 @@
182
182
  "server": "lib/vendor/blamejs-pki.cjs"
183
183
  },
184
184
  "bundler": "esbuild --format=cjs --platform=node --external:crypto --external:node:crypto",
185
- "bundledAt": "2026-08-05T00:00:00Z",
186
- "cpe": "cpe:2.3:a:blamejs:pki:0.3.32:*:*:*:*:node.js:*:*",
185
+ "bundledAt": "2026-08-06T00:00:00Z",
186
+ "cpe": "cpe:2.3:a:blamejs:pki:0.3.33:*:*:*:*:node.js:*:*",
187
187
  "hashes": {
188
- "server": "sha256:275a0ef678a74e9ce035658ae95b18c05dcf69200bf934f609f149d1c8e739e3"
188
+ "server": "sha256:2c297ccbbb48e04306bd6cb681e80f41549fd12267dde9db2a771d55d688745b"
189
189
  },
190
- "refreshedAt": "2026-08-06T00:01:44.784Z"
190
+ "refreshedAt": "2026-08-06T21:56:25.536Z"
191
191
  }
192
192
  }
193
193
  }
@@ -1,4 +1,4 @@
1
- // @blamejs/pki v0.3.32 — vendored (Apache-2.0). Zero-dep pure CJS.
1
+ // @blamejs/pki v0.3.33 — vendored (Apache-2.0). Zero-dep pure CJS.
2
2
  // https://github.com/blamejs/pki Exports: x509, crl, pkcs12, key, webcrypto, schema, csr, cms, ...
3
3
  // Backs lib/mtls-engine-default.js (PQC-capable CA + PKCS#12 engine).
4
4
  var __getOwnPropNames = Object.getOwnPropertyNames;
@@ -137,7 +137,7 @@ var require_package = __commonJS({
137
137
  "node_modules/@blamejs/pki/package.json"(exports2, module2) {
138
138
  module2.exports = {
139
139
  name: "@blamejs/pki",
140
- version: "0.3.32",
140
+ version: "0.3.33",
141
141
  description: "Pure-JavaScript PKI toolkit that owns its stack \u2014 X.509, ASN.1/DER, CMS, PQC-first.",
142
142
  license: "Apache-2.0",
143
143
  author: "blamejs contributors",
@@ -2561,6 +2561,38 @@ var require_oid = __commonJS({
2561
2561
  // negative ints: id-on-hardwareModuleName (RFC 4108), id-on-SmtpUTF8Mailbox (RFC 9598),
2562
2562
  // id-on-MACAddress (I-D.ietf-lamps-macaddress-on) on the id-pkix 8 arc.
2563
2563
  pkixOn: { base: [1, 3, 6, 1, 5, 5, 7, 8], of: { hardwareModuleName: 4, smtpUtf8Mailbox: 9, macAddress: 12 } },
2564
+ // CA/Browser Forum certificate policies (draft-ietf-cose-cbor-encoded-cert sec. 8.9 ints 1-4) on the
2565
+ // 2.23.140.1 arc: the id-cabf validation levels under .2, ev-guidelines under .1.
2566
+ cabfPolicy: { base: [2, 23, 140, 1], of: {
2567
+ "ev-guidelines": 1,
2568
+ "domain-validated": [2, 1],
2569
+ "organization-validated": [2, 2],
2570
+ "individual-validated": [2, 3]
2571
+ } },
2572
+ // id-cp-ipAddr-asNumber (RFC 3779 / RFC 8360) on the id-pkix 14 arc -- the RPKI resource-certificate
2573
+ // policy OIDs (sec. 8.9 ints 7-8).
2574
+ idCp: { base: [1, 3, 6, 1, 5, 5, 7, 14], of: { "id-cp-ipAddr-asNumber": 2, "id-cp-ipAddr-asNumber-v2": 3 } },
2575
+ // GSMA SGP.22 Remote SIM Provisioning roles (sec. 8.9 ints 24-38) on the 2.23.146.1.2.1 id-rspRole arc.
2576
+ // The -v2 roles are shallow leaves .{1..7}; the non-v2 roles descend into deeper sub-arcs (draft-20).
2577
+ gsmaRspRole: { base: [2, 23, 146, 1, 2, 1], of: {
2578
+ "id-rspRole-ci": 0,
2579
+ "id-rspRole-euicc-v2": 1,
2580
+ "id-rspRole-euicc": [0, 0, 0, 0, 0],
2581
+ "id-rspRole-eum-v2": 2,
2582
+ "id-rspRole-eum": [0, 0, 0],
2583
+ "id-rspRole-dp-tls-v2": 3,
2584
+ "id-rspRole-dp-tls": [0, 0, 1, 0],
2585
+ "id-rspRole-dp-auth-v2": 4,
2586
+ "id-rspRole-dp-auth": [0, 0, 1, 1],
2587
+ "id-rspRole-dp-pb-v2": 5,
2588
+ "id-rspRole-dp-pb": [0, 0, 1, 2],
2589
+ "id-rspRole-ds-tls-v2": 6,
2590
+ "id-rspRole-ds-tls": [0, 0, 2, 0],
2591
+ "id-rspRole-ds-auth-v2": 7,
2592
+ "id-rspRole-ds-auth": [0, 0, 2, 1]
2593
+ } },
2594
+ // id-qt policy qualifiers (RFC 5280 sec. 4.2.1.4) on the id-pkix 2 arc -- the C509 sec. 8.10 registry.
2595
+ pkixQt: { base: [1, 3, 6, 1, 5, 5, 7, 2], of: { cps: 1, unotice: 2 } },
2564
2596
  // OCSP (RFC 6960) on the id-pkix-ocsp arc (= id-ad-ocsp). id-pkix-ocsp-basic is
2565
2597
  // the ResponseBytes.responseType this build decodes; id-pkix-ocsp-nonce (sec. 4.4.1)
2566
2598
  // names the nonce extension; the remaining members name the other OCSP extensions
@@ -7503,6 +7535,7 @@ var require_schema_c509 = __commonJS({
7503
7535
  3: _name("subjectAltName"),
7504
7536
  4: _name("basicConstraints"),
7505
7537
  5: _name("cRLDistributionPoints"),
7538
+ 6: _name("certificatePolicies"),
7506
7539
  7: _name("authorityKeyIdentifier"),
7507
7540
  8: _name("extKeyUsage"),
7508
7541
  9: _name("authorityInfoAccess"),
@@ -7529,7 +7562,8 @@ var require_schema_c509 = __commonJS({
7529
7562
  cRLDistributionPoints: 1,
7530
7563
  freshestCRL: 1,
7531
7564
  authorityInfoAccess: 1,
7532
- subjectInfoAccess: 1
7565
+ subjectInfoAccess: 1,
7566
+ certificatePolicies: 1
7533
7567
  };
7534
7568
  var EKU_BY_INT = {
7535
7569
  0: _name("anyExtendedKeyUsage"),
@@ -7575,6 +7609,39 @@ var require_schema_c509 = __commonJS({
7575
7609
  Object.keys(IA_BY_INT).forEach(function(k) {
7576
7610
  IA_TO_INT[oid.byName(IA_BY_INT[k])] = Number(k);
7577
7611
  });
7612
+ var CP_BY_INT = {
7613
+ 0: _name("anyPolicy"),
7614
+ 1: _name("domain-validated"),
7615
+ 2: _name("organization-validated"),
7616
+ 3: _name("individual-validated"),
7617
+ 4: _name("ev-guidelines"),
7618
+ 7: _name("id-cp-ipAddr-asNumber"),
7619
+ 8: _name("id-cp-ipAddr-asNumber-v2"),
7620
+ 24: _name("id-rspRole-ci"),
7621
+ 25: _name("id-rspRole-euicc-v2"),
7622
+ 26: _name("id-rspRole-euicc"),
7623
+ 27: _name("id-rspRole-eum-v2"),
7624
+ 28: _name("id-rspRole-eum"),
7625
+ 29: _name("id-rspRole-dp-tls-v2"),
7626
+ 30: _name("id-rspRole-dp-tls"),
7627
+ 31: _name("id-rspRole-dp-auth-v2"),
7628
+ 32: _name("id-rspRole-dp-auth"),
7629
+ 33: _name("id-rspRole-dp-pb-v2"),
7630
+ 34: _name("id-rspRole-dp-pb"),
7631
+ 35: _name("id-rspRole-ds-tls-v2"),
7632
+ 36: _name("id-rspRole-ds-tls"),
7633
+ 37: _name("id-rspRole-ds-auth-v2"),
7634
+ 38: _name("id-rspRole-ds-auth")
7635
+ };
7636
+ var CP_TO_INT = {};
7637
+ Object.keys(CP_BY_INT).forEach(function(k) {
7638
+ CP_TO_INT[oid.byName(CP_BY_INT[k])] = Number(k);
7639
+ });
7640
+ var PQ_BY_INT = { 1: _name("cps"), 2: _name("unotice") };
7641
+ var PQ_TO_INT = {};
7642
+ Object.keys(PQ_BY_INT).forEach(function(k) {
7643
+ PQ_TO_INT[oid.byName(PQ_BY_INT[k])] = Number(k);
7644
+ });
7578
7645
  function _biguint(node, code, label) {
7579
7646
  if (!node || node.majorType !== 2) throw _err(code, label + " must be an unwrapped CBOR byte string (~biguint)");
7580
7647
  var b2 = node.content;
@@ -7908,6 +7975,61 @@ var require_schema_c509 = __commonJS({
7908
7975
  if (mag.length === 0) return Buffer.from([0]);
7909
7976
  return mag[0] & 128 ? Buffer.concat([Buffer.from([0]), mag]) : mag;
7910
7977
  }
7978
+ function _ia5Universal(text, label) {
7979
+ if (text.length === 0) throw _err("c509/bad-extensions", label + " must be non-empty");
7980
+ for (var i = 0; i < text.length; i++) {
7981
+ if (text.charCodeAt(i) > 127) throw _err("c509/bad-extensions", label + " must be a 7-bit IA5String");
7982
+ }
7983
+ guard.name.assertPrintableIa5(Buffer.from(text, "latin1"), _err, "c509/bad-extensions", label);
7984
+ return b.ia5(text);
7985
+ }
7986
+ function _policyIdToDerOid(node) {
7987
+ if (node.majorType === 0 || node.majorType === 1) {
7988
+ var i = Number(cbor.read.int(node)), nm = CP_BY_INT[i];
7989
+ if (nm === void 0) throw _err("c509/bad-extensions", "a certificatePolicies policy int " + i + " has no C509 sec. 8.9 registry row");
7990
+ return oid.byName(nm);
7991
+ }
7992
+ return _oidName(node, "c509/bad-extensions", "a certificatePolicies policyIdentifier").oid;
7993
+ }
7994
+ function _policyIdFromDer(dotted) {
7995
+ var i = CP_TO_INT[dotted];
7996
+ return i !== void 0 ? cbor.build.int(BigInt(i)) : _oidCbor(dotted);
7997
+ }
7998
+ function _qualifierToDer(qidNode, qtextNode) {
7999
+ if (qidNode.majorType !== 0 && qidNode.majorType !== 1) throw _err("c509/bad-extensions", "a policyQualifierId must be a C509 sec. 8.10 int (a ~oid qualifier is not compact-representable)");
8000
+ var qi = Number(cbor.read.int(qidNode)), nm = PQ_BY_INT[qi];
8001
+ if (nm === void 0) throw _err("c509/bad-extensions", "a policyQualifierId int " + qi + " has no C509 sec. 8.10 registry row");
8002
+ if (qtextNode.majorType !== 3) throw _err("c509/bad-extensions", "a policyQualifier value must be a CBOR text string");
8003
+ var text = cbor.read.textString(qtextNode);
8004
+ if (qi === 1) return b.sequence([b.oid(oid.byName("cps")), _ia5Universal(text, "a CPSuri")]);
8005
+ if (text.length === 0) throw _err("c509/bad-extensions", "a UserNotice explicitText must be non-empty (DisplayText SIZE 1..200)");
8006
+ return b.sequence([b.oid(oid.byName("unotice")), b.sequence([b.utf8(text)])]);
8007
+ }
8008
+ function _qualifierFromDer(pq) {
8009
+ var qid = asn1.read.oid(pq.children[0]), qi = PQ_TO_INT[qid];
8010
+ if (qi === void 0) return null;
8011
+ var q = pq.children[1];
8012
+ if (qi === 1) {
8013
+ if (q.tagClass !== "universal" || q.tagNumber !== asn1.TAGS.IA5_STRING) return null;
8014
+ var uri;
8015
+ try {
8016
+ uri = asn1.read.string(q);
8017
+ } catch (_e) {
8018
+ return null;
8019
+ }
8020
+ return [cbor.build.int(1n), cbor.build.textString(uri)];
8021
+ }
8022
+ if (q.tagClass !== "universal" || q.tagNumber !== asn1.TAGS.SEQUENCE || !q.children || q.children.length !== 1) return null;
8023
+ var dt = q.children[0];
8024
+ if (dt.tagClass !== "universal" || dt.tagNumber !== asn1.TAGS.UTF8_STRING) return null;
8025
+ var txt;
8026
+ try {
8027
+ txt = asn1.read.string(dt);
8028
+ } catch (_e2) {
8029
+ return null;
8030
+ }
8031
+ return [cbor.build.int(2n), cbor.build.textString(txt)];
8032
+ }
7911
8033
  function _dpToDer(dpNode) {
7912
8034
  if (dpNode.majorType !== 4 || !dpNode.children || dpNode.children.length !== 3) throw _err("c509/bad-extensions", "a DistributionPoint must be a CBOR [ fullName, reasons, cRLIssuer ] array");
7913
8035
  var fullName = dpNode.children[0], reasons = dpNode.children[1], crlIssuer = dpNode.children[2];
@@ -8052,6 +8174,28 @@ var require_schema_c509 = __commonJS({
8052
8174
  return b.sequence(node.children.map(function(dp) {
8053
8175
  return _dpToDer(dp);
8054
8176
  }));
8177
+ case "certificatePolicies": {
8178
+ if (node.majorType !== 4 || !node.children) throw _err("c509/bad-extensions", "a certificatePolicies value must be a CBOR array");
8179
+ var cpKids = node.children;
8180
+ if (cpKids.length === 0 || cpKids.length % 2 !== 0) throw _err("c509/bad-extensions", "a certificatePolicies array must be non-empty (policyIdentifier, qualifiers) pairs (sec. 3.3)");
8181
+ var polInfos = [], seenPolicy = {};
8182
+ for (var cpi = 0; cpi + 1 < cpKids.length; cpi += 2) {
8183
+ var quals = cpKids[cpi + 1];
8184
+ if (quals.majorType !== 4 || !quals.children) throw _err("c509/bad-extensions", "a certificatePolicies qualifiers slot must be a CBOR array");
8185
+ var policyOid = _policyIdToDerOid(cpKids[cpi]);
8186
+ if (seenPolicy[policyOid]) throw _err("c509/bad-extensions", "a certificatePolicies policy OID must not appear more than once (RFC 5280 sec. 4.2.1.4)");
8187
+ seenPolicy[policyOid] = true;
8188
+ var polFields = [b.oid(policyOid)];
8189
+ if (quals.children.length) {
8190
+ if (quals.children.length % 2 !== 0) throw _err("c509/bad-extensions", "a policyQualifiers array must be (policyQualifierId, qualifier) pairs");
8191
+ var pqDers = [];
8192
+ for (var qk = 0; qk + 1 < quals.children.length; qk += 2) pqDers.push(_qualifierToDer(quals.children[qk], quals.children[qk + 1]));
8193
+ polFields.push(b.sequence(pqDers));
8194
+ }
8195
+ polInfos.push(b.sequence(polFields));
8196
+ }
8197
+ return b.sequence(polInfos);
8198
+ }
8055
8199
  default:
8056
8200
  throw _err("c509/bad-extensions", "extension " + name + " has no compact value decoder");
8057
8201
  }
@@ -8167,6 +8311,30 @@ var require_schema_c509 = __commonJS({
8167
8311
  return r.triple;
8168
8312
  }));
8169
8313
  }
8314
+ case "certificatePolicies": {
8315
+ if (node.tagClass !== "universal" || node.tagNumber !== asn1.TAGS.SEQUENCE || !node.children || node.children.length < 1) return null;
8316
+ var cpOut = [];
8317
+ for (var pli = 0; pli < node.children.length; pli++) {
8318
+ var pol = node.children[pli];
8319
+ if (pol.tagClass !== "universal" || pol.tagNumber !== asn1.TAGS.SEQUENCE || !pol.children || pol.children.length < 1 || pol.children.length > 2) return null;
8320
+ cpOut.push(_policyIdFromDer(asn1.read.oid(pol.children[0])));
8321
+ var qArr = [];
8322
+ if (pol.children.length === 2) {
8323
+ var qseq = pol.children[1];
8324
+ if (qseq.tagClass !== "universal" || qseq.tagNumber !== asn1.TAGS.SEQUENCE || !qseq.children || !qseq.children.length) return null;
8325
+ for (var qj = 0; qj < qseq.children.length; qj++) {
8326
+ var pq = qseq.children[qj];
8327
+ if (pq.tagClass !== "universal" || pq.tagNumber !== asn1.TAGS.SEQUENCE || !pq.children || pq.children.length !== 2) return null;
8328
+ var qpair = _qualifierFromDer(pq);
8329
+ if (qpair == null) return null;
8330
+ qArr.push(qpair[0]);
8331
+ qArr.push(qpair[1]);
8332
+ }
8333
+ }
8334
+ cpOut.push(cbor.build.array(qArr));
8335
+ }
8336
+ return cbor.build.array(cpOut);
8337
+ }
8170
8338
  default:
8171
8339
  return null;
8172
8340
  }