@blamejs/pki 0.5.7 → 0.5.9
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 +418 -386
- package/MIGRATING.md +43 -0
- package/README.md +12 -12
- package/lib/acme.js +31 -31
- package/lib/asn1-der.js +10 -10
- package/lib/attrcert-sign.js +19 -19
- package/lib/byte-reader.js +6 -6
- package/lib/byte-writer.js +5 -5
- package/lib/cbor-det.js +27 -24
- package/lib/cmc-build.js +97 -92
- package/lib/cmc-verify.js +106 -30
- package/lib/cmp-build.js +25 -25
- package/lib/cmp-session.js +70 -70
- package/lib/cmp-verify.js +71 -57
- package/lib/cms-compress.js +7 -7
- package/lib/cms-decrypt.js +90 -72
- package/lib/cms-encrypt.js +32 -32
- package/lib/cms-sign.js +74 -55
- package/lib/cms-verify.js +97 -75
- package/lib/composite-sig.js +13 -13
- package/lib/constants.js +4 -4
- package/lib/crl-sign.js +22 -22
- package/lib/crl-verify.js +7 -6
- package/lib/crmf-sign.js +14 -14
- package/lib/csr-sign.js +8 -8
- package/lib/ct.js +37 -37
- package/lib/edwards-point.js +7 -7
- package/lib/est.js +98 -55
- package/lib/framework-error.js +5 -5
- package/lib/guard-all.js +5 -3
- package/lib/guard-async.js +4 -4
- package/lib/guard-bytes.js +79 -79
- package/lib/guard-compress.js +17 -17
- package/lib/guard-crypto.js +1 -1
- package/lib/guard-der.js +152 -0
- package/lib/guard-encoding.js +15 -15
- package/lib/guard-header.js +3 -3
- package/lib/guard-identifier.js +16 -16
- package/lib/guard-json.js +15 -15
- package/lib/guard-limits.js +7 -7
- package/lib/guard-name.js +81 -16
- package/lib/guard-parsed.js +80 -80
- package/lib/guard-range.js +19 -19
- package/lib/guard-secret.js +11 -10
- package/lib/guard-text.js +6 -6
- package/lib/guard-time.js +10 -10
- package/lib/hpke.js +18 -17
- package/lib/http-digest.js +35 -35
- package/lib/http-retry-after.js +13 -13
- package/lib/http-transport.js +20 -19
- package/lib/inspect.js +53 -53
- package/lib/ip-utils.js +2 -2
- package/lib/jose.js +13 -13
- package/lib/key.js +16 -16
- package/lib/lint.js +51 -51
- package/lib/merkle.js +51 -36
- package/lib/mime.js +18 -18
- package/lib/ocsp-verify.js +10 -10
- package/lib/ocsp.js +13 -13
- package/lib/oid.js +29 -29
- package/lib/path-validate.js +114 -113
- package/lib/pbes2.js +16 -16
- package/lib/pkcs12-build.js +53 -53
- package/lib/pki-build.js +48 -20
- package/lib/rc2.js +1 -1
- package/lib/rfc3339.js +5 -5
- package/lib/schema-all.js +31 -31
- package/lib/schema-attrcert.js +12 -12
- package/lib/schema-c509.js +149 -197
- package/lib/schema-cmc.js +58 -58
- package/lib/schema-cmp.js +43 -43
- package/lib/schema-cms.js +45 -45
- package/lib/schema-crl.js +7 -7
- package/lib/schema-crmf.js +28 -28
- package/lib/schema-csr.js +12 -12
- package/lib/schema-csrattrs.js +16 -16
- package/lib/schema-engine.js +18 -18
- package/lib/schema-ocsp.js +15 -15
- package/lib/schema-pkcs12.js +20 -20
- package/lib/schema-pkcs8.js +2 -2
- package/lib/schema-pkix.js +131 -126
- package/lib/schema-smime.js +19 -19
- package/lib/schema-tsp.js +12 -12
- package/lib/schema-x509.js +3 -3
- package/lib/shbs.js +18 -18
- package/lib/sign-scheme.js +13 -13
- package/lib/sigstore.js +10 -11
- package/lib/sleep.js +1 -1
- package/lib/smime.js +308 -96
- package/lib/tls-cert-compress.js +18 -18
- package/lib/trust.js +27 -27
- package/lib/tsp-sign.js +17 -17
- package/lib/validator-all.js +1 -1
- package/lib/validator-attcert.js +1 -1
- package/lib/validator-cose.js +43 -44
- package/lib/validator-keydesc.js +3 -3
- package/lib/validator-sig.js +13 -13
- package/lib/validator-tls.js +11 -11
- package/lib/validator-tpm.js +20 -19
- package/lib/webauthn-mds.js +66 -66
- package/lib/webauthn.js +33 -33
- package/lib/webcrypto.js +15 -15
- package/lib/x509-sign.js +13 -13
- package/package.json +3 -2
- 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)
|
|
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 (
|
|
70
|
-
// consumer). Every ECDSA/RSA algorithm a TPM AIK may sign with
|
|
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
|
|
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
|
|
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
|
|
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":[...]}`
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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,
|
|
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":[...]}`
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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`
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
1906
|
-
* whole BLOB
|
|
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
|
-
*
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
17
|
-
* CMS, OCSP)
|
|
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
|
|
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
|
|
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
|
|
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
|
-
//
|
|
106
|
-
// length is a parameter
|
|
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
|
|
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
|
-
//
|
|
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"),
|
|
177
|
-
// under one of these names is unsupported (W3C: NotSupportedError)
|
|
178
|
-
// would mint a mislabeled CryptoKey wrapping an asymmetric handle
|
|
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
|
|
1201
|
-
* key throws `webcrypto/not-supported`
|
|
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
|
-
*
|
|
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
|
|
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
|
|
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
|
-
//
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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),
|
|
173
|
-
// keyCertSign bit (sec. 4.2.1.3). Refuse a non-CA issuer
|
|
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
|
|
241
|
-
* or PSS via `opts.pss`), ECDSA, EdDSA, ML-DSA, SLH-DSA, or a composite arm
|
|
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
|
|
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.
|
|
3
|
+
"version": "0.5.9",
|
|
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.
|
|
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:
|
|
5
|
+
"serialNumber": "urn:uuid:0541b2cd-7339-4ee1-9122-38999da30d35",
|
|
6
6
|
"version": 1,
|
|
7
7
|
"metadata": {
|
|
8
|
-
"timestamp": "2026-08-
|
|
8
|
+
"timestamp": "2026-08-18T06:52:40.138Z",
|
|
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.9",
|
|
23
23
|
"type": "application",
|
|
24
24
|
"name": "pki",
|
|
25
|
-
"version": "0.5.
|
|
25
|
+
"version": "0.5.9",
|
|
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.9",
|
|
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.9",
|
|
58
58
|
"dependsOn": []
|
|
59
59
|
}
|
|
60
60
|
]
|