@blamejs/pki 0.5.7 → 0.5.8

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.
Files changed (104) hide show
  1. package/CHANGELOG.md +405 -386
  2. package/MIGRATING.md +43 -0
  3. package/README.md +12 -12
  4. package/lib/acme.js +31 -31
  5. package/lib/asn1-der.js +10 -10
  6. package/lib/attrcert-sign.js +19 -19
  7. package/lib/byte-reader.js +6 -6
  8. package/lib/byte-writer.js +5 -5
  9. package/lib/cbor-det.js +27 -24
  10. package/lib/cmc-build.js +97 -92
  11. package/lib/cmc-verify.js +106 -30
  12. package/lib/cmp-build.js +25 -25
  13. package/lib/cmp-session.js +70 -70
  14. package/lib/cmp-verify.js +71 -57
  15. package/lib/cms-compress.js +7 -7
  16. package/lib/cms-decrypt.js +90 -72
  17. package/lib/cms-encrypt.js +32 -32
  18. package/lib/cms-sign.js +74 -55
  19. package/lib/cms-verify.js +97 -75
  20. package/lib/composite-sig.js +13 -13
  21. package/lib/constants.js +4 -4
  22. package/lib/crl-sign.js +22 -22
  23. package/lib/crl-verify.js +7 -6
  24. package/lib/crmf-sign.js +14 -14
  25. package/lib/csr-sign.js +8 -8
  26. package/lib/ct.js +37 -37
  27. package/lib/edwards-point.js +7 -7
  28. package/lib/est.js +98 -55
  29. package/lib/framework-error.js +5 -5
  30. package/lib/guard-all.js +3 -3
  31. package/lib/guard-async.js +4 -4
  32. package/lib/guard-bytes.js +79 -79
  33. package/lib/guard-compress.js +17 -17
  34. package/lib/guard-crypto.js +1 -1
  35. package/lib/guard-encoding.js +15 -15
  36. package/lib/guard-header.js +3 -3
  37. package/lib/guard-identifier.js +16 -16
  38. package/lib/guard-json.js +15 -15
  39. package/lib/guard-limits.js +7 -7
  40. package/lib/guard-name.js +81 -16
  41. package/lib/guard-parsed.js +80 -80
  42. package/lib/guard-range.js +19 -19
  43. package/lib/guard-secret.js +11 -10
  44. package/lib/guard-text.js +6 -6
  45. package/lib/guard-time.js +10 -10
  46. package/lib/hpke.js +18 -17
  47. package/lib/http-digest.js +35 -35
  48. package/lib/http-retry-after.js +13 -13
  49. package/lib/http-transport.js +20 -19
  50. package/lib/inspect.js +53 -53
  51. package/lib/ip-utils.js +2 -2
  52. package/lib/jose.js +13 -13
  53. package/lib/key.js +16 -16
  54. package/lib/lint.js +51 -51
  55. package/lib/merkle.js +51 -36
  56. package/lib/mime.js +18 -18
  57. package/lib/ocsp-verify.js +10 -10
  58. package/lib/ocsp.js +13 -13
  59. package/lib/oid.js +29 -29
  60. package/lib/path-validate.js +114 -113
  61. package/lib/pbes2.js +16 -16
  62. package/lib/pkcs12-build.js +53 -53
  63. package/lib/pki-build.js +21 -19
  64. package/lib/rc2.js +1 -1
  65. package/lib/rfc3339.js +5 -5
  66. package/lib/schema-all.js +31 -31
  67. package/lib/schema-attrcert.js +12 -12
  68. package/lib/schema-c509.js +144 -142
  69. package/lib/schema-cmc.js +58 -58
  70. package/lib/schema-cmp.js +43 -43
  71. package/lib/schema-cms.js +45 -45
  72. package/lib/schema-crl.js +7 -7
  73. package/lib/schema-crmf.js +28 -28
  74. package/lib/schema-csr.js +12 -12
  75. package/lib/schema-csrattrs.js +16 -16
  76. package/lib/schema-engine.js +18 -18
  77. package/lib/schema-ocsp.js +15 -15
  78. package/lib/schema-pkcs12.js +20 -20
  79. package/lib/schema-pkcs8.js +2 -2
  80. package/lib/schema-pkix.js +131 -126
  81. package/lib/schema-smime.js +19 -19
  82. package/lib/schema-tsp.js +12 -12
  83. package/lib/schema-x509.js +3 -3
  84. package/lib/shbs.js +18 -18
  85. package/lib/sign-scheme.js +13 -13
  86. package/lib/sigstore.js +10 -11
  87. package/lib/sleep.js +1 -1
  88. package/lib/smime.js +308 -96
  89. package/lib/tls-cert-compress.js +18 -18
  90. package/lib/trust.js +27 -27
  91. package/lib/tsp-sign.js +17 -17
  92. package/lib/validator-all.js +1 -1
  93. package/lib/validator-attcert.js +1 -1
  94. package/lib/validator-cose.js +43 -44
  95. package/lib/validator-keydesc.js +3 -3
  96. package/lib/validator-sig.js +13 -13
  97. package/lib/validator-tls.js +11 -11
  98. package/lib/validator-tpm.js +20 -19
  99. package/lib/webauthn-mds.js +66 -66
  100. package/lib/webauthn.js +33 -33
  101. package/lib/webcrypto.js +15 -15
  102. package/lib/x509-sign.js +13 -13
  103. package/package.json +3 -2
  104. package/sbom.cdx.json +6 -6
