@blamejs/pki 0.5.8 → 0.5.10
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 +43 -18
- package/README.md +1 -1
- package/lib/acme.js +1 -1
- package/lib/cmc-build.js +5 -5
- package/lib/cmc-verify.js +7 -7
- package/lib/cms-decrypt.js +2 -2
- package/lib/cms-verify.js +2 -2
- package/lib/constants.js +1 -1
- package/lib/est.js +3 -3
- package/lib/guard-all.js +14 -12
- package/lib/guard-bytes.js +4 -4
- package/lib/guard-compress.js +2 -2
- package/lib/guard-der.js +152 -0
- package/lib/guard-identifier.js +3 -3
- package/lib/guard-limits.js +2 -2
- package/lib/guard-parsed.js +1 -1
- package/lib/guard-range.js +1 -1
- package/lib/guard-secret.js +1 -1
- package/lib/guard-text.js +1 -1
- package/lib/hpke.js +1 -1
- package/lib/http-digest.js +1 -1
- package/lib/jose.js +1 -1
- package/lib/lint.js +1 -1
- package/lib/ocsp.js +1 -1
- package/lib/oid.js +1 -1
- package/lib/path-validate.js +5 -5
- package/lib/pki-build.js +27 -1
- package/lib/schema-c509.js +10 -60
- package/lib/schema-cmp.js +1 -1
- package/lib/schema-crl.js +1 -1
- package/lib/schema-engine.js +2 -2
- package/lib/schema-pkix.js +1 -1
- package/lib/smime.js +1 -1
- package/lib/validator-cose.js +3 -3
- package/lib/webauthn-mds.js +12 -12
- package/lib/webauthn.js +11 -11
- package/lib/webcrypto.js +1 -1
- package/package.json +4 -2
- package/sbom.cdx.json +6 -6
package/lib/webauthn-mds.js
CHANGED
|
@@ -85,7 +85,7 @@ function _schemeE(kind, message, cause) { return new WebauthnError("webauthn/" +
|
|
|
85
85
|
// WebCrypto algorithms.
|
|
86
86
|
//
|
|
87
87
|
// Null-prototype: `alg` is attacker-supplied, and an inherited Object member would otherwise
|
|
88
|
-
// resolve to a truthy non-row and read as a
|
|
88
|
+
// resolve to a truthy non-row and read as a recognized algorithm.
|
|
89
89
|
function _deriveBlobAlgs() {
|
|
90
90
|
var out = Object.create(null);
|
|
91
91
|
jose.sigAlgs().forEach(function (row) {
|
|
@@ -120,7 +120,7 @@ function _blobAlgParams(algRow, leafAlgName) {
|
|
|
120
120
|
|
|
121
121
|
// The status values that deny trust (MDS v3.0 sec. 3.1.4). An unknown status is IGNORED for the
|
|
122
122
|
// gate and surfaced raw -- the specification requires that a verifier not fail on a status it does
|
|
123
|
-
// not
|
|
123
|
+
// not recognize -- unless the caller opts into refusing them.
|
|
124
124
|
var DISQUALIFYING = Object.assign(Object.create(null), {
|
|
125
125
|
REVOKED: 1, ATTESTATION_KEY_COMPROMISE: 1, USER_KEY_REMOTE_COMPROMISE: 1,
|
|
126
126
|
USER_KEY_PHYSICAL_COMPROMISE: 1, USER_VERIFICATION_BYPASS: 1,
|
|
@@ -140,7 +140,7 @@ var _BLOB_OPTS = Object.assign(Object.create(null), {
|
|
|
140
140
|
function _isPlainObject(v) { return !!v && typeof v === "object" && !Array.isArray(v); }
|
|
141
141
|
|
|
142
142
|
// The results this module actually produced. Membership is the PROVENANCE check: a metadata object
|
|
143
|
-
// is only allowed to decide trust if verifyMetadataBlob made it.
|
|
143
|
+
// is only allowed to decide trust if verifyMetadataBlob made it. Recognizing it by shape instead
|
|
144
144
|
// would accept anything carrying the right property names, including a catalogue deserialized
|
|
145
145
|
// from a cache, where nothing establishes that the signature and chain were ever checked, and an
|
|
146
146
|
// attacker who can write that cache chooses which roots an authenticator is allowed to chain to.
|
|
@@ -207,7 +207,7 @@ function _isAnchorItself(cert, anchor) {
|
|
|
207
207
|
//
|
|
208
208
|
// An already-parsed certificate is taken as-is, never re-encoded and re-parsed, but the
|
|
209
209
|
// recognition test names every field the anchor is later read for: the subject name, the public
|
|
210
|
-
// key bytes, and the signature algorithm OID.
|
|
210
|
+
// key bytes, and the signature algorithm OID. Recognizing an object on a looser test lets something
|
|
211
211
|
// that is merely certificate-SHAPED through: a parsed CSR satisfies "has a subject and an SPKI", and
|
|
212
212
|
// a hand-built object literal satisfies it too and then raises a raw TypeError from deep inside the
|
|
213
213
|
// path validator, which is an untyped throw escaping a public verb.
|
|
@@ -250,8 +250,8 @@ function _verifyMetadataBlob(blob, opts) {
|
|
|
250
250
|
throw _err("webauthn/bad-input", "opts.previousNo must be a non-negative safe integer");
|
|
251
251
|
}
|
|
252
252
|
// The boolean options are checked for TYPE, not merely compared against true. A caller who wrote
|
|
253
|
-
// `rejectUnknownStatus: "true"` from a config file is asking for the stricter
|
|
254
|
-
// against `true` silently records the policy as off and the authenticator carrying an
|
|
253
|
+
// `rejectUnknownStatus: "true"` from a config file is asking for the stricter behavior; comparing
|
|
254
|
+
// against `true` silently records the policy as off and the authenticator carrying an unrecognized
|
|
255
255
|
// status is then accepted -- the fail-open the option exists to prevent.
|
|
256
256
|
["requireRollbackCheck", "allowStale", "rejectUnknownStatus"].forEach(function (k) {
|
|
257
257
|
if (opts[k] !== undefined && typeof opts[k] !== "boolean") {
|
|
@@ -326,7 +326,7 @@ function _verifyMetadataBlob(blob, opts) {
|
|
|
326
326
|
maxBytes: C.MDS_BLOB_HEADER_MAX_BYTES, maxDepth: C.JSON_MAX_DEPTH,
|
|
327
327
|
tooLarge: "webauthn/too-large", badJson: "webauthn/bad-metadata-blob",
|
|
328
328
|
// Every code the guard can raise is named. An omitted one falls back to the framework default
|
|
329
|
-
// and the module's own
|
|
329
|
+
// and the module's own defenses -- duplicate-member smuggling, the depth cap -- surface under
|
|
330
330
|
// a generic code no webauthn/* consumer can switch on.
|
|
331
331
|
tooDeep: "webauthn/bad-metadata-blob", duplicateMember: "webauthn/bad-metadata-blob",
|
|
332
332
|
badInput: "webauthn/bad-metadata-blob", label: "the metadata BLOB header",
|
|
@@ -449,7 +449,7 @@ function _verifyMetadataBlob(blob, opts) {
|
|
|
449
449
|
// anchors through this same walk while keeping the verdict its own callers already handle. A second copy
|
|
450
450
|
// of the walk is not merely duplication: the anchor-stripping rule is subtle (a terminal
|
|
451
451
|
// certificate that IS the anchor is identified by subject name AND public key, which is what
|
|
452
|
-
//
|
|
452
|
+
// recognizes a cross-signed root), and a copy that got it slightly differently would refuse a valid
|
|
453
453
|
// chain in one place and accept a self-validated one in another.
|
|
454
454
|
function _chainToAnchor(chain, anchors, at, what, code) {
|
|
455
455
|
var subject = what || "metadata BLOB certificate chain";
|
|
@@ -509,7 +509,7 @@ function _staleAfter(nextUpdate) {
|
|
|
509
509
|
// has to be re-established every time it decides something, not only when it was parsed. A
|
|
510
510
|
// catalogue fetched before its nextUpdate and reused a month later would otherwise keep authorizing
|
|
511
511
|
// an authenticator whose status reports have since revoked it, which is precisely what nextUpdate
|
|
512
|
-
// exists to prevent. The caller's original allowStale decision is carried on the result and
|
|
512
|
+
// exists to prevent. The caller's original allowStale decision is carried on the result and honored
|
|
513
513
|
// here, so opting out stays opted out and never silently reappears at the point of use.
|
|
514
514
|
function assertFresh(metadata, at, label) {
|
|
515
515
|
if (!metadata || metadata.allowStale === true || typeof metadata.nextUpdate !== "string") return;
|
|
@@ -696,7 +696,7 @@ function metadataFor(metadata, identifier) {
|
|
|
696
696
|
return null;
|
|
697
697
|
}
|
|
698
698
|
|
|
699
|
-
// The options metadataAnchors
|
|
699
|
+
// The options metadataAnchors recognizes. Null-prototype for the same reason every other
|
|
700
700
|
// caller-keyed table in this namespace is: a supplied key must not resolve to an inherited member.
|
|
701
701
|
var _ANCHOR_OPTS = Object.assign(Object.create(null), { metadata: 1, time: 1, certificate: 1 });
|
|
702
702
|
|
|
@@ -846,13 +846,13 @@ function statusDenied(entry, metadata, leaf, at) {
|
|
|
846
846
|
if (s === null) return false;
|
|
847
847
|
// Scope was settled above, so anything still here applies to this authenticator.
|
|
848
848
|
if (DISQUALIFYING[s]) return true;
|
|
849
|
-
// An
|
|
849
|
+
// An unrecognized status is IGNORED for the gate unless the caller opted in: the specification
|
|
850
850
|
// requires a verifier not to fail on a status value it does not know.
|
|
851
851
|
return rejectUnknown && !_KNOWN_STATUS[s];
|
|
852
852
|
});
|
|
853
853
|
}
|
|
854
854
|
|
|
855
|
-
// Status values this library
|
|
855
|
+
// Status values this library recognizes as non-disqualifying, so `rejectUnknownStatus` can tell an
|
|
856
856
|
// unknown value from a known-good one.
|
|
857
857
|
var _KNOWN_STATUS = Object.assign(Object.create(null), {
|
|
858
858
|
NOT_FIDO_CERTIFIED: 1, SELF_ASSERTION_SUBMITTED: 1, FIDO_CERTIFIED: 1, FIDO_CERTIFIED_L1: 1,
|
package/lib/webauthn.js
CHANGED
|
@@ -474,8 +474,8 @@ var ATT_STMT_MAJOR = { compound: 4 };
|
|
|
474
474
|
var _AAGUID_SIGNED_BY_FMT = Object.assign(Object.create(null), { "fido-u2f": false });
|
|
475
475
|
function _aaguidIsSigned(fmt) { return _AAGUID_SIGNED_BY_FMT[fmt] !== false; }
|
|
476
476
|
|
|
477
|
-
// The options pki.webauthn.verify
|
|
478
|
-
// resolve to an inherited Object member and read as
|
|
477
|
+
// The options pki.webauthn.verify recognizes. Null-prototype, so a caller-supplied key cannot
|
|
478
|
+
// resolve to an inherited Object member and read as recognized.
|
|
479
479
|
var _VERIFY_OPTS = Object.assign(Object.create(null), {
|
|
480
480
|
time: 1, metadata: 1, tpmPolicy: 1, safetyNetRoots: 1, verifySafetyNetJws: 1, requireCtsProfileMatch: 1,
|
|
481
481
|
expectedRpId: 1, requireUserPresence: 1, requireUserVerification: 1, allowedAlgorithms: 1,
|
|
@@ -511,7 +511,7 @@ var _FORMAT_SCOPED_BOOLEAN_OPTS = ["verifySafetyNetJws", "requireCtsProfileMatch
|
|
|
511
511
|
// so a caller that recycles the array or overwrites a certificate's bytes in between
|
|
512
512
|
// would have the attestation anchored against the replacement roots while the verdict
|
|
513
513
|
// still reports `anchoredTo: "rootCertificates"` (CWE-367 reaching a wrong trust
|
|
514
|
-
// decision). Same
|
|
514
|
+
// decision). Same defense, and the same reason, as the assertion input's snapshot.
|
|
515
515
|
// Shape faults are NOT raised here: the array is validated where it is consumed, so
|
|
516
516
|
// supplying a malformed pin alongside a metadata catalogue that answers keeps failing
|
|
517
517
|
// exactly where it did before.
|
|
@@ -645,7 +645,7 @@ function _applyCallerRoots(res, supplied, vopts, onlyPaths) {
|
|
|
645
645
|
var at = vopts.time !== undefined ? vopts.time : new Date();
|
|
646
646
|
// EVERY path, not merely one: a compound's elements are independent claims, and
|
|
647
647
|
// accepting the whole because one element anchored would let an unanchored element
|
|
648
|
-
// ride along on its
|
|
648
|
+
// ride along on its neighbor's trust.
|
|
649
649
|
return paths.reduce(function (p, info) {
|
|
650
650
|
return p.then(function () {
|
|
651
651
|
var pathAt = vopts.time !== undefined ? vopts.time : (info.at || at);
|
|
@@ -1114,7 +1114,7 @@ var VERIFIERS = {
|
|
|
1114
1114
|
});
|
|
1115
1115
|
});
|
|
1116
1116
|
}, Promise.resolve()).then(function () {
|
|
1117
|
-
// sec. 8.9 lists the supported attestation type as "Any" and
|
|
1117
|
+
// sec. 8.9 lists the supported attestation type as "Any" and authorizes returning
|
|
1118
1118
|
// "implementation-specific values representing any combination of outputs". A distinct type
|
|
1119
1119
|
// rather than a merge: collapsing to the strongest element would let a wrapper upgrade a
|
|
1120
1120
|
// caller's attestationType check, and collapsing to the weakest would spuriously fail one.
|
|
@@ -1388,7 +1388,7 @@ function verify(attestationObject, clientDataHash, opts) {
|
|
|
1388
1388
|
// Every option here either GATES the verdict or supplies the trust material a gate needs, so a
|
|
1389
1389
|
// misspelled key is not a harmless no-op: `metdata` leaves the metadata gate switched off and the
|
|
1390
1390
|
// call returns a pass the caller believes was checked against the catalogue. Reject an
|
|
1391
|
-
//
|
|
1391
|
+
// unrecognized key at the boundary, and validate the one shared option every arm reads, so a bad
|
|
1392
1392
|
// instant is a config fault here rather than a trust failure reported from deep inside a chain.
|
|
1393
1393
|
try {
|
|
1394
1394
|
if (!_isPlainObject(opts)) throw _err("webauthn/bad-input", "opts must be an object");
|
|
@@ -1406,7 +1406,7 @@ function verify(attestationObject, clientDataHash, opts) {
|
|
|
1406
1406
|
// The boolean switches are typed HERE rather than in the arm that reads them. Validated only
|
|
1407
1407
|
// inside the android-safetynet arm, `requireCtsProfileMatch: "true"` reaching any other format
|
|
1408
1408
|
// is never examined at all -- a truthy string that demands nothing, silently accepted. A
|
|
1409
|
-
//
|
|
1409
|
+
// recognized key with an unusable value is the same class of caller mistake as an unrecognized
|
|
1410
1410
|
// key, and belongs at the same boundary.
|
|
1411
1411
|
_FORMAT_SCOPED_BOOLEAN_OPTS.forEach(function (k) {
|
|
1412
1412
|
if (opts[k] !== undefined && typeof opts[k] !== "boolean") throw _err("webauthn/bad-input", "opts." + k + " must be a boolean");
|
|
@@ -1597,7 +1597,7 @@ function verify(attestationObject, clientDataHash, opts) {
|
|
|
1597
1597
|
function _applyMetadata(res, att, opts) {
|
|
1598
1598
|
var md = opts.metadata;
|
|
1599
1599
|
// Provenance, not shape: only a catalogue this toolkit actually verified may decide which roots
|
|
1600
|
-
// an authenticator is allowed to chain to.
|
|
1600
|
+
// an authenticator is allowed to chain to. Recognizing it by its property names would accept a
|
|
1601
1601
|
// hand-built object, or one restored from a cache an attacker can write -- neither of which has
|
|
1602
1602
|
// been through the signature and chain checks that give the catalogue its authority.
|
|
1603
1603
|
if (!mds.isVerifiedResult(md)) {
|
|
@@ -1703,7 +1703,7 @@ function _applyMetadata(res, att, opts) {
|
|
|
1703
1703
|
mds.assertFresh(md, at, "the metadata supplied as opts.metadata");
|
|
1704
1704
|
// EVERY path must pass, not merely one of them: for a compound attestation each element is an
|
|
1705
1705
|
// independent claim, and accepting the whole because one element anchored would let an
|
|
1706
|
-
// unanchored -- or revoked -- element ride along on its
|
|
1706
|
+
// unanchored -- or revoked -- element ride along on its neighbor's trust.
|
|
1707
1707
|
// Govern EVERY path BEFORE any is chain-validated, and let the SEVEREST outcome decide
|
|
1708
1708
|
// rather than the first one reached. A compound's elements are independent claims and
|
|
1709
1709
|
// its element order is NOT signed, so aborting on the first failure lets that order pick
|
|
@@ -1836,7 +1836,7 @@ void constants;
|
|
|
1836
1836
|
* (the default; any disqualifying report ever filed), `"latest-by-date"` (only the
|
|
1837
1837
|
* most recent report counts, so a later remediation clears an earlier revocation),
|
|
1838
1838
|
* or a function receiving the raw report array and returning true to deny.
|
|
1839
|
-
* - `rejectUnknownStatus` -- treat a status this toolkit does not
|
|
1839
|
+
* - `rejectUnknownStatus` -- treat a status this toolkit does not recognize as
|
|
1840
1840
|
* disqualifying. Off by default: the specification requires an unknown status be
|
|
1841
1841
|
* ignored, never failed on.
|
|
1842
1842
|
*
|
|
@@ -2146,7 +2146,7 @@ function parseAuthenticatorData(bytes) {
|
|
|
2146
2146
|
return _parseAuthData(_bytesArg(bytes, "authenticatorData"), _err);
|
|
2147
2147
|
}
|
|
2148
2148
|
|
|
2149
|
-
// The options pki.webauthn.verifyAssertion
|
|
2149
|
+
// The options pki.webauthn.verifyAssertion recognizes, null-prototype for the same
|
|
2150
2150
|
// reason _VERIFY_OPTS is.
|
|
2151
2151
|
var _ASSERT_OPTS = Object.assign(Object.create(null), {
|
|
2152
2152
|
authenticatorData: 1, clientDataHash: 1, clientDataJSON: 1, signature: 1,
|
package/lib/webcrypto.js
CHANGED
|
@@ -1344,7 +1344,7 @@ function exportAnyKey(key, E, code) {
|
|
|
1344
1344
|
// A separately-installed copy of this engine is the same code under a different class identity, so
|
|
1345
1345
|
// its keys fail the `instanceof` above while carrying the very handle this engine reads -- and no
|
|
1346
1346
|
// WebCrypto implementation but that copy would accept them. Read the handle directly. The
|
|
1347
|
-
// extractable check above stays AHEAD of this: the handle can always be read, so
|
|
1347
|
+
// extractable check above stays AHEAD of this: the handle can always be read, so honoring the
|
|
1348
1348
|
// key's own extractable promise is this branch's responsibility, not the crypto library's.
|
|
1349
1349
|
var handle = key._handle;
|
|
1350
1350
|
if (handle instanceof nodeCrypto.KeyObject && handle.type === key.type) {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@blamejs/pki",
|
|
3
|
-
"version": "0.5.
|
|
3
|
+
"version": "0.5.10",
|
|
4
4
|
"description": "Pure-JavaScript PKI toolkit that owns its stack — X.509, ASN.1/DER, CMS, PQC-first.",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"author": "blamejs contributors",
|
|
@@ -68,9 +68,11 @@
|
|
|
68
68
|
"test": "node test/smoke.js",
|
|
69
69
|
"lint": "eslint --max-warnings 0 .",
|
|
70
70
|
"fuzz": "npm ci --prefix fuzz && npx --prefix fuzz jazzer fuzz/asn1-der.fuzz.js -- -max_total_time=60",
|
|
71
|
-
"gates": "node test/layer-0-primitives/codebase-patterns.test.js && node scripts/validate-source-comment-blocks.js && node scripts/check-api-snapshot.js",
|
|
71
|
+
"gates": "node test/layer-0-primitives/codebase-patterns.test.js && node scripts/validate-source-comment-blocks.js && node scripts/check-api-snapshot.js && node scripts/check-spelling-consistency.js",
|
|
72
|
+
"check:spelling": "node scripts/check-spelling-consistency.js",
|
|
72
73
|
"coverage": "c8 --include=lib/** --include=index.js --reporter=text-summary --reporter=lcov node test/smoke.js",
|
|
73
74
|
"check:prose": "node scripts/check-operator-prose.js",
|
|
75
|
+
"check:prose:all": "node -e \"process.env.PKI_PROSE_CHECKS='all';var r=require('child_process').spawnSync(process.execPath,['scripts/check-operator-prose.js'],{stdio:'inherit'});if(r.error)throw r.error;process.exit(r.signal?1:r.status)\"",
|
|
74
76
|
"check:swallows": "node scripts/check-swallow-coverage.js",
|
|
75
77
|
"coverage:gated": "npm run coverage && npm run check:swallows",
|
|
76
78
|
"coverage:unified": "node scripts/coverage-unified.js",
|
package/sbom.cdx.json
CHANGED
|
@@ -2,10 +2,10 @@
|
|
|
2
2
|
"$schema": "http://cyclonedx.org/schema/bom-1.5.schema.json",
|
|
3
3
|
"bomFormat": "CycloneDX",
|
|
4
4
|
"specVersion": "1.5",
|
|
5
|
-
"serialNumber": "urn:uuid:
|
|
5
|
+
"serialNumber": "urn:uuid:b51c65a4-3df8-4dec-af37-301d0b285af6",
|
|
6
6
|
"version": 1,
|
|
7
7
|
"metadata": {
|
|
8
|
-
"timestamp": "2026-08-
|
|
8
|
+
"timestamp": "2026-08-18T10:39:56.596Z",
|
|
9
9
|
"lifecycles": [
|
|
10
10
|
{
|
|
11
11
|
"phase": "build"
|
|
@@ -19,14 +19,14 @@
|
|
|
19
19
|
}
|
|
20
20
|
],
|
|
21
21
|
"component": {
|
|
22
|
-
"bom-ref": "@blamejs/pki@0.5.
|
|
22
|
+
"bom-ref": "@blamejs/pki@0.5.10",
|
|
23
23
|
"type": "application",
|
|
24
24
|
"name": "pki",
|
|
25
|
-
"version": "0.5.
|
|
25
|
+
"version": "0.5.10",
|
|
26
26
|
"scope": "required",
|
|
27
27
|
"author": "blamejs contributors",
|
|
28
28
|
"description": "Pure-JavaScript PKI toolkit that owns its stack — X.509, ASN.1/DER, CMS, PQC-first.",
|
|
29
|
-
"purl": "pkg:npm/%40blamejs/pki@0.5.
|
|
29
|
+
"purl": "pkg:npm/%40blamejs/pki@0.5.10",
|
|
30
30
|
"properties": [],
|
|
31
31
|
"externalReferences": [
|
|
32
32
|
{
|
|
@@ -54,7 +54,7 @@
|
|
|
54
54
|
"components": [],
|
|
55
55
|
"dependencies": [
|
|
56
56
|
{
|
|
57
|
-
"ref": "@blamejs/pki@0.5.
|
|
57
|
+
"ref": "@blamejs/pki@0.5.10",
|
|
58
58
|
"dependsOn": []
|
|
59
59
|
}
|
|
60
60
|
]
|