@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.
- package/CHANGELOG.md +405 -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 +3 -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-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 +21 -19
- 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 +144 -142
- 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/inspect.js
CHANGED
|
@@ -5,19 +5,19 @@
|
|
|
5
5
|
* @module pki.inspect
|
|
6
6
|
* @nav Tooling
|
|
7
7
|
* @title Inspect
|
|
8
|
-
* @intro Human-readable inspection of a parsed certificate
|
|
8
|
+
* @intro Human-readable inspection of a parsed certificate: the pure-JS
|
|
9
9
|
* equivalent of `openssl x509 -text`. `certificate(input)` ingests a PEM string,
|
|
10
10
|
* a DER Buffer, or an already-parsed certificate and returns a familiar
|
|
11
11
|
* OpenSSL-style report: version, serial, signature algorithm, the issuer and
|
|
12
12
|
* subject distinguished names, the validity window, the public-key details
|
|
13
13
|
* (curve or modulus size plus the raw point/modulus), every decoded extension
|
|
14
14
|
* with its critical flag, and the signature. It renders purely from the toolkit's
|
|
15
|
-
* own strict parser and two-way OID registry
|
|
16
|
-
* drift-prone second naming table
|
|
17
|
-
* OpenSSL build shows only as raw bytes. The format is stable and OpenSSL-*familiar
|
|
18
|
-
*
|
|
15
|
+
* own strict parser and two-way OID registry, with no OpenSSL dependency and no
|
|
16
|
+
* drift-prone second naming table, so it names extension and algorithm OIDs an
|
|
17
|
+
* OpenSSL build shows only as raw bytes. The format is stable and OpenSSL-*familiar*,
|
|
18
|
+
* never byte-identical to any one OpenSSL version (those disagree across
|
|
19
19
|
* releases). Rendering is best-effort: a malformed extension falls back to a hex
|
|
20
|
-
* dump
|
|
20
|
+
* dump and does not throw.
|
|
21
21
|
* @spec RFC 5280
|
|
22
22
|
* @card Read a certificate like `openssl x509 -text`, in pure JS.
|
|
23
23
|
*/
|
|
@@ -70,10 +70,10 @@ function _hexColon(buf, opts) {
|
|
|
70
70
|
return lines.join("\n");
|
|
71
71
|
}
|
|
72
72
|
|
|
73
|
-
// Coverage residual
|
|
74
|
-
// * `opts = opts || {}
|
|
73
|
+
// Coverage residual: two _hexColon default arms are unreachable through the public API.
|
|
74
|
+
// * `opts = opts || {}`. All call sites pass an explicit opts object literal, so the
|
|
75
75
|
// `|| {}` default never fires.
|
|
76
|
-
// * `" ".repeat(opts.indent || 0)
|
|
76
|
+
// * `" ".repeat(opts.indent || 0)`. Every wrap-mode caller passes a positive indent
|
|
77
77
|
// (pad.length + 8 >= 8, or inner.length == 16), so `opts.indent` is never falsy.
|
|
78
78
|
|
|
79
79
|
// Control-byte neutralization for a GeneralName string value routes through the
|
|
@@ -83,7 +83,7 @@ var _clean = guard.name.escapeControlBytes;
|
|
|
83
83
|
|
|
84
84
|
// The DN display string. pki.schema.pkix already assembles a fully RFC 4514-escaped
|
|
85
85
|
// dn (short names from pki.C.NAMES.DN_SHORT, values escaped via guard.name.escapeDnValue,
|
|
86
|
-
// with the '#'-hex / leading-'\' sentinel handled), so reuse it
|
|
86
|
+
// with the '#'-hex / leading-'\' sentinel handled), so reuse it and never re-escape
|
|
87
87
|
// the already-escaped values (which would double-escape a leading '#' / '\').
|
|
88
88
|
function _dnString(name) { return (name && name.dn) || ""; }
|
|
89
89
|
|
|
@@ -108,7 +108,7 @@ function _serial(cert, indent) {
|
|
|
108
108
|
if (hex.length % 2) hex = "0" + hex;
|
|
109
109
|
var buf = Buffer.from(hex, "hex");
|
|
110
110
|
// Strip a single DER positive-sign 00 byte (present when the value's high bit is
|
|
111
|
-
// set) so the printed serial is the integer
|
|
111
|
+
// set) so the printed serial is the integer value, matching OpenSSL, and not the
|
|
112
112
|
// encoding's leading octet.
|
|
113
113
|
if (buf.length > 1 && buf[0] === 0x00 && (buf[1] & 0x80)) buf = buf.subarray(1);
|
|
114
114
|
// Small non-negative serials render inline as decimal (0xhex), like OpenSSL;
|
|
@@ -122,9 +122,9 @@ function _serial(cert, indent) {
|
|
|
122
122
|
|
|
123
123
|
var CURVE_BITS = { "P-256": 256, "P-384": 384, "P-521": 521, "prime256v1": 256, "secp384r1": 384, "secp521r1": 521 };
|
|
124
124
|
var NIST_NAME = NAMES.NIST_CURVE;
|
|
125
|
-
// Every RSA-family key algorithm carries the same SPKI subjectPublicKey
|
|
125
|
+
// Every RSA-family key algorithm carries the same SPKI subjectPublicKey, an
|
|
126
126
|
// RSAPublicKey SEQUENCE { modulus, publicExponent } (RFC 4055 sec. 1.2 for
|
|
127
|
-
// id-RSASSA-PSS / id-RSAES-OAEP)
|
|
127
|
+
// id-RSASSA-PSS / id-RSAES-OAEP), so all decode to modulus + exponent, not raw bytes.
|
|
128
128
|
var RSA_KEY_ALGS = { rsaEncryption: 1, rsassaPss: 1, rsaesOaep: 1 };
|
|
129
129
|
function _keyBlock(spki, pad) {
|
|
130
130
|
var algName = _algName(spki.algorithm);
|
|
@@ -200,8 +200,8 @@ function _ipString(buf) {
|
|
|
200
200
|
// RFC 4514-equivalent escaping profile, and escaping a legitimate comma in a URI
|
|
201
201
|
// would misrepresent it).
|
|
202
202
|
// The DN of a decoded directoryName GeneralName value. Shared so every renderer that meets this
|
|
203
|
-
// form
|
|
204
|
-
// decoded shapes
|
|
203
|
+
// form (the SAN/AKI GeneralName path and the AIA/SIA accessLocation path, which carry different
|
|
204
|
+
// decoded shapes) prints the same DN, with neither falling back to a bare tag number.
|
|
205
205
|
function _gnDn(value) {
|
|
206
206
|
return (value && Array.isArray(value.rdns)) ? _dnString(value) : ((value && value.dn) || "");
|
|
207
207
|
}
|
|
@@ -219,7 +219,7 @@ function _gn(g) {
|
|
|
219
219
|
}
|
|
220
220
|
|
|
221
221
|
// Format a GeneralName still in its raw DER TLV (a CRL distribution point leaves
|
|
222
|
-
// its fullName entries undecoded)
|
|
222
|
+
// its fullName entries undecoded): decode the context tag and render its content.
|
|
223
223
|
function _gnRaw(buf) {
|
|
224
224
|
if (!Buffer.isBuffer(buf)) return "";
|
|
225
225
|
try {
|
|
@@ -233,26 +233,26 @@ function _gnRaw(buf) {
|
|
|
233
233
|
} catch (_e) { return _hexColon(buf, {}); }
|
|
234
234
|
}
|
|
235
235
|
|
|
236
|
-
// Coverage residual
|
|
236
|
+
// Coverage residual: the GeneralName render fallbacks are unreachable because the strict
|
|
237
237
|
// generalName decoder and each caller already narrow the shape (the _gnRaw catch above is
|
|
238
|
-
// separately documented)
|
|
239
|
-
// * _ipString `if (!Buffer.isBuffer(buf)) return ""
|
|
238
|
+
// separately documented).
|
|
239
|
+
// * _ipString `if (!Buffer.isBuffer(buf)) return "";`. Every caller passes a Buffer (the
|
|
240
240
|
// recursion slices a Buffer, _gn guards Buffer.isBuffer(g.value), _gnRaw passes
|
|
241
241
|
// asn1.decode(...).content).
|
|
242
|
-
// * _ipString trailing `return _hexColon(buf, {})
|
|
242
|
+
// * _ipString trailing `return _hexColon(buf, {});`. generalName enforces iPAddress to
|
|
243
243
|
// 4/16 octets (8/32 for a name-constraints subtree base), so length is only ever 4/8/16/32.
|
|
244
|
-
// * _gn `if (!g || typeof g !== "object") return ""
|
|
244
|
+
// * _gn `if (!g || typeof g !== "object") return "";`. Every _gn call maps a decoder-produced
|
|
245
245
|
// GeneralName object; the decoders never emit a null element.
|
|
246
|
-
// * _gn directoryName `: ((g.value && g.value.dn) || "")
|
|
246
|
+
// * _gn directoryName `: ((g.value && g.value.dn) || "")`. A decoded directoryName [4] always
|
|
247
247
|
// carries a Name with an rdns array, so the rdns arm is always taken.
|
|
248
|
-
// * _gn otherName `: "<unsupported>"
|
|
249
|
-
// * _gn `|| ("tag" + t)
|
|
248
|
+
// * _gn otherName `: "<unsupported>"`. A decoded GeneralName always carries its raw bytes TLV.
|
|
249
|
+
// * _gn `|| ("tag" + t)`. GeneralName CHOICE tags are 0..8, all mapped in NAMES.GENERAL_NAME;
|
|
250
250
|
// generalName rejects a tag outside 0..8.
|
|
251
|
-
// * _gn `? _hexColon(g.value, {})
|
|
251
|
+
// * _gn `? _hexColon(g.value, {})`. iPAddress [7] is the only choice whose decoded value is a
|
|
252
252
|
// Buffer, handled at the t === 7 branch before this ternary; other tags' value is string or null.
|
|
253
|
-
// * _gn trailing `: ""
|
|
253
|
+
// * _gn trailing `: ""`. A decoded GeneralName always carries a bytes Buffer, so the
|
|
254
254
|
// Buffer.isBuffer(g.bytes) arm is always taken.
|
|
255
|
-
// * _gnRaw `if (!Buffer.isBuffer(buf)) return ""
|
|
255
|
+
// * _gnRaw `if (!Buffer.isBuffer(buf)) return "";`. The sole caller (the CRL-DP fullName loop)
|
|
256
256
|
// guards Buffer.isBuffer(nm) before calling _gnRaw.
|
|
257
257
|
|
|
258
258
|
// Shared value renderers reused by more than one extension key.
|
|
@@ -281,7 +281,7 @@ function _renderCrlDp(decoded, inner) {
|
|
|
281
281
|
if (rf.length) { dpLines.push(inner + "Reasons: " + rf.join(", ")); wrote = true; }
|
|
282
282
|
}
|
|
283
283
|
// A DistributionPoint may carry only cRLIssuer (an indirect CRL, no
|
|
284
|
-
// distributionPoint)
|
|
284
|
+
// distributionPoint): render the issuer GeneralNames, never drop them.
|
|
285
285
|
if (dp.cRLIssuer && Array.isArray(dp.cRLIssuer.names)) {
|
|
286
286
|
dpLines.push(inner + "CRL Issuer:");
|
|
287
287
|
dp.cRLIssuer.names.forEach(function (g) { dpLines.push(inner + " " + _gn(g)); });
|
|
@@ -296,17 +296,17 @@ function _renderCrlDp(decoded, inner) {
|
|
|
296
296
|
// strict decoders already narrow the shape:
|
|
297
297
|
// * _renderAltName `(decoded.names || [])` -- the subjectAltName/issuerAltName decoder always
|
|
298
298
|
// yields a names array.
|
|
299
|
-
// * _renderCrlDp `(decoded || [])
|
|
299
|
+
// * _renderCrlDp `(decoded || [])`. The cRLDistributionPoints/freshestCRL decoder always
|
|
300
300
|
// yields an array.
|
|
301
|
-
// * _renderCrlDp `: _gn(nm)
|
|
301
|
+
// * _renderCrlDp `: _gn(nm)`. distributionPointName surfaces fullName entries as raw
|
|
302
302
|
// GeneralName Buffers, so Buffer.isBuffer(nm) is always true.
|
|
303
|
-
// * _renderCrlDp `if (!wrote) ... "(distribution point)"
|
|
303
|
+
// * _renderCrlDp `if (!wrote) ... "(distribution point)"`. crlDistributionPoints throws unless
|
|
304
304
|
// a distributionPoint (always fullName/rdn) or cRLIssuer (sets wrote) is present, so !wrote
|
|
305
305
|
// never holds.
|
|
306
306
|
|
|
307
307
|
// Declarative extension-value renderer registry: extension name -> (decoded, inner) ->
|
|
308
308
|
// text. Data-driven dispatch (the schema family's "registry, not switch" shape) so a
|
|
309
|
-
// new extension is a row
|
|
309
|
+
// new extension is a row, never another hand-coded branch, and the set an
|
|
310
310
|
// operator sees rendered is visible in one place. An extension with no row here
|
|
311
311
|
// hex-dumps its raw value (best-effort); each row's output is pinned by an
|
|
312
312
|
// inspect.test.js conformance vector.
|
|
@@ -366,7 +366,7 @@ var EXT_RENDERERS = {
|
|
|
366
366
|
lines.push(inner + "Policy: " + p.policyIdentifier);
|
|
367
367
|
if (!Buffer.isBuffer(p.qualifiersBytes)) return;
|
|
368
368
|
// Render each PolicyQualifierInfo { policyQualifierId, qualifier }: a printable
|
|
369
|
-
// qualifier (a CPS URI is an IA5String) shows as text, else a hex dump
|
|
369
|
+
// qualifier (a CPS URI is an IA5String) shows as text, else a hex dump, and is never
|
|
370
370
|
// dropped (which would make a qualified policy look unqualified).
|
|
371
371
|
try {
|
|
372
372
|
(asn1.decode(p.qualifiersBytes).children || []).forEach(function (pqi) {
|
|
@@ -379,9 +379,9 @@ var EXT_RENDERERS = {
|
|
|
379
379
|
// qualifier exists to display. Render its DisplayText members through the shared pkix
|
|
380
380
|
// reader (the same one pki.lint measures), so both agree on what the notice says.
|
|
381
381
|
if (qid === OID_UNOTICE) {
|
|
382
|
-
// Render only when
|
|
382
|
+
// Render only when every member decoded under its declared string type. A null text means
|
|
383
383
|
// the value did not, and showing the members that happened to decode would present a
|
|
384
|
-
// partial notice as a complete one
|
|
384
|
+
// partial notice as a complete one, so the whole qualifier falls through to the hex
|
|
385
385
|
// dump, where the operator sees the bytes the certificate actually holds.
|
|
386
386
|
// A null noticeNumbers means the reference did not fully decode, and is refused for the
|
|
387
387
|
// same reason as a null text: a partially decoded notice must not be shown as a whole one.
|
|
@@ -414,15 +414,15 @@ var EXT_RENDERERS = {
|
|
|
414
414
|
authorityInfoAccess: function (decoded, inner) {
|
|
415
415
|
// AccessDescription list: <accessMethod> - <accessLocation>. The method resolves to its name (caIssuers /
|
|
416
416
|
// ocsp); the accessLocation is a GeneralName (a URI in the common case). An unregistered method / an
|
|
417
|
-
// uncommon accessLocation tag falls back to the raw OID / bracketed tag
|
|
417
|
+
// uncommon accessLocation tag falls back to the raw OID / bracketed tag, and the entry is never dropped.
|
|
418
418
|
var LABEL = { caIssuers: "CA Issuers", ocsp: "OCSP" };
|
|
419
419
|
return (decoded || []).map(function (ad) {
|
|
420
420
|
var m = null;
|
|
421
421
|
try { m = oid.name(ad.accessMethod); } catch (_e) { /* allow:swallow-unverified display best-effort: an unregistered accessMethod OID falls back to the raw dotted OID below (inspection is best-effort, never a verdict) */ }
|
|
422
422
|
var loc = ad.accessLocation || {}, lv;
|
|
423
423
|
// The string choices (URI/DNS/email) are IA5String values already control-byte-rejected at decode by the
|
|
424
|
-
// CVE-2009-2408 guard, so they are safe to emit directly. The iPAddress choice is a
|
|
425
|
-
// render it through _ipString (never raw)
|
|
424
|
+
// CVE-2009-2408 guard, so they are safe to emit directly. The iPAddress choice is a raw 4/16-byte Buffer,
|
|
425
|
+
// so render it through _ipString (never raw) and a byte such as 0x0a cannot inject a line and spoof a field.
|
|
426
426
|
if (loc.tag === 6) lv = "URI:" + loc.value;
|
|
427
427
|
else if (loc.tag === 2) lv = "DNS:" + loc.value;
|
|
428
428
|
else if (loc.tag === 1) lv = "email:" + loc.value;
|
|
@@ -477,7 +477,7 @@ var EXT_RENDERERS = {
|
|
|
477
477
|
},
|
|
478
478
|
authorityKeyIdentifier: function (decoded, inner) {
|
|
479
479
|
// Any of the three fields may be present; the issuer+serial form carries no
|
|
480
|
-
// keyIdentifier, so render whichever the decoder populated
|
|
480
|
+
// keyIdentifier, so render whichever the decoder populated, never claiming
|
|
481
481
|
// "keyid:(none)" and dropping the certificate's real authority identifier.
|
|
482
482
|
var akiLines = [];
|
|
483
483
|
if (Buffer.isBuffer(decoded.keyIdentifier)) akiLines.push(inner + "keyid:" + _hexColon(decoded.keyIdentifier, { upper: true }));
|
|
@@ -499,29 +499,29 @@ EXT_RENDERERS.msApplicationPolicies = EXT_RENDERERS.certificatePolicies;
|
|
|
499
499
|
// Coverage residual -- the EXT_RENDERERS entry fallbacks are unreachable because each shared
|
|
500
500
|
// decoder (and asn1.read.oid) already narrows the shape before the renderer runs:
|
|
501
501
|
// * extKeyUsage `catch (_e) { /* unregistered EKU OID */ }` and certificatePolicies
|
|
502
|
-
// `catch (_e) { /* unregistered qualifier */ }
|
|
502
|
+
// `catch (_e) { /* unregistered qualifier */ }`. oid.name returns undefined (never throws)
|
|
503
503
|
// for a well-formed unregistered OID; it throws only on a non-dotted argument, and both OIDs
|
|
504
504
|
// come from asn1.read.oid (always a valid dotted OID).
|
|
505
|
-
// * certificatePolicies `(asn1.decode(p.qualifiersBytes).children || [])
|
|
505
|
+
// * certificatePolicies `(asn1.decode(p.qualifiersBytes).children || [])`. asn1.decode of the
|
|
506
506
|
// assertPolicyQualifiers-validated qualifiers SEQUENCE always yields a children array.
|
|
507
|
-
// * certificatePolicies `: Buffer.alloc(0)
|
|
507
|
+
// * certificatePolicies `: Buffer.alloc(0)`. assertPolicyQualifiers requires every
|
|
508
508
|
// PolicyQualifierInfo to be a 2-child SEQUENCE, so pqi.children[1] is always a present node
|
|
509
509
|
// carrying a bytes Buffer.
|
|
510
|
-
// * certificatePolicies outer `catch (_e) { ... _hexColon(p.qualifiersBytes, {}) ... }
|
|
510
|
+
// * certificatePolicies outer `catch (_e) { ... _hexColon(p.qualifiersBytes, {}) ... }`.
|
|
511
511
|
// certificatePolicies already validated qualifiersBytes as a SEQUENCE of 2-child PQIs each
|
|
512
512
|
// leading with a valid OID, so the re-decode + asn1.read.oid cannot throw.
|
|
513
|
-
// * policyConstraints `: inner + "(empty)"
|
|
513
|
+
// * policyConstraints `: inner + "(empty)"`. policyConstraints rejects an empty SEQUENCE
|
|
514
514
|
// (>= 1 context field), so requireExplicitPolicy or inhibitPolicyMapping is non-null;
|
|
515
515
|
// pc.length is never 0.
|
|
516
|
-
// * SCT `(decoded.scts || [])` / `(decoded.unknownScts || [])
|
|
516
|
+
// * SCT `(decoded.scts || [])` / `(decoded.unknownScts || [])`. ct.parseSctList always returns
|
|
517
517
|
// both arrays.
|
|
518
|
-
// * SCT `: 0
|
|
518
|
+
// * SCT `: 0`. Every scts entry is a decoded v1 SCT with numeric version 0 (unknown-version
|
|
519
519
|
// SCTs go to unknownScts and are not iterated here).
|
|
520
|
-
// * SCT `: inner + "(empty SCT list)"
|
|
520
|
+
// * SCT `: inner + "(empty SCT list)"`. ct.parseSctList rejects an empty list and routes every
|
|
521
521
|
// SerializedSCT into scts/unknownScts, so at least one line is always emitted.
|
|
522
|
-
// * subjectKeyIdentifier `: (decoded.bytes || Buffer.alloc(0))
|
|
522
|
+
// * subjectKeyIdentifier `: (decoded.bytes || Buffer.alloc(0))`. The subjectKeyIdentifier
|
|
523
523
|
// decoder returns the KeyIdentifier as a Buffer, so Buffer.isBuffer(decoded) is always true.
|
|
524
|
-
// * authorityKeyIdentifier `: BigInt(decoded.authorityCertSerialNumber)
|
|
524
|
+
// * authorityKeyIdentifier `: BigInt(decoded.authorityCertSerialNumber)`. The AKI decoder reads
|
|
525
525
|
// authorityCertSerialNumber via asn1.read.integerImplicit (a bigint), so typeof === "bigint"
|
|
526
526
|
// is always true.
|
|
527
527
|
|
|
@@ -535,7 +535,7 @@ function _renderExtValue(ext, decoded, inner) {
|
|
|
535
535
|
// string; a DER-wrapped character string is decoded and shown; otherwise a hex
|
|
536
536
|
// dump. Never throws.
|
|
537
537
|
var _STRING_TAGS = { 12: 1, 19: 1, 22: 1, 20: 1, 26: 1, 27: 1, 30: 1 }; // UTF8/Printable/IA5/Teletex/Visible/General/BMP
|
|
538
|
-
// A value renders as text only when
|
|
538
|
+
// A value renders as text only when every byte is a printable, non-control ASCII
|
|
539
539
|
// character. A control byte (a bare CR / LF / TAB, etc.) is rejected here so a
|
|
540
540
|
// hostile private-extension value cannot forge or overwrite report lines in a
|
|
541
541
|
// terminal or log -- such a value falls through to the hex dump instead.
|
|
@@ -573,7 +573,7 @@ function _extension(ext, pad) {
|
|
|
573
573
|
|
|
574
574
|
// A genuine pki.schema.x509.parse result carries this whole shape. The fast path
|
|
575
575
|
// accepts a pre-parsed certificate to skip re-parsing, but a bare or partial object
|
|
576
|
-
// with only a tbsBytes property is
|
|
576
|
+
// with only a tbsBytes property is not a certificate: without this check the renderer
|
|
577
577
|
// would dereference a missing field (c.validity.notBefore, ...) and throw a raw
|
|
578
578
|
// TypeError instead of the documented typed inspect/bad-input (the API's error contract).
|
|
579
579
|
function _looksParsed(o) {
|
|
@@ -615,7 +615,7 @@ function _parse(input) {
|
|
|
615
615
|
* is a PEM string, a DER Buffer, or a `pki.schema.x509.parse` result. A value that
|
|
616
616
|
* is none of those throws `inspect/bad-input`; a malformed certificate throws
|
|
617
617
|
* `inspect/bad-certificate`; but a malformed individual extension is rendered as a
|
|
618
|
-
* hex dump
|
|
618
|
+
* hex dump and does not fail the whole report. Pure, with no OpenSSL dependency.
|
|
619
619
|
*
|
|
620
620
|
* @example
|
|
621
621
|
* var pair = await pki.key.generate("Ed25519");
|
|
@@ -766,7 +766,7 @@ function _crlExtension(ext, pad) {
|
|
|
766
766
|
* revocation date, entry extensions), and the signature. `input` is a PEM string, a
|
|
767
767
|
* DER Buffer, or a `pki.schema.crl.parse` result; a non-CRL throws
|
|
768
768
|
* `inspect/bad-crl`, a wrong-type input `inspect/bad-input`. A malformed individual
|
|
769
|
-
* extension renders as hex
|
|
769
|
+
* extension renders as hex and does not fail the report.
|
|
770
770
|
*
|
|
771
771
|
* @example
|
|
772
772
|
* var pair = await pki.key.generate("Ed25519");
|
package/lib/ip-utils.js
CHANGED
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
// sec. 2.2 (8 groups of 1-4 hex, one "::" run compressing a contiguous zero run) plus the
|
|
11
11
|
// RFC 4291 sec. 2.5.5.2 / RFC 5952 sec. 5 IPv4-mapped + dual-stack "::ffff:1.2.3.4" tail.
|
|
12
12
|
// This mirrors the vetted validation in the sibling blamejs framework (lib/ip-utils.js);
|
|
13
|
-
// this toolkit keeps its own copy
|
|
13
|
+
// this toolkit keeps its own copy, since Hard rule #1 rules out a runtime dependency.
|
|
14
14
|
|
|
15
15
|
// Strict RFC 791 dotted-quad: four 0-255 octets. Anchored + per-octet repeat-capped (no
|
|
16
16
|
// ReDoS on unbounded input).
|
|
@@ -56,7 +56,7 @@ function expandIpv6Hex(ip) {
|
|
|
56
56
|
// Is `s` a syntactically valid IPv4 or IPv6 textual literal?
|
|
57
57
|
function isIpLiteral(s) { return isIPv4(s) || expandIpv6Hex(s) !== null; }
|
|
58
58
|
|
|
59
|
-
// Pack an IPv4/IPv6 textual literal to its network-order octets
|
|
59
|
+
// Pack an IPv4/IPv6 textual literal to its network-order octets (4 for IPv4, 16 for IPv6),
|
|
60
60
|
// or null when `s` is not a valid literal. The binary inverse of the textual forms isIPv4 /
|
|
61
61
|
// expandIpv6Hex validate, so a SAN/GeneralName iPAddress (RFC 5280 sec. 4.2.1.6, always a bare
|
|
62
62
|
// host address of 4 or 16 octets) can be given as a string instead of a pre-packed Buffer.
|
package/lib/jose.js
CHANGED
|
@@ -11,12 +11,12 @@
|
|
|
11
11
|
* @intro
|
|
12
12
|
* The JOSE message envelope (RFC 7515 JWS, RFC 7518 JWA, RFC 7638 JWK
|
|
13
13
|
* thumbprint), profiled for RFC 8555 ACME but usable on its own. A JWS here
|
|
14
|
-
* is the Flattened JSON Serialization only
|
|
15
|
-
* signature }
|
|
14
|
+
* is the Flattened JSON Serialization only, `{ protected, payload,
|
|
15
|
+
* signature }`, with the multi-signature `signatures` member, the
|
|
16
16
|
* unprotected `header` member, and the RFC 7797 unencoded-payload `b64`
|
|
17
17
|
* option all structurally forbidden. Every base64url field is decoded by a
|
|
18
|
-
*
|
|
19
|
-
* whitespace, and non-canonical trailing bits
|
|
18
|
+
* strict codec (Node's `Buffer.from(s, "base64url")` accepts padding,
|
|
19
|
+
* whitespace, and non-canonical trailing bits, all of which are rejected
|
|
20
20
|
* here), and every JSON document is read by a bounded reader that rejects a
|
|
21
21
|
* duplicate member at any nesting depth (the parser-differential smuggling
|
|
22
22
|
* class `JSON.parse` silently allows).
|
|
@@ -24,10 +24,10 @@
|
|
|
24
24
|
* Algorithms resolve through an `alg`-keyed registry (ES256/384/512,
|
|
25
25
|
* RS256/384/512, PS256/384/512, EdDSA, and the RFC 9964 ML-DSA-44/65/87 PQC
|
|
26
26
|
* rows), never a switch: the registry binds `alg` to a key type and pins the
|
|
27
|
-
* exact signature byte length
|
|
27
|
+
* exact signature byte length before any crypto call, so `alg:"none"`, a MAC
|
|
28
28
|
* algorithm on the outer profile, an ES256/RSA-key confusion, and a DER-vs-raw
|
|
29
|
-
* ECDSA signature all fail closed. `sign` and `verify` are driven by
|
|
30
|
-
* declarative profile table
|
|
29
|
+
* ECDSA signature all fail closed. `sign` and `verify` are each driven by one
|
|
30
|
+
* declarative profile table, so the same data drives both directions.
|
|
31
31
|
*
|
|
32
32
|
* @card
|
|
33
33
|
* RFC 7515 Flattened JWS sign/verify + RFC 7638 JWK thumbprints, ACME-profiled:
|
|
@@ -306,10 +306,10 @@ var PRIVATE_JWK_MEMBERS = ["d", "p", "q", "dp", "dq", "qi", "k", "priv"];
|
|
|
306
306
|
* @spec RFC 7517, RFC 7518
|
|
307
307
|
* @related pki.jose.sign, pki.jose.thumbprint
|
|
308
308
|
*
|
|
309
|
-
* Assert that a JWK is
|
|
309
|
+
* Assert that a JWK is public-only before it is published (embedded in a JWS
|
|
310
310
|
* protected header or an ACME External Account Binding payload). A JWK carrying any
|
|
311
311
|
* private member (`d`, the RSA CRT parameters `p`/`q`/`dp`/`dq`/`qi`, the symmetric
|
|
312
|
-
* `k`, or the AKP `priv`) throws `jose/private-key-material
|
|
312
|
+
* `k`, or the AKP `priv`) throws `jose/private-key-material`, so an accidentally
|
|
313
313
|
* exported private JWK is never sent to a server. Returns the JWK.
|
|
314
314
|
*
|
|
315
315
|
* @example
|
|
@@ -334,13 +334,13 @@ function assertPublicJwk(jwk) {
|
|
|
334
334
|
* @related pki.jose.sign, pki.jose.parseJson
|
|
335
335
|
*
|
|
336
336
|
* Verify a Flattened JSON JWS against a profile (`opts.profile`, default
|
|
337
|
-
* `"acme-outer"`). Structural rules fail closed
|
|
337
|
+
* `"acme-outer"`). Structural rules fail closed before any crypto: the
|
|
338
338
|
* `signatures`/`header` members and a detached payload are rejected, the
|
|
339
339
|
* protected header is validated against the profile (alg registry, nonce, url,
|
|
340
340
|
* exactly-one-of jwk/kid, crit), and the signature byte length is pinned per alg.
|
|
341
341
|
*
|
|
342
342
|
* `opts.key` names the key the message must be signed under, and it governs: where
|
|
343
|
-
* the profile also permits an embedded header `jwk`, the two must be the
|
|
343
|
+
* the profile also permits an embedded header `jwk`, the two must be the same key
|
|
344
344
|
* or the message is refused with `jose/key-mismatch`. They are compared as RFC 7638
|
|
345
345
|
* thumbprints, so member order and members outside the key itself cannot make equal
|
|
346
346
|
* keys look different. Without `opts.key` the embedded `jwk` is used where the
|
|
@@ -545,7 +545,7 @@ var THUMBPRINT_MEMBERS = {
|
|
|
545
545
|
* @spec RFC 7638, RFC 8037, RFC 9964
|
|
546
546
|
* @related pki.jose.verify
|
|
547
547
|
*
|
|
548
|
-
* The RFC 7638 JWK SHA-256 thumbprint as base64url: the canonical JSON of
|
|
548
|
+
* The RFC 7638 JWK SHA-256 thumbprint as base64url: the canonical JSON of just
|
|
549
549
|
* the key type's required members, lexicographically ordered, no whitespace,
|
|
550
550
|
* hashed. Optional members (`alg`, `use`, `kid`) are excluded, so the same key
|
|
551
551
|
* always yields the same thumbprint (the ACME key-authorization anchor).
|
|
@@ -593,7 +593,7 @@ async function thumbprint(jwk) {
|
|
|
593
593
|
* listing it beside `RS256` is how the HMAC key-confusion class starts. `none`
|
|
594
594
|
* does not exist here at all.
|
|
595
595
|
*
|
|
596
|
-
* Each call returns a fresh array of fresh rows
|
|
596
|
+
* Each call returns a fresh array of fresh rows. The registry that drives
|
|
597
597
|
* verification is never handed out, so nothing a caller does to the result can
|
|
598
598
|
* widen what a signature check accepts.
|
|
599
599
|
*
|
package/lib/key.js
CHANGED
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
* @intro The key-material lifecycle: export / import a private key as PKCS#8 (`OneAsymmetricKey`,
|
|
9
9
|
* RFC 5958) or a public key as SPKI (RFC 5280 sec. 4.1.2.7), encrypt / decrypt a private key under
|
|
10
10
|
* RFC 8018 PBES2 (`EncryptedPrivateKeyInfo`, PBKDF2 + AES-CBC-Pad), and `generate` /
|
|
11
|
-
* `publicFromPrivate` over every algorithm the WebCrypto engine drives
|
|
11
|
+
* `publicFromPrivate` over every algorithm the WebCrypto engine drives: RSA, EC, Ed25519/Ed448,
|
|
12
12
|
* X25519/X448, and the FIPS post-quantum ML-DSA / ML-KEM. Unencrypted export / import DELEGATES to the
|
|
13
13
|
* WebCrypto `exportKey` / `importKey` PKCS#8 / SPKI encoders (which already emit each algorithm's
|
|
14
14
|
* `AlgorithmIdentifier.parameters` correctly -- RSA NULL, EC namedCurve, Ed/X ABSENT), so the wrapper
|
|
@@ -18,15 +18,15 @@
|
|
|
18
18
|
* @card Export / import PKCS#8 and SPKI keys and encrypt a private key under RFC 8018 PBES2.
|
|
19
19
|
*/
|
|
20
20
|
//
|
|
21
|
-
// PBES2 is composed from lib/pbes2.js (the
|
|
21
|
+
// PBES2 is composed from lib/pbes2.js (the single PBKDF2 + AES-CBC home shared with pki.cms), bound to the key
|
|
22
22
|
// namespace through the `_err` error FACTORY + the "key" domain prefix so every reject keeps a key/* code.
|
|
23
|
-
// A MAC-less PBES2-CBC decrypt is not a padding oracle (RFC 8018 sec. 8): every post-derivation failure
|
|
24
|
-
// a bad PKCS#7 pad
|
|
23
|
+
// A MAC-less PBES2-CBC decrypt is not a padding oracle (RFC 8018 sec. 8): every post-derivation failure
|
|
24
|
+
// (a bad PKCS#7 pad, or a valid pad that does not re-parse as a PrivateKeyInfo) collapses into one uniform
|
|
25
25
|
// key/decrypt-failed; the structural pre-derivation faults (a non-PBKDF2 KDF, a non-AES-CBC scheme, an
|
|
26
26
|
// over-cap salt / iteration count, a wrong-length IV, a malformed parameter SEQUENCE) stay distinct and are
|
|
27
|
-
// thrown
|
|
28
|
-
// and publicFromPrivate
|
|
29
|
-
// AlgorithmIdentifier
|
|
27
|
+
// thrown before any key derivation, so they leak nothing password-dependent. Unencrypted export / import
|
|
28
|
+
// and publicFromPrivate delegate to the WebCrypto / node key engines and re-serialize no
|
|
29
|
+
// AlgorithmIdentifier. A re-encode is exactly where a params-absent-vs-NULL divergence sneaks back in.
|
|
30
30
|
|
|
31
31
|
var nodeCrypto = require("crypto");
|
|
32
32
|
var asn1 = require("./asn1-der");
|
|
@@ -43,17 +43,17 @@ var subtle = webcrypto.webcrypto.subtle;
|
|
|
43
43
|
var KeyError = frameworkError.KeyError;
|
|
44
44
|
var PemError = frameworkError.PemError;
|
|
45
45
|
function O(n) { return oid.byName(n); }
|
|
46
|
-
// The guard / pbes2 error convention: a (code, msg, cause)
|
|
47
|
-
// the guards invoke it as E(code, msg) with no `new` (a class there crashes on the error path).
|
|
46
|
+
// The guard / pbes2 error convention: a (code, msg, cause) factory, never the KeyError class, because pbes2
|
|
47
|
+
// and the guards invoke it as E(code, msg) with no `new` (a class there crashes on the error path).
|
|
48
48
|
function _err(code, msg, cause) { return new KeyError(code, msg, cause); }
|
|
49
49
|
|
|
50
50
|
// The operator-facing node cipher name <-> the AES-CBC registry name the PBES2 home speaks.
|
|
51
51
|
var CIPHER_NAME = { "aes-128-cbc": "aes128-CBC", "aes-192-cbc": "aes192-CBC", "aes-256-cbc": "aes256-CBC" };
|
|
52
52
|
|
|
53
|
-
// import inference: the algorithm OIDs that name exactly
|
|
53
|
+
// import inference: the algorithm OIDs that name exactly one WebCrypto algorithm and need no parameters --
|
|
54
54
|
// the Edwards/Montgomery curves and the FIPS post-quantum ML-DSA, ML-KEM, and SLH-DSA (all signing-only or
|
|
55
|
-
// agreement-only, so unambiguous). RSA (sign vs OAEP) and EC (ECDSA vs ECDH) are deliberately
|
|
56
|
-
//
|
|
55
|
+
// agreement-only, so unambiguous). RSA (sign vs OAEP) and EC (ECDSA vs ECDH) are deliberately absent. Each
|
|
56
|
+
// is ambiguous, so import fails closed there and asks for opts.algorithm.
|
|
57
57
|
var INFER_ALG = {};
|
|
58
58
|
["Ed25519", "Ed448", "X25519", "X448"].forEach(function (n) { INFER_ALG[O(n)] = { name: n }; });
|
|
59
59
|
[["id-ml-dsa-44", "ML-DSA-44"], ["id-ml-dsa-65", "ML-DSA-65"], ["id-ml-dsa-87", "ML-DSA-87"],
|
|
@@ -153,9 +153,9 @@ async function encrypt(privateKey, password, opts) {
|
|
|
153
153
|
* PBKDF2 key-derivation function and an AES-CBC encryption scheme is accepted; PBES1, PBMAC1, scrypt, and any
|
|
154
154
|
* other `encryptionAlgorithm` fail closed with `key/unsupported-algorithm`.
|
|
155
155
|
*
|
|
156
|
-
* The salt and iteration count are attacker-controlled work: both caps are enforced
|
|
156
|
+
* The salt and iteration count are attacker-controlled work: both caps are enforced before any derivation
|
|
157
157
|
* (`opts.maxIterations` may lower the cap, never raise it), and a wrong-length IV or malformed parameter set
|
|
158
|
-
* is a typed `key/bad-algorithm-parameters`. A MAC-less PBES2-CBC decrypt is not a padding oracle
|
|
158
|
+
* is a typed `key/bad-algorithm-parameters`. A MAC-less PBES2-CBC decrypt is not a padding oracle: a wrong
|
|
159
159
|
* password and a valid-pad-but-not-a-PrivateKeyInfo both surface the single uniform `key/decrypt-failed`.
|
|
160
160
|
*
|
|
161
161
|
* @opts
|
|
@@ -252,7 +252,7 @@ async function export_(key, opts) {
|
|
|
252
252
|
* KEY` -- auto-detecting the structure -- into a `CryptoKey`. The WebCrypto algorithm is inferred from the
|
|
253
253
|
* key's OID for the algorithms that name exactly one (Ed25519 / Ed448 / X25519 / X448 / ML-DSA / ML-KEM /
|
|
254
254
|
* SLH-DSA); RSA and EC are ambiguous between signing and key agreement, so `opts.algorithm` must be supplied
|
|
255
|
-
* for them (import fails closed
|
|
255
|
+
* for them (without it the import fails closed). Default key usages follow the algorithm and key type.
|
|
256
256
|
*
|
|
257
257
|
* @opts
|
|
258
258
|
* - `algorithm` (string | object) -- the WebCrypto algorithm (required for RSA / EC; overrides inference).
|
|
@@ -324,7 +324,7 @@ async function generate(algorithm, opts) {
|
|
|
324
324
|
*
|
|
325
325
|
* Derive the SubjectPublicKeyInfo (SPKI) public key from a PKCS#8 private key (DER `Buffer`, `PRIVATE KEY`
|
|
326
326
|
* PEM, or extractable private `CryptoKey`). The derivation is delegated to the node key engine, which infers
|
|
327
|
-
* the algorithm from the key structure, so no `AlgorithmIdentifier` is re-encoded
|
|
327
|
+
* the algorithm from the key structure, so no `AlgorithmIdentifier` is re-encoded: Ed25519 stays
|
|
328
328
|
* parameters-absent, RSA keeps its NULL, EC keeps its namedCurve.
|
|
329
329
|
*
|
|
330
330
|
* @opts
|