package/lib/webauthn.js CHANGED
@@ -9,7 +9,7 @@
9
9
  * the attestation object + authenticatorData, decode the COSE credential public
10
10
  * key, and verify each defined attestation-statement format (packed, tpm,
11
11
  * android-key, apple, fido-u2f, none, compound, and android-safetynet behind an
12
- * opt-in) -- the attestation-statement signature and each format's structural
12
+ * opt-in): the attestation-statement signature and each format's structural
13
13
  * bindings. The attestation CBOR is decoded by the strict,
14
14
  * fail-closed `pki.cbor` codec (WebAuthn keys are CTAP2-canonical), the signature by
15
15
  * `pki.webcrypto`. Chaining the returned x5c trust path to a caller-pinned root via
@@ -66,8 +66,8 @@ function _ucmp(a, b) {
66
66
  // A decoded node is a primitive universal INTEGER (so `.content` is a real buffer,
67
67
  // not null as it is for a constructed node).
68
68
  function _isInteger(node) { return !!node && !node.constructed && node.tagClass === "universal" && node.tagNumber === asn1.TAGS.INTEGER; }
69
- // alg -> the digest a TPM attestation's certInfo.extraData is taken under (the ONLY
70
- // consumer). Every ECDSA/RSA algorithm a TPM AIK may sign with -- including the RFC 9864
69
+ // alg -> the digest a TPM attestation's certInfo.extraData is taken under (its only
70
+ // consumer). Every ECDSA/RSA algorithm a TPM AIK may sign with, including the RFC 9864
71
71
  // fully-specified ECDSA ids (ESP256/384/512) -- MUST appear here, or the TPM extraData
72
72
  // step rejects the attestation before the signature is evaluated. EdDSA (-8/-19/-53) is
73
73
  // absent by design: a TPM 2.0 AIK never signs with EdDSA, so such an attestation is
@@ -83,7 +83,7 @@ function _coseAlgHash(alg, E) {
83
83
 
84
84
  // authData = rpIdHash[32] || flags[1] || signCount[4 BE] || (AT? attestedCredentialData) || (ED? extensions CBOR).
85
85
  // A bounded big-endian read: every slice is length-checked before it is taken, so a
86
- // truncated / oversize field fails closed rather than reading past the buffer.
86
+ // truncated / oversize field fails closed and never reads past the buffer.
87
87
  var AAGUID_LEN = 16;
88
88
  function _parseAuthData(buf, E) {
89
89
  if (!Buffer.isBuffer(buf) || buf.length < 37) throw E("webauthn/bad-auth-data", "authenticatorData is shorter than the 37-byte minimum (RFC WebAuthn sec. 6.1)");
@@ -98,7 +98,7 @@ function _parseAuthData(buf, E) {
98
98
  // "backed up" if it is not "backup eligible" (WebAuthn sec. 6.1).
99
99
  if (out.flags.bs && !out.flags.be) throw E("webauthn/bad-auth-data", "authenticatorData sets Backup State (BS) without Backup Eligibility (BE) (WebAuthn sec. 6.1)");
100
100
  // The reserved flag bits (bit 1 = 0x02, bit 5 = 0x20) are undefined; a conforming
101
- // authenticator leaves them 0. Reject a set reserved bit rather than ignore an
101
+ // authenticator leaves them 0. Reject a set reserved bit; never ignore an
102
102
  // unknown flag (WebAuthn sec. 6.1, fail-closed on undefined structure).
103
103
  if (flags & 0x22) throw E("webauthn/bad-auth-data", "authenticatorData sets a reserved (RFU) flag bit (WebAuthn sec. 6.1)");
104
104
  var off = 37;
@@ -129,7 +129,7 @@ function _parseAuthData(buf, E) {
129
129
  } else if (off < buf.length) {
130
130
  // authenticatorData is fixed-layout: with the ED flag clear there MUST be no
131
131
  // bytes after the attestedCredentialData (RFC WebAuthn sec. 6.1). Trailing
132
- // bytes are a malformed structure -- fail closed rather than ignore them.
132
+ // bytes are a malformed structure, so fail closed and never ignore them.
133
133
  throw E("webauthn/bad-auth-data", "authenticatorData has trailing bytes after attestedCredentialData with the ED flag clear");
134
134
  }
135
135
  return out;
@@ -156,16 +156,16 @@ function _decodeCoseKey(node) {
156
156
  * registration -- back into the object `verifyAssertion` takes. `pki.webauthn.verify`
157
157
  * returns that object, but the durable form is bytes: the object carries `Buffer`
158
158
  * values, so a JSON round trip through a datastore yields
159
- * `{"type":"Buffer","data":[...]}` rather than the object that went in, and existing
159
+ * `{"type":"Buffer","data":[...]}` in place of the object that went in, and existing
160
160
  * credential stores already hold COSE bytes whoever wrote them. Without this the only
161
161
  * routes into the decoder were `parseAttestationObject` and `parseAuthenticatorData`,
162
- * both of which parse a CONTAINING structure -- so recovering a stored key meant
162
+ * both of which parse a containing structure, so recovering a stored key meant
163
163
  * fabricating an authenticatorData that never existed.
164
164
  *
165
165
  * The same validation the attestation path applies: the key type, the algorithm, the
166
166
  * curve, and the coordinates are checked, and anything that is not a credential COSE
167
167
  * key is refused with `webauthn/bad-cose-key`. `verifyAssertion` accepts either form
168
- * for `credentialPublicKey`, so calling this first is a convenience rather than a step.
168
+ * for `credentialPublicKey`, so calling this first is a convenience, not a required step.
169
169
  *
170
170
  * @example
171
171
  * // requires: `attestationObject` / `clientDataHash` -- what a browser returns from a
@@ -1283,23 +1283,23 @@ function _checkAndroidKeyDescription(cert, clientDataHash) {
1283
1283
  *
1284
1284
  * Give it the client data in exactly one of the two forms, neither inferred from the
1285
1285
  * other's absence: the raw `opts.clientDataJSON`, or the SHA-256 digest of it as the
1286
- * second argument. Given the JSON, this reads it -- the ceremony TYPE is checked
1286
+ * second argument. Given the JSON, this reads it: the ceremony type is checked
1287
1287
  * unconditionally, because which ceremony a response belongs to is fixed by the
1288
- * specification rather than chosen by a caller, and a login response replayed into a
1288
+ * specification and never chosen by a caller, and a login response replayed into a
1289
1289
  * registration is exactly what that check stops. The challenge, origin and top-level
1290
1290
  * origin are checked when you supply what you issued, and `clientData.checked` reports
1291
1291
  * which ran. Given only the digest, nothing reads it and `clientData` is null.
1292
1292
  *
1293
1293
  * The verdict field is `attestationVerified`, and the name is the point: a sound
1294
1294
  * attestation statement is not the same claim as an acceptable registration. The
1295
- * statement says nothing about WHICH relying party asked for it, or whether a user
1295
+ * statement says nothing about which relying party asked for it, or whether a user
1296
1296
  * was present -- so an attestation naming another origin's RP ID, with user presence
1297
1297
  * clear, is perfectly sound and must not be registered. Supply `expectedRpId`,
1298
1298
  * `requireUserPresence`, `requireUserVerification` and `allowedAlgorithms` and those
1299
1299
  * are checked here; `bindingChecked` reports which ran, so a check that passed can be
1300
1300
  * told from one that never happened.
1301
1301
  *
1302
- * Four more fields appear only where they mean something, rather than as nulls on every
1302
+ * Four more fields appear only where they mean something, never as nulls on every
1303
1303
  * verdict: `metadata` when the catalogue governed, `anchoredElements` when a trust path
1304
1304
  * was anchored, `compound` for a sec. 8.9 statement's per-element results, and
1305
1305
  * `safetyNet` with `chainValidatedAt` for an android-safetynet response.
@@ -1308,7 +1308,7 @@ function _checkAndroidKeyDescription(cert, clientDataHash) {
1308
1308
  * `credentialId`, `credentialPublicKey` and the initial `signCount`. The credential key
1309
1309
  * comes back in both forms: the decoded object, and `credentialPublicKeyBytes`, which is
1310
1310
  * what a credential row should hold -- the object carries `Buffer` values, so a JSON round
1311
- * trip through a datastore returns `{"type":"Buffer","data":[...]}` rather than the object
1311
+ * trip through a datastore returns `{"type":"Buffer","data":[...]}` in place of the object
1312
1312
  * that went in. `pki.webauthn.parseCoseKey` reads those bytes back, and
1313
1313
  * `verifyAssertion` accepts either form.
1314
1314
  *
@@ -1317,10 +1317,10 @@ function _checkAndroidKeyDescription(cert, clientDataHash) {
1317
1317
  * certInfo Name/extraData, the android KeyDescription, the fido-u2f verificationData).
1318
1318
  * Chaining the returned `trustPath` (the x5c certificates in `pki.path.validate`
1319
1319
  * order -- anchor-adjacent first, leaf last) to a trusted root is a separate step,
1320
- * and there are three ways to take it. Pass `opts.metadata` -- a
1320
+ * and there are three ways to take it. Pass `opts.metadata`, a
1321
1321
  * `pki.webauthn.verifyMetadataBlob` result -- and the authenticator's registered
1322
1322
  * attestation roots are resolved from its own identifier and the trust path is
1323
- * required to reach one, so an unlisted or revoked model is refused rather than
1323
+ * required to reach one, so an unlisted or revoked model is refused and never
1324
1324
  * reported as verified. Pass `opts.rootCertificates` and the path is required to
1325
1325
  * validate to a root you pin: this is what anchors the formats the catalogue does
1326
1326
  * not cover, since Apple does not publish its authenticators to the FIDO Metadata
@@ -1343,7 +1343,7 @@ function _checkAndroidKeyDescription(cert, clientDataHash) {
1343
1343
  * but it does mean "anchored" covered fewer elements than the statement holds.
1344
1344
  *
1345
1345
  * @opts
1346
- * clientDataJSON -- the RAW clientDataJSON bytes; supply this OR the digest argument
1346
+ * clientDataJSON the raw clientDataJSON bytes; supply this or the digest argument
1347
1347
  * expectedChallenge -- the challenge bytes this ceremony issued (needs clientDataJSON)
1348
1348
  * expectedOrigin -- the origin string, or an array of acceptable origins
1349
1349
  * expectedTopOrigin -- the acceptable top-level origin(s), or null to require an
@@ -1802,7 +1802,7 @@ void constants;
1802
1802
  * @related pki.webauthn.verify, pki.webauthn.metadataFor
1803
1803
  *
1804
1804
  * Verify a FIDO Metadata Service BLOB -- the signed catalogue of every registered
1805
- * authenticator model, its attestation roots, and its certification status -- and
1805
+ * authenticator model, its attestation roots, and its certification status, and
1806
1806
  * return its entries indexed by aaguid for lookup. `blob` is caller-supplied bytes or
1807
1807
  * a string; retrieval is out of scope, so this never touches the network.
1808
1808
  *
@@ -1817,7 +1817,7 @@ void constants;
1817
1817
  * still answer for itself: `stale` and `allowStale` for freshness, `rollbackChecked` and
1818
1818
  * the `previousNo` it was compared against for rollback, `statusPolicy` and
1819
1819
  * `rejectUnknownStatus` for the status reading every later lookup will use. A rule that
1820
- * did not run reads as not-run rather than as passed.
1820
+ * did not run reads as not-run, never as passed.
1821
1821
  *
1822
1822
  * @intro No FIDO root ships with this toolkit and there is no trust-on-first-use:
1823
1823
  * which metadata authority to trust is the operator's decision, exactly as a root
@@ -1833,12 +1833,12 @@ void constants;
1833
1833
  * rollback check by forgetting to pass it.
1834
1834
  * - `allowStale` -- accept a BLOB past its `nextUpdate`. Off by default.
1835
1835
  * - `statusPolicy` -- which status reports disqualify an authenticator: `"any"`
1836
- * (default -- any disqualifying report ever filed), `"latest-by-date"` (only the
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
1839
  * - `rejectUnknownStatus` -- treat a status this toolkit does not recognise as
1840
1840
  * disqualifying. Off by default: the specification requires an unknown status be
1841
- * ignored rather than failed on.
1841
+ * ignored, never failed on.
1842
1842
  *
1843
1843
  * @example
1844
1844
  * // requires: `mdsBlobBytes` -- the signed BLOB from https://mds3.fidoalliance.org/
@@ -1862,13 +1862,13 @@ void constants;
1862
1862
  * @related pki.webauthn.verifyMetadataBlob, pki.webauthn.metadataAnchors
1863
1863
  *
1864
1864
  * The verified metadata entry for an authenticator model, or `null` when the BLOB
1865
- * lists none. `metadata` is a `verifyMetadataBlob` RESULT, never raw bytes -- so a
1865
+ * lists none. `metadata` is a `verifyMetadataBlob` result, never raw bytes, so a
1866
1866
  * lookup can never be answered out of a BLOB nobody verified.
1867
1867
  *
1868
1868
  * `identifier` is whichever of the catalogue's two key spaces names the authenticator:
1869
- * its aaguid, or -- for a U2F authenticator, which carries none -- the key identifier
1869
+ * its aaguid, or, for a U2F authenticator which carries none, the key identifier
1870
1870
  * of its attestation certificate (RFC 5280 sec. 4.2.1.2 method 1, 40 hex digits). The
1871
- * two are disjoint by shape, so the form is dispatched on rather than guessed at, and
1871
+ * two are disjoint by shape, so the form is dispatched on and never guessed at, and
1872
1872
  * anything matching neither is a miss. The all-zero aaguid means "this authenticator
1873
1873
  * declares no model identity" and matches nothing.
1874
1874
  *
@@ -1889,7 +1889,7 @@ void constants;
1889
1889
  * @defends webauthn-revoked-authenticator-accepted (CWE-299)
1890
1890
  * @related pki.webauthn.metadataFor, pki.path.validate
1891
1891
  *
1892
- * The parsed attestation root certificates a metadata entry registers -- the anchors an
1892
+ * The parsed attestation root certificates a metadata entry registers: the anchors an
1893
1893
  * attestation from that model must chain to. An entry whose status reports disqualify
1894
1894
  * the model registers none: the catalogue exists to say which authenticators are still
1895
1895
  * trusted, so handing back the roots of one it has revoked would answer a different
@@ -1899,11 +1899,11 @@ void constants;
1899
1899
  * does not: pass the verified `metadata` and its own `statusPolicy` governs and its
1900
1900
  * freshness is re-checked, pass `time` and reports are judged as of that instant, pass
1901
1901
  * the `certificate` an attestation actually presented and a report naming a single
1902
- * certificate is judged against that one rather than denying every device the entry
1902
+ * certificate is judged against that one, so the entry does not deny every device it
1903
1903
  * covers. With none of them: any disqualifying report denies, judged now.
1904
1904
  *
1905
- * Decoding is per entry rather than for the
1906
- * whole BLOB on purpose: a handful of certificates in the live metadata do not parse
1905
+ * Decoding is per entry, and deliberately not for the
1906
+ * whole BLOB: a handful of certificates in the live metadata do not parse
1907
1907
  * under a strict decoder, and decoding everything up front would let one vendor's
1908
1908
  * malformed root refuse the entire catalogue for every other authenticator in it.
1909
1909
  *
@@ -1959,7 +1959,7 @@ var CLIENT_DATA_TYPE = Object.assign(Object.create(null), { "webauthn.create": 1
1959
1959
  * the page that framed it, so a relying party that allows framing at all should say
1960
1960
  * which pages may do it. `expectedTopOrigin: null` requires an unframed ceremony,
1961
1961
  * which an origin list cannot express. Whether a ceremony was framed is stated by
1962
- * BOTH `crossOrigin` and `topOrigin` and is only usable when they agree: a response
1962
+ * both `crossOrigin` and `topOrigin` and is only usable when they agree: a response
1963
1963
  * declaring itself cross-origin does not satisfy `null` by omitting the origin, and
1964
1964
  * one that does not declare itself cross-origin makes no framing claim for an origin
1965
1965
  * list to accept.
@@ -2125,7 +2125,7 @@ var _CLIENT_DATA_OPTS = Object.assign(Object.create(null), {
2125
2125
  * @spec W3C WebAuthn Level 3 sec. 6.1
2126
2126
  * @related pki.webauthn.verifyAssertion, pki.webauthn.parseAttestationObject
2127
2127
  *
2128
- * Decode a BARE authenticatorData, fail-closed -- the form an authentication
2128
+ * Decode a bare authenticatorData, fail-closed: the form an authentication
2129
2129
  * assertion returns, with no attestation-object wrapper around it. Same parser the
2130
2130
  * registration path uses: the 37-byte minimum, the reserved (RFU) flag bits, the
2131
2131
  * Backup State / Backup Eligibility rule, the 1..1023 credentialId bound, a
@@ -2165,12 +2165,12 @@ var _ASSERT_OPTS = Object.assign(Object.create(null), {
2165
2165
  * @related pki.webauthn.parseAuthenticatorData, pki.webauthn.verify
2166
2166
  *
2167
2167
  * Verify an authentication assertion's signature: the authenticator signs
2168
- * `authenticatorData || SHA-256(clientDataJSON)` as RAW bytes with the credential
2168
+ * `authenticatorData || SHA-256(clientDataJSON)` as raw bytes with the credential
2169
2169
  * key registered earlier -- no COSE_Sign1 wrapper, so a COSE message verifier is
2170
2170
  * the wrong tool and fails on structure before it ever reaches the signature. An
2171
2171
  * ES256 assertion signature is an ASN.1 DER `SEQUENCE { r, s }`, converted here
2172
2172
  * with the same order-aware reader the attestation path uses, so an r or s outside
2173
- * `[1, n-1]` is refused rather than normalized.
2173
+ * `[1, n-1]` is refused, never normalized.
2174
2174
  *
2175
2175
  * `signatureVerified`, not `verified`: this establishes that the holder of the
2176
2176
  * registered credential key produced this response. What makes the response
@@ -2194,7 +2194,7 @@ var _ASSERT_OPTS = Object.assign(Object.create(null), {
2194
2194
  * signature -- the assertion signature bytes
2195
2195
  * credentialPublicKey -- the stored COSE key (as parseAttestationObject surfaced it)
2196
2196
  * previousSignCount -- the stored counter, enabling the sec. 7.2 step 21 rule
2197
- * expectedRpId, requireUserPresence, requireUserVerification, allowedAlgorithms -- the bindings above
2197
+ * expectedRpId, requireUserPresence, requireUserVerification, allowedAlgorithms: the bindings above
2198
2198
  *
2199
2199
  * @example
2200
2200
  * // requires: `assertion` -- credential.response from navigator.credentials.get();
package/lib/webcrypto.js CHANGED
@@ -13,8 +13,8 @@
13
13
  * A zero-dependency W3C Web Cryptography API (`Crypto` / `SubtleCrypto`
14
14
  * / `CryptoKey`) built directly on Node's native `node:crypto`. It is
15
15
  * the toolkit's injectable crypto engine, presented in the standard
16
- * WebCrypto shape so operators -- and every higher structure (X.509,
17
- * CMS, OCSP) -- reach for one familiar surface.
16
+ * WebCrypto shape, so operators, and every higher structure (X.509,
17
+ * CMS, OCSP), reach for one familiar surface.
18
18
  *
19
19
  * Unlike the browser's built-in `crypto.subtle`, this engine is
20
20
  * **PQC-first without being PQC-only**: the FIPS 204 ML-DSA and FIPS
@@ -31,7 +31,7 @@
31
31
  *
32
32
  * @card
33
33
  * A zero-dep, PQC-first W3C WebCrypto (`SubtleCrypto`) engine over
34
- * `node:crypto` -- ML-DSA + SLH-DSA signatures alongside the full
34
+ * `node:crypto`: ML-DSA and SLH-DSA signatures alongside the full
35
35
  * classical algorithm set.
36
36
  */
37
37
 
@@ -62,7 +62,7 @@ function _toArrayBuffer(buf) {
62
62
 
63
63
  function _wcErr(code, msg) { return new WebCryptoError(code, msg); }
64
64
  // STRICT base64url decode via the shared encoding guard: a missing / non-canonical
65
- // / non-alphabet value throws webcrypto/data rather than silently importing WRONG
65
+ // / non-alphabet value throws webcrypto/data instead of silently importing wrong
66
66
  // key material (the lenient Buffer.from(String(undefined),"base64url") returned a
67
67
  // bogus 6-byte key). Node's own canonical JWK export (_rawPublic) round-trips
68
68
  // cleanly, so the strictness is benign there and fail-closed on untrusted import.
@@ -82,7 +82,7 @@ function _normalizeAlg(algorithm, who) {
82
82
 
83
83
  // The counterpart to _normalizeAlg's upper-casing: _normalizeAlg upper-cases every algorithm name for
84
84
  // case-insensitive INTERNAL matching, but the name a CryptoKey EMITS on `.algorithm.name` MUST carry the
85
- // WebCrypto-registered casing, which for a few algorithms is not all-upper -- "RSASSA-PKCS1-v1_5" (lowercase
85
+ // WebCrypto-registered casing, which for a few algorithms is not all-upper: "RSASSA-PKCS1-v1_5" (lowercase
86
86
  // v), "Ed25519" / "Ed448" (mixed). Every emitted algorithm.name passes through here so a consumer that
87
87
  // compares against the standard name (e.g. a signer matching a CryptoKey to a certificate key) matches.
88
88
  var _STD_ALG_NAME = { "RSASSA-PKCS1-V1_5": "RSASSA-PKCS1-v1_5", "ED25519": "Ed25519", "ED448": "Ed448" };
@@ -102,8 +102,8 @@ var HASH_NODE = {
102
102
  };
103
103
 
104
104
  // FIPS 202 extendable-output functions, as message digests. Deliberately a
105
- // SEPARATE table from HASH_NODE rather than rows added to it: an XOF's output
106
- // length is a parameter rather than a property of its name, and node:crypto
105
+ // separate table from HASH_NODE, not rows added to it: an XOF's output
106
+ // length is a parameter and not a property of its name, and node:crypto
107
107
  // refuses an XOF at every other HASH_NODE consumer (signature, HMAC, HKDF,
108
108
  // PBKDF2, OAEP). Widening the shared table would turn those consumers' typed
109
109
  // "unsupported hash" rejection into a raw error from the underlying library.
@@ -126,7 +126,7 @@ function _hashNode(h, who) {
126
126
  // W3C HMAC get-key-length: an explicit `length` is used as given (validated
127
127
  // to a positive multiple of 8, so the byte-level key material is exact and
128
128
  // never a raw RangeError out of randomBytes); an OMITTED length defaults to
129
- // the BLOCK size of the hash -- the HMAC key-pad width -- NOT the digest
129
+ // the block size of the hash, the HMAC key-pad width, and not the digest
130
130
  // size. A digest-size default would mint different key material than every
131
131
  // conforming WebCrypto for identical inputs, so MACs keyed through this
132
132
  // engine would fail to verify elsewhere.
@@ -163,7 +163,7 @@ var SLH_DSA_NODE = {};
163
163
  ].forEach(function (s) { SLH_DSA_NODE["SLH-DSA-" + s.toUpperCase()] = "slh-dsa-" + s; });
164
164
 
165
165
  // The algorithm names each keyed operation recognizes. Membership is checked
166
- // BEFORE the algorithm/key name binding so an unrecognized algorithm reports
166
+ // before the algorithm/key name binding so an unrecognized algorithm reports
167
167
  // NotSupportedError while a recognized-but-wrong-for-this-key one reports
168
168
  // InvalidAccessError, matching the W3C error ordering.
169
169
  var SIGN_VERIFY_NAMES = {};
@@ -173,9 +173,9 @@ var SIGN_VERIFY_NAMES = {};
173
173
  var ENCRYPT_DECRYPT_NAMES = { "RSA-OAEP": true, "AES-GCM": true, "AES-CBC": true, "AES-CTR": true };
174
174
  var DERIVE_NAMES = { "ECDH": true, "X25519": true, "X448": true, "HKDF": true, "PBKDF2": true, "X963KDF": true };
175
175
  // The secret-key / KDF algorithms whose key material is raw octets (imported via "raw" or a
176
- // JWK "oct"), NEVER an SPKI / PKCS#8 asymmetric-key structure. importKey("spki"|"pkcs8", ...)
177
- // under one of these names is unsupported (W3C: NotSupportedError) -- and, without this gate, it
178
- // would mint a mislabeled CryptoKey wrapping an asymmetric handle AND dodge the algorithm-keyed
176
+ // JWK "oct"), never an SPKI / PKCS#8 asymmetric-key structure. importKey("spki"|"pkcs8", ...)
177
+ // under one of these names is unsupported (W3C: NotSupportedError) and, without this gate, it
178
+ // would mint a mislabeled CryptoKey wrapping an asymmetric handle and dodge the algorithm-keyed
179
179
  // pkcs8 pre-validation (e.g. the RFC 9935 ML-KEM CHOICE guard).
180
180
  var SECRET_KEY_NAMES = { "AES-GCM": true, "AES-CBC": true, "AES-CTR": true, "AES-KW": true, "HMAC": true, "HKDF": true, "PBKDF2": true, "X963KDF": true };
181
181
 
@@ -1197,8 +1197,8 @@ function _curveFromKey(ko) {
1197
1197
  * (either), or `raw` (symmetric, or an uncompressed EC / OKP public
1198
1198
  * point). Throws unless the key was created `extractable`.
1199
1199
  *
1200
- * `raw` is defined for public and secret keys only -- asking for it on a private
1201
- * key throws `webcrypto/not-supported` rather than answering with the public half.
1200
+ * `raw` is defined for public and secret keys only; asking for it on a private
1201
+ * key throws `webcrypto/not-supported` instead of answering with the public half.
1202
1202
  * This matters through `wrapKey`, which forwards the caller's format here: wrapping
1203
1203
  * a private key as `raw` would otherwise escrow the public key, and unwrapping it
1204
1204
  * returns a handle announcing `usages: ["sign"]` that cannot sign, with the private
@@ -1206,7 +1206,7 @@ function _curveFromKey(ko) {
1206
1206
  *
1207
1207
  * A private `jwk` round-trips as a private key for every algorithm, ML-DSA, ML-KEM
1208
1208
  * and SLH-DSA included: those are `kty: "AKP"` and carry the private half in `priv`
1209
- * rather than the `d` an EC or OKP key uses.
1209
+ * in place of the `d` an EC or OKP key uses.
1210
1210
  *
1211
1211
  * @example
1212
1212
  * var keyPair = await pki.webcrypto.subtle.generateKey({ name: "Ed25519" }, true, ["sign", "verify"]);
package/lib/x509-sign.js CHANGED
@@ -8,18 +8,18 @@
8
8
  * @title Certificates
9
9
  * @intro The X.509 certificate-issuance producing side. `pki.x509.sign` builds a `TBSCertificate`,
10
10
  * signs it, and emits a `Certificate` (RFC 5280 sec. 4) that `pki.schema.x509.parse`,
11
- * `pki.path.validate`, and OpenSSL all accept -- self-signed or CA-signed, over any signature
11
+ * `pki.path.validate`, and OpenSSL all accept, self-signed or CA-signed, over any signature
12
12
  * algorithm the toolkit registry resolves: RSA (PKCS#1 v1.5 / PSS), ECDSA, EdDSA, ML-DSA, SLH-DSA,
13
13
  * and the composite (hybrid) arms. Parsing lives at `pki.schema.x509.parse`.
14
14
  * @spec RFC 5280
15
- * @card Build and sign an X.509 certificate -- self-signed or CA-signed, over any registry algorithm.
15
+ * @card Build and sign an X.509 certificate, self-signed or CA-signed, over any registry algorithm.
16
16
  */
17
17
  //
18
18
  // The whole algorithm matrix comes from the shared sign-scheme resolver (the same registry
19
19
  // pki.cms.sign / pki.tsp.sign drive), so a new algorithm is a registry row, never a branch here. The
20
20
  // TBS + extension DER is hand-assembled through the canonical asn1.build.* layer (the shipped
21
21
  // cms/tsp/ocsp producing pattern); the strict schema-x509 decoder round-trips it, and that round-trip
22
- // -- plus OpenSSL interop -- is the divergence guard.
22
+ // (plus OpenSSL interop) is the divergence guard.
23
23
 
24
24
  var asn1 = require("./asn1-der");
25
25
  var oid = require("./oid");
@@ -32,12 +32,12 @@ var pkix = require("./schema-pkix");
32
32
  var pkiBuild = require("./pki-build");
33
33
 
34
34
  var CertificateError = frameworkError.CertificateError;
35
- // The x509 schema namespace + Name parser -- the SAME RDNSequence parser pki.schema.x509.parse uses, so
35
+ // The x509 schema namespace and Name parser: the same RDNSequence parser pki.schema.x509.parse uses, so
36
36
  // a raw Name DER is validated fully (structure, DirectoryString types) with the frozen x509/* codes.
37
37
  var NS = pkix.makeNS("x509", CertificateError, oid);
38
38
  var NAME_SCHEMA = pkix.name(NS);
39
39
  var SPKI_SCHEMA = pkix.spki(NS);
40
- // The SAME RFC 5280 sec. 4.2.1 extension value decoders pki.schema.x509.parse uses, so a recognized
40
+ // The same RFC 5280 sec. 4.2.1 extension value decoders pki.schema.x509.parse uses, so a recognized
41
41
  // pre-encoded (array-form) extension is fully validated with the frozen x509/* codes.
42
42
  var EXT_DECODERS = pkix.certExtensionDecoders(NS).byOid;
43
43
  var b = asn1.build;
@@ -60,7 +60,7 @@ var KNOWN_EXT_KEYS = {
60
60
 
61
61
  // The shared PKIX producing primitives (lib/pki-build.js), bound to the x509 namespace so they keep the
62
62
  // frozen x509/* codes. Thin local aliases let the x509 call sites read unchanged. csr-sign binds the same
63
- // builder to its own namespace (Hard rule #5 -- one encode definition, no per-format hand-roll).
63
+ // builder to its own namespace: one encode definition, no per-format hand-roll.
64
64
  var _b = pkiBuild.makeBuilder({ ErrorClass: CertificateError, prefix: "x509", O: O, NS: NS, NAME_SCHEMA: NAME_SCHEMA, SPKI_SCHEMA: SPKI_SCHEMA, EXT_DECODERS: EXT_DECODERS });
65
65
  var _encodeName = _b.encodeName, _isEmptyName = _b.isEmptyName, _reqDer = _b.reqDer,
66
66
  _assertValidSpki = _b.assertValidSpki, _assertValidExtension = _b.assertValidExtension,
@@ -91,7 +91,7 @@ function _akiKeyId(val, ctx) {
91
91
  function _buildExtensions(extSpec, ctx) {
92
92
  if (extSpec == null) return [];
93
93
  if (Array.isArray(extSpec)) {
94
- // Validate each pre-encoded extension, reject a duplicate extnID (RFC 5280 sec. 4.2 -- at most one
94
+ // Validate each pre-encoded extension, reject a duplicate extnID (RFC 5280 sec. 4.2 allows at most one
95
95
  // instance of an extension), and decode basicConstraints + keyUsage to apply the same CA
96
96
  // cross-field rules the object form enforces (below), so the array escape hatch cannot bypass them.
97
97
  var seenExt = {}, arrCa = false, arrKeyCertSign = false, arrPathLen = false;
@@ -129,7 +129,7 @@ function _buildExtensions(extSpec, ctx) {
129
129
  return arr;
130
130
  }
131
131
  if (typeof extSpec !== "object") throw _err("x509/bad-input", "extensions must be an object or an array of pre-encoded Extension DER");
132
- // Reject a typo'd / unsupported extension key at config-time rather than silently dropping it (a
132
+ // Reject a typo'd or unsupported extension key at config-time instead of silently dropping it (a
133
133
  // misspelled `keyUsag` would otherwise omit keyUsage). A custom extension goes in the array form.
134
134
  guard.identifier.assertKnownKeys(extSpec, KNOWN_EXT_KEYS, _err, "x509/bad-input", function (k) {
135
135
  return "unknown extension " + JSON.stringify(k) + " in the extensions spec; pass a pre-encoded Extension DER via the array form for a custom extension";
@@ -169,8 +169,8 @@ function _buildExtensions(extSpec, ctx) {
169
169
  // thin wrapper here so the certificate validity label reads "certificate notBefore/notAfter".
170
170
  function _timeDer(date, which) { return _b.timeDer(date, "certificate " + which); }
171
171
  // A supplied issuer certificate MUST be a CA that may sign certificates: basicConstraints present AND
172
- // critical AND cA=TRUE (RFC 5280 sec. 4.2.1.9), and -- when a keyUsage extension is present -- the
173
- // keyCertSign bit (sec. 4.2.1.3). Refuse a non-CA issuer rather than mint a certificate that will not
172
+ // critical AND cA=TRUE (RFC 5280 sec. 4.2.1.9), plus, when a keyUsage extension is present, the
173
+ // keyCertSign bit (sec. 4.2.1.3). Refuse a non-CA issuer instead of minting a certificate that will not
174
174
  // chain. Returns the issuer's pathLenConstraint (or null) so the caller can honor it.
175
175
  function _assertIssuerIsCa(issuerCert) {
176
176
  var exts = issuerCert.extensions || [];
@@ -237,8 +237,8 @@ function _hasCriticalSan(extSpec) {
237
237
  * key being certified), `notBefore` / `notAfter` (`Date`s), an optional `serialNumber`, and an optional
238
238
  * `extensions` object. `issuer` is the signing side: `{ key }` alone issues a self-signed certificate
239
239
  * (issuer = subject, signed with the subject's own key); `{ name, publicKey, key }` or `{ cert, key }`
240
- * issues a CA-signed one. The signature algorithm is resolved from the signing key -- RSA (PKCS#1 v1.5
241
- * or PSS via `opts.pss`), ECDSA, EdDSA, ML-DSA, SLH-DSA, or a composite arm -- so every algorithm the
240
+ * issues a CA-signed one. The signature algorithm is resolved from the signing key: RSA (PKCS#1 v1.5
241
+ * or PSS via `opts.pss`), ECDSA, EdDSA, ML-DSA, SLH-DSA, or a composite arm, so every algorithm the
242
242
  * toolkit signs with is available here without a per-algorithm branch.
243
243
  *
244
244
  * The version is derived from the field set (v3 when extensions are present, else v1). Serial bounds
@@ -251,7 +251,7 @@ function _hasCriticalSan(extSpec) {
251
251
  *
252
252
  * @opts
253
253
  * - `pem` (boolean) -- return a PEM `CERTIFICATE` string instead of DER.
254
- * - `pss` (boolean) -- sign an RSA key with RSASSA-PSS rather than PKCS#1 v1.5.
254
+ * - `pss` (boolean) -- sign an RSA key with RSASSA-PSS instead of PKCS#1 v1.5.
255
255
  * - `digestAlgorithm` (string) -- override the message digest where the algorithm permits a choice.
256
256
  * @example
257
257
  * var pair = await pki.key.generate("Ed25519");
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@blamejs/pki",
3
- "version": "0.5.7",
3
+ "version": "0.5.8",
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",
@@ -70,6 +70,7 @@
70
70
  "fuzz": "npm ci --prefix fuzz && npx --prefix fuzz jazzer fuzz/asn1-der.fuzz.js -- -max_total_time=60",
71
71
  "gates": "node test/layer-0-primitives/codebase-patterns.test.js && node scripts/validate-source-comment-blocks.js && node scripts/check-api-snapshot.js",
72
72
  "coverage": "c8 --include=lib/** --include=index.js --reporter=text-summary --reporter=lcov node test/smoke.js",
73
+ "check:prose": "node scripts/check-operator-prose.js",
73
74
  "check:swallows": "node scripts/check-swallow-coverage.js",
74
75
  "coverage:gated": "npm run coverage && npm run check:swallows",
75
76
  "coverage:unified": "node scripts/coverage-unified.js",
@@ -79,7 +80,7 @@
79
80
  },
80
81
  "devDependencies": {
81
82
  "c8": "12.0.0",
82
- "esbuild": "0.28.1",
83
+ "esbuild": "0.28.2",
83
84
  "eslint": "10.8.1"
84
85
  }
85
86
  }
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:a8c014a2-3631-443a-b33d-f10501d992fb",
5
+ "serialNumber": "urn:uuid:7e436b38-9f18-4222-a517-c69211740ba0",
6
6
  "version": 1,
7
7
  "metadata": {
8
- "timestamp": "2026-08-16T09:48:05.398Z",
8
+ "timestamp": "2026-08-18T02:52:33.662Z",
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.7",
22
+ "bom-ref": "@blamejs/pki@0.5.8",
23
23
  "type": "application",
24
24
  "name": "pki",
25
- "version": "0.5.7",
25
+ "version": "0.5.8",
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.7",
29
+ "purl": "pkg:npm/%40blamejs/pki@0.5.8",
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.7",
57
+ "ref": "@blamejs/pki@0.5.8",
58
58
  "dependsOn": []
59
59
  }
60
60
  ]