@blamejs/pki 0.5.30 → 0.5.32
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 +28 -0
- package/README.md +2 -2
- package/lib/acme.js +347 -90
- package/lib/asn1-der.js +97 -33
- package/lib/attrcert-sign.js +1 -1
- package/lib/cmc-build.js +19 -8
- package/lib/cmc-verify.js +4 -4
- package/lib/cmp-build.js +36 -6
- package/lib/cmp-session.js +5 -5
- package/lib/cmp-verify.js +86 -16
- package/lib/cms-compress.js +2 -2
- package/lib/cms-decrypt.js +3 -3
- package/lib/cms-encrypt.js +1 -1
- package/lib/cms-sign.js +7 -7
- package/lib/cms-verify.js +9 -4
- package/lib/crl-sign.js +5 -5
- package/lib/crmf-sign.js +2 -1
- package/lib/csr-sign.js +1 -4
- package/lib/ct.js +8 -4
- package/lib/est.js +234 -34
- package/lib/framework-error.js +20 -6
- package/lib/guard-bytes.js +24 -3
- package/lib/http-digest.js +69 -16
- package/lib/http-retry-after.js +68 -24
- package/lib/http-transport.js +60 -6
- package/lib/inspect.js +13 -2
- package/lib/ip-utils.js +102 -25
- package/lib/key.js +2 -2
- package/lib/lint.js +1 -1
- package/lib/mime.js +27 -4
- package/lib/ocsp.js +7 -7
- package/lib/oid.js +16 -0
- package/lib/path-validate.js +76 -18
- package/lib/pbes2.js +2 -2
- package/lib/pkcs12-build.js +2 -2
- package/lib/pki-build.js +320 -21
- package/lib/rfc3339.js +75 -17
- package/lib/schema-c509.js +36 -9
- package/lib/schema-cmc.js +3 -3
- package/lib/schema-pkix.js +174 -25
- package/lib/schema-tsp.js +10 -1
- package/lib/sign-scheme.js +27 -7
- package/lib/sigstore.js +52 -9
- package/lib/smime.js +86 -6
- package/lib/trust.js +114 -14
- package/lib/tsp-sign.js +39 -27
- package/lib/validator-tpm.js +2 -2
- package/lib/webauthn-mds.js +24 -6
- package/lib/webauthn.js +3 -3
- package/lib/webcrypto.js +1 -1
- package/lib/x509-sign.js +9 -5
- package/package.json +1 -1
- package/sbom.cdx.json +6 -6
package/lib/mime.js
CHANGED
|
@@ -36,15 +36,38 @@ function _splitPoint(bytes) {
|
|
|
36
36
|
return null;
|
|
37
37
|
}
|
|
38
38
|
|
|
39
|
+
// Line-ending + whitespace helpers, decided character by character (no regex): _toLf collapses any
|
|
40
|
+
// CRLF / bare CR / bare LF to a single LF; _toCrlf then expands every LF to CRLF; _stripLeadingHtab
|
|
41
|
+
// drops a leading run of SP/HTAB. They match the /\r\n/g, /\r/g, /\n/g and /^[ \t]+/ replacements.
|
|
42
|
+
function _toLf(s) {
|
|
43
|
+
var out = "";
|
|
44
|
+
for (var i = 0; i < s.length; i++) {
|
|
45
|
+
var c = s.charAt(i);
|
|
46
|
+
if (c === "\r") { if (s.charAt(i + 1) === "\n") continue; out += "\n"; } // CRLF -> its LF next; bare CR -> LF
|
|
47
|
+
else out += c;
|
|
48
|
+
}
|
|
49
|
+
return out;
|
|
50
|
+
}
|
|
51
|
+
function _toCrlf(s) {
|
|
52
|
+
var lf = _toLf(s), out = "";
|
|
53
|
+
for (var i = 0; i < lf.length; i++) { var c = lf.charAt(i); out += (c === "\n") ? "\r\n" : c; }
|
|
54
|
+
return out;
|
|
55
|
+
}
|
|
56
|
+
function _stripLeadingHtab(s) {
|
|
57
|
+
var i = 0;
|
|
58
|
+
while (i < s.length && (s.charAt(i) === " " || s.charAt(i) === "\t")) i += 1;
|
|
59
|
+
return s.slice(i);
|
|
60
|
+
}
|
|
61
|
+
|
|
39
62
|
// Split a header block into unfolded logical lines (RFC 5322 folding: a continuation line begins with
|
|
40
63
|
// SP/HTAB and joins the previous). Returns the raw header strings (each "Name: value").
|
|
41
64
|
function _unfoldHeaders(headerText) {
|
|
42
|
-
var lines = headerText
|
|
65
|
+
var lines = _toLf(headerText).split("\n");
|
|
43
66
|
var out = [];
|
|
44
67
|
for (var i = 0; i < lines.length; i++) {
|
|
45
68
|
var ln = lines[i];
|
|
46
69
|
if (ln === "") continue;
|
|
47
|
-
if ((ln[0] === " " || ln[0] === "\t") && out.length) out[out.length - 1] += " " + ln
|
|
70
|
+
if ((ln[0] === " " || ln[0] === "\t") && out.length) out[out.length - 1] += " " + _stripLeadingHtab(ln);
|
|
48
71
|
else out.push(ln);
|
|
49
72
|
}
|
|
50
73
|
return out;
|
|
@@ -130,7 +153,7 @@ function parse(input, E, code) {
|
|
|
130
153
|
// with only the single conventional ": " delimiter space removed. Leading/trailing whitespace inside
|
|
131
154
|
// the body is preserved, so a consumer that must reproduce the exact authenticated field body (S/MIME
|
|
132
155
|
// header protection) is not handed a lossily-trimmed value.
|
|
133
|
-
headers.push({ name: nm, lname: nm.toLowerCase(), value: body.trim(), rawValue: body.
|
|
156
|
+
headers.push({ name: nm, lname: nm.toLowerCase(), value: body.trim(), rawValue: body.charAt(0) === " " ? body.slice(1) : body });
|
|
134
157
|
}
|
|
135
158
|
function header(name) { var l = name.toLowerCase(); for (var j = 0; j < headers.length; j++) if (headers[j].lname === l) return headers[j].value; return null; }
|
|
136
159
|
var ctv = header("content-type");
|
|
@@ -146,7 +169,7 @@ function parse(input, E, code) {
|
|
|
146
169
|
// Normalize a text body's line endings to the RFC 8551 sec. 3.1.1 canonical CRLF pair (any bare CR or
|
|
147
170
|
// LF becomes CRLF; an existing CRLF is preserved).
|
|
148
171
|
function canonicalizeText(bodyBytes) {
|
|
149
|
-
var s = bodyBytes.toString("latin1")
|
|
172
|
+
var s = _toCrlf(bodyBytes.toString("latin1"));
|
|
150
173
|
return Buffer.from(s, "latin1");
|
|
151
174
|
}
|
|
152
175
|
|
package/lib/ocsp.js
CHANGED
|
@@ -84,7 +84,7 @@ function _responseFromBytes(response) {
|
|
|
84
84
|
}
|
|
85
85
|
|
|
86
86
|
function _toDer(input, what) {
|
|
87
|
-
if (
|
|
87
|
+
if (guard.bytes.isByteSource(input)) return guard.bytes.snapshotSource(input, OcspError, "ocsp/bad-input", what || "input");
|
|
88
88
|
if (typeof input === "string") { try { return ocspSchema.pemDecode(input); } catch (e) { throw _err("ocsp/bad-input", (what || "input") + " PEM could not be decoded", e); } }
|
|
89
89
|
throw _err("ocsp/bad-input", (what || "input") + " must be a DER Buffer, Uint8Array, or PEM string");
|
|
90
90
|
}
|
|
@@ -221,16 +221,16 @@ function _buildRequest(query, opts) {
|
|
|
221
221
|
}
|
|
222
222
|
function _emitReq(der, opts) { return opts.pem ? ocspSchema.pemEncode(der, "OCSP REQUEST") : der; }
|
|
223
223
|
function _nameDer(name) {
|
|
224
|
-
if (
|
|
224
|
+
if (guard.bytes.isByteSource(name)) return guard.bytes.snapshotSource(name, OcspError, "ocsp/bad-input", "requestorName");
|
|
225
225
|
if (name && name.bytes) return name.bytes; // a parsed Name
|
|
226
|
-
throw _err("ocsp/bad-input", "requestorName must be a DER Name
|
|
226
|
+
throw _err("ocsp/bad-input", "requestorName must be a DER Name BufferSource or a parsed Name");
|
|
227
227
|
}
|
|
228
228
|
// The RAW DER of a certificate that will be embedded verbatim (the responder cert in a
|
|
229
229
|
// BasicOCSPResponse, the signer cert in a signed OCSPRequest). It must be supplied as bytes:
|
|
230
230
|
// a parsed certificate does not retain its full DER encoding, and re-encoding it would risk
|
|
231
231
|
// byte drift from the signed original, so a parsed cert is rejected rather than reconstructed.
|
|
232
232
|
function _normCertDer(cert, what) {
|
|
233
|
-
if (
|
|
233
|
+
if (guard.bytes.isByteSource(cert)) return guard.bytes.snapshotSource(cert, OcspError, "ocsp/bad-input", what || "a certificate");
|
|
234
234
|
if (typeof cert === "string") { try { return x509.pemDecode(cert); } catch (e) { throw _err("ocsp/bad-input", (what || "a certificate") + " PEM could not be decoded", e); } }
|
|
235
235
|
if (cert && cert.tbsBytes && cert.subjectPublicKeyInfo) {
|
|
236
236
|
throw _err("ocsp/bad-input", (what || "a certificate") + " to embed must be supplied as DER bytes or a PEM string, not a parsed certificate (the parser does not retain the full DER encoding needed to embed it verbatim)");
|
|
@@ -378,7 +378,7 @@ function _asDate(d) {
|
|
|
378
378
|
// the aliasing window is wiped rather than left to the garbage collector. Only our own
|
|
379
379
|
// allocations are listed: the caller's key is never written to.
|
|
380
380
|
function _snapshotSignerKey(key, owned) {
|
|
381
|
-
if (Buffer.isBuffer(key) || key instanceof Uint8Array) {
|
|
381
|
+
if (Buffer.isBuffer(key) || key instanceof Uint8Array) { // allow:byte-source-narrow -- responder private key on the key-ownership contract (Buffer / Uint8Array / PEM / CryptoKey); widening keys is a separate root decision
|
|
382
382
|
var copy = guard.bytes.snapshot(key, OcspError, "ocsp/bad-input", "the responder key");
|
|
383
383
|
owned.push(copy);
|
|
384
384
|
return copy;
|
|
@@ -550,8 +550,8 @@ function verify(response, opts) {
|
|
|
550
550
|
// A client that sent a nonce binds it (RFC 9654 / RFC 5019 sec. 4): a missing or mismatched
|
|
551
551
|
// response nonce fails the verdict closed, even if the status/signature were otherwise good.
|
|
552
552
|
var respNonce = _responseNonce(parsed);
|
|
553
|
-
var reqNonce =
|
|
554
|
-
? guard.bytes.
|
|
553
|
+
var reqNonce = guard.bytes.isByteSource(opts.requestNonce)
|
|
554
|
+
? guard.bytes.snapshotSource(opts.requestNonce, OcspError, "ocsp/bad-input", "opts.requestNonce") : null;
|
|
555
555
|
var matched = respNonce != null && reqNonce != null && guard.crypto.constantTimeEqual(respNonce, reqNonce);
|
|
556
556
|
var out = Object.assign({}, verdict, { nonceMatched: matched });
|
|
557
557
|
// The downgrade applies to `good` ONLY. `unknown` is the closed direction for a
|
package/lib/oid.js
CHANGED
|
@@ -652,6 +652,21 @@ function byName(n) {
|
|
|
652
652
|
return _byName.get(n);
|
|
653
653
|
}
|
|
654
654
|
|
|
655
|
+
// Does `s` look like a dotted-decimal OID -- one or more digit groups joined by single dots (at least two
|
|
656
|
+
// groups, no leading/trailing/doubled dot)? Distinguishes a numeric OID from a symbolic name so a caller may
|
|
657
|
+
// pass either. Decided by character scan (no regex); the arc-value bounds are the encoder's authority.
|
|
658
|
+
function isDottedDecimal(s) {
|
|
659
|
+
if (typeof s !== "string" || s.length === 0) return false;
|
|
660
|
+
var i, c, sawDot = false, atomEmpty = true;
|
|
661
|
+
for (i = 0; i < s.length; i += 1) {
|
|
662
|
+
c = s.charCodeAt(i);
|
|
663
|
+
if (c === 46) { if (atomEmpty) return false; sawDot = true; atomEmpty = true; } // "."
|
|
664
|
+
else if (c >= 48 && c <= 57) { atomEmpty = false; } // 0-9
|
|
665
|
+
else return false;
|
|
666
|
+
}
|
|
667
|
+
return sawDot && !atomEmpty;
|
|
668
|
+
}
|
|
669
|
+
|
|
655
670
|
function has(dotted) {
|
|
656
671
|
_assertDotted(dotted, "has");
|
|
657
672
|
return _byOid.has(dotted);
|
|
@@ -876,6 +891,7 @@ function kemParams(oidOrName) { return KEM_PARAMS[oidOrName]; }
|
|
|
876
891
|
module.exports = {
|
|
877
892
|
name: name,
|
|
878
893
|
byName: byName,
|
|
894
|
+
isDottedDecimal: isDottedDecimal,
|
|
879
895
|
has: has,
|
|
880
896
|
paramsMustBeAbsent: paramsMustBeAbsent,
|
|
881
897
|
kemParams: kemParams,
|
package/lib/path-validate.js
CHANGED
|
@@ -49,6 +49,7 @@ var cmsVerify = require("./cms-verify");
|
|
|
49
49
|
var cmpSession = require("./cmp-session");
|
|
50
50
|
var guard = require("./guard-all");
|
|
51
51
|
var intrinsic = require("./guard-intrinsic");
|
|
52
|
+
var _charCodeAt = intrinsic.uncurry(String.prototype.charCodeAt);
|
|
52
53
|
var constants = require("./constants");
|
|
53
54
|
var validator = require("./validator-all");
|
|
54
55
|
var cms = require("./schema-cms");
|
|
@@ -568,11 +569,54 @@ function hostConstraintMatch(constraint, host) {
|
|
|
568
569
|
// or a value carrying non-hostname characters (a scheme "://", a path "/", a
|
|
569
570
|
// port ":") is not a FQDN and cannot be matched against a domain-suffix
|
|
570
571
|
// constraint. Only [A-Za-z0-9.-] with at least one dot qualifies.
|
|
572
|
+
// A code-point membership table built from a string of the allowed characters, and the shared scan that
|
|
573
|
+
// checks every character of a non-empty string against one -- replacing the hostname-charset regexes
|
|
574
|
+
// through one loop (no per-predicate character-class chain).
|
|
575
|
+
function _charTable(chars) { var t = []; for (var i = 0; i < chars.length; i++) t[_charCodeAt(chars, i)] = true; return t; }
|
|
576
|
+
var _FQDN_TABLE = _charTable("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789.-"); // /^[a-z0-9.-]+$/i
|
|
577
|
+
var _DIGDOT_TABLE = _charTable("0123456789."); // /^[0-9.]+$/
|
|
578
|
+
function _allCharsIn(s, table) {
|
|
579
|
+
if (s.length === 0) return false;
|
|
580
|
+
for (var i = 0; i < s.length; i++) if (!table[_charCodeAt(s, i)]) return false;
|
|
581
|
+
return true;
|
|
582
|
+
}
|
|
583
|
+
// /^[0-9]/ : begins with an ASCII digit (a dotted-OID attempt, vs an EKU / purpose name).
|
|
584
|
+
function _startsWithDigit(s) { if (s.length === 0) return false; var c = _charCodeAt(s, 0); return c >= 48 && c <= 57; }
|
|
585
|
+
|
|
586
|
+
var _SCHEME_TABLE = _charTable("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789+.-"); // [a-z0-9+.-]
|
|
587
|
+
// /^[a-z][a-z0-9+.-]*:\/\/([^/?#]*)/i : the [start, end) range of the authority (up to the first / ? #
|
|
588
|
+
// or end), or null when the input is not a scheme "//" URI. Returns a range so the caller slices once
|
|
589
|
+
// (the slice that replaces the removed .exec), not a fresh substring here.
|
|
590
|
+
function _uriAuthorityRange(uri) {
|
|
591
|
+
var n = uri.length;
|
|
592
|
+
if (n === 0) return null;
|
|
593
|
+
var c0 = _charCodeAt(uri, 0);
|
|
594
|
+
if (!((c0 >= 65 && c0 <= 90) || (c0 >= 97 && c0 <= 122))) return null; // scheme starts with a letter
|
|
595
|
+
var p = 1;
|
|
596
|
+
while (p < n && _SCHEME_TABLE[_charCodeAt(uri, p)]) p += 1;
|
|
597
|
+
if (_charCodeAt(uri, p) !== 58 || _charCodeAt(uri, p + 1) !== 47 || _charCodeAt(uri, p + 2) !== 47) return null; // "://"
|
|
598
|
+
p += 3;
|
|
599
|
+
var authStart = p;
|
|
600
|
+
while (p < n) { var d = _charCodeAt(uri, p); if (d === 47 || d === 63 || d === 35) break; p += 1; } // [^/?#]*
|
|
601
|
+
return [authStart, p];
|
|
602
|
+
}
|
|
603
|
+
// /:\d+$/ : the index at which a trailing ":" + digits port starts (to slice off), or s.length when absent.
|
|
604
|
+
function _portStripIndex(s) {
|
|
605
|
+
var i = s.length;
|
|
606
|
+
while (i > 0) { var c = _charCodeAt(s, i - 1); if (c >= 48 && c <= 57) i -= 1; else break; }
|
|
607
|
+
return (i < s.length && i > 0 && _charCodeAt(s, i - 1) === 58) ? i - 1 : s.length;
|
|
608
|
+
}
|
|
609
|
+
// /^\[(.*)\]$/ -> "$1" : an IPv6 literal's surrounding "[" "]" removed, else the string unchanged.
|
|
610
|
+
function _stripBrackets(s) {
|
|
611
|
+
var n = s.length;
|
|
612
|
+
return (n >= 2 && _charCodeAt(s, 0) === 91 && _charCodeAt(s, n - 1) === 93) ? s.slice(1, n - 1) : s;
|
|
613
|
+
}
|
|
614
|
+
|
|
571
615
|
function isFqdnHost(host) {
|
|
572
616
|
var h = stripTrailingDot(host);
|
|
573
617
|
if (h === "" || h.indexOf(".") === -1) return false; // empty or single-label (localhost)
|
|
574
|
-
if (
|
|
575
|
-
if (
|
|
618
|
+
if (!_allCharsIn(h, _FQDN_TABLE)) return false; // scheme/path/port/IPv6 chars, "@", etc.
|
|
619
|
+
if (_allCharsIn(h, _DIGDOT_TABLE)) return false; // IPv4 dotted-quad literal
|
|
576
620
|
return true;
|
|
577
621
|
}
|
|
578
622
|
|
|
@@ -595,9 +639,9 @@ function uriMatch(constraint, uri) {
|
|
|
595
639
|
}
|
|
596
640
|
|
|
597
641
|
function uriHost(uri) {
|
|
598
|
-
var
|
|
599
|
-
if (
|
|
600
|
-
var authority =
|
|
642
|
+
var r = _uriAuthorityRange(uri);
|
|
643
|
+
if (r === null) return null;
|
|
644
|
+
var authority = uri.slice(r[0], r[1]);
|
|
601
645
|
// authority = [ userinfo "@" ] host [ ":" port ]. RFC 3986 userinfo does not
|
|
602
646
|
// contain a raw "@", so a conformant authority has at most one "@". With more
|
|
603
647
|
// than one the host cannot be determined reliably, so this fails closed
|
|
@@ -606,7 +650,7 @@ function uriHost(uri) {
|
|
|
606
650
|
var firstAt = authority.indexOf("@");
|
|
607
651
|
if (firstAt !== authority.lastIndexOf("@")) return null;
|
|
608
652
|
if (firstAt !== -1) authority = authority.slice(firstAt + 1);
|
|
609
|
-
var host = authority.
|
|
653
|
+
var host = authority.slice(0, _portStripIndex(authority));
|
|
610
654
|
return host === "" ? null : host; // an empty authority cannot be evaluated -> unsupported
|
|
611
655
|
}
|
|
612
656
|
|
|
@@ -788,8 +832,7 @@ function treeIsEmpty(tree) { return tree.children.length === 0; }
|
|
|
788
832
|
function isSubtreeBaseValid(tag, base) {
|
|
789
833
|
switch (tag) {
|
|
790
834
|
case 1: case 2: case 6: return typeof base === "string"; // rfc822Name / dNSName / URI
|
|
791
|
-
case 7: return
|
|
792
|
-
(base.length === 8 || base.length === 32); // iPAddress: address + mask
|
|
835
|
+
case 7: return Buffer.isBuffer(base) && (base.length === 8 || base.length === 32); // iPAddress: address + mask (any BufferSource is normalized to a Buffer in checkedSubtreeSeeds)
|
|
793
836
|
case 4: return base !== null && typeof base === "object" && !!base.rdns && intrinsic.isArray(base.rdns); // directoryName
|
|
794
837
|
default: return base !== undefined;
|
|
795
838
|
}
|
|
@@ -803,10 +846,19 @@ function checkedSubtreeSeeds(list, optName) {
|
|
|
803
846
|
if (list === undefined || list === null) return [];
|
|
804
847
|
if (!intrinsic.isArray(list)) throw E("path/bad-input", "validate: opts." + optName + " must be an array of { tag, base } subtree entries");
|
|
805
848
|
return list.map(function (st) {
|
|
806
|
-
if (!st || typeof st !== "object" || !Number.isInteger(st.tag) || st.tag < 0 || st.tag > 8
|
|
849
|
+
if (!st || typeof st !== "object" || !Number.isInteger(st.tag) || st.tag < 0 || st.tag > 8) {
|
|
850
|
+
throw E("path/bad-input", "validate: opts." + optName + " entries must be { tag: <GeneralName tag number 0..8>, base: <that form's constraint value> }");
|
|
851
|
+
}
|
|
852
|
+
// An iPAddress base (address+mask) may arrive as any BufferSource; normalize it to a
|
|
853
|
+
// private Buffer so the matcher can index it by byte and a caller cannot mutate it once a
|
|
854
|
+
// microtask has passed. The name forms (string / directoryName) are validated as they stand.
|
|
855
|
+
var base = (st.tag === 7 && guard.bytes.isByteSource(st.base))
|
|
856
|
+
? guard.bytes.snapshotSource(st.base, PathError, "path/bad-input", "an iPAddress subtree base")
|
|
857
|
+
: st.base;
|
|
858
|
+
if (!isSubtreeBaseValid(st.tag, base)) {
|
|
807
859
|
throw E("path/bad-input", "validate: opts." + optName + " entries must be { tag: <GeneralName tag number 0..8>, base: <that form's constraint value> }");
|
|
808
860
|
}
|
|
809
|
-
return { tag: st.tag, base:
|
|
861
|
+
return { tag: st.tag, base: base };
|
|
810
862
|
});
|
|
811
863
|
}
|
|
812
864
|
|
|
@@ -2522,8 +2574,8 @@ function verifyOcspResponse(response, cert, issuerCert, time, opts) {
|
|
|
2522
2574
|
var _OCSP_CLAIM = ["responseStatus", "basicResponse", "tbsResponseDataBytes"];
|
|
2523
2575
|
function _ocspFromBytes(response) {
|
|
2524
2576
|
return guard.parsed.fromTrustedSource(response, "ocspResponse", _OCSP_CLAIM, function (bytes) {
|
|
2525
|
-
if (!
|
|
2526
|
-
throw E("path/bad-input", "verifyOcspResponse: the response must be an OCSP response DER
|
|
2577
|
+
if (!guard.bytes.isByteSource(bytes) && typeof bytes !== "string") {
|
|
2578
|
+
throw E("path/bad-input", "verifyOcspResponse: the response must be an OCSP response DER BufferSource, a PEM string, or a pki.schema.ocsp.parseResponse result");
|
|
2527
2579
|
}
|
|
2528
2580
|
return ocsp.parseResponse(bytes);
|
|
2529
2581
|
}, E, "path/bad-input",
|
|
@@ -2674,7 +2726,7 @@ function resolvePurposeOpts(opts) {
|
|
|
2674
2726
|
// A dotted-form attempt (leads with a digit) must be a canonical OID -- a
|
|
2675
2727
|
// loose regex accepted a leading-zero / out-of-bounds key that would never
|
|
2676
2728
|
// match the canonical EKU the target advertises; anything else is a name.
|
|
2677
|
-
if (
|
|
2729
|
+
if (_startsWithDigit(p)) return guard.identifier.assertCanonicalOid(p, E, "path/bad-input", "validate: opts.requiredEku entry " + JSON.stringify(p));
|
|
2678
2730
|
var dotted = oid.byName(p);
|
|
2679
2731
|
if (typeof dotted !== "string") throw E("path/bad-input", "validate: opts.requiredEku entry " + JSON.stringify(p) + " is not a registered OID name");
|
|
2680
2732
|
return dotted;
|
|
@@ -2685,7 +2737,7 @@ function resolvePurposeOpts(opts) {
|
|
|
2685
2737
|
if (typeof opts.checkPurpose !== "string" || opts.checkPurpose.length === 0) {
|
|
2686
2738
|
throw E("path/bad-input", "validate: opts.checkPurpose must be a key-purpose OID name or dotted OID string");
|
|
2687
2739
|
}
|
|
2688
|
-
if (
|
|
2740
|
+
if (_startsWithDigit(opts.checkPurpose)) {
|
|
2689
2741
|
var cpDotted = guard.identifier.assertCanonicalOid(opts.checkPurpose, E, "path/bad-input", "validate: opts.checkPurpose");
|
|
2690
2742
|
checkPurpose = oid.name(cpDotted) || cpDotted; // normalize a dotted purpose OID to its name for the anchor map
|
|
2691
2743
|
} else {
|
|
@@ -2840,7 +2892,7 @@ function toAnchor(entry, verb) {
|
|
|
2840
2892
|
// entry refusal, not a raw exception leaked past the typed guards. `alg` and the raw publicKey are PINNED
|
|
2841
2893
|
// to ONE read here and used throughout, so a stateful accessor that returns a value now and throws (or
|
|
2842
2894
|
// returns a different value) on a later read cannot slip past this guard at the shape check or the snapshot.
|
|
2843
|
-
var isTuple = false, alg = null, pubRaw
|
|
2895
|
+
var isTuple = false, alg = null, pubRaw, nameRaw = null, pk = null, purposesSnap, distrustSnap;
|
|
2844
2896
|
var subjectDerRaw, labelRaw, mozRaw;
|
|
2845
2897
|
// A Proxy anchor is refused outright. Every field read below -- whether by property access or from an own
|
|
2846
2898
|
// data descriptor -- is answered by the anchor's own reflection, and a Proxy's traps let the same read
|
|
@@ -2915,7 +2967,10 @@ function toAnchor(entry, verb) {
|
|
|
2915
2967
|
// it is captured. A non-tuple leaves pk null / isTuple false and falls through to coerceCert below.
|
|
2916
2968
|
pubRaw = entry.publicKey;
|
|
2917
2969
|
if (guard.bytes.isByteSource(pubRaw)) {
|
|
2918
|
-
|
|
2970
|
+
// snapshotSource, not snapshot: isByteSource accepts all four BufferSource forms (Buffer, any view,
|
|
2971
|
+
// DataView, ArrayBuffer), so the normalizer must too -- snapshot accepts only Buffer/Uint8Array and
|
|
2972
|
+
// would accept-then-throw on an ArrayBuffer / DataView anchor publicKey.
|
|
2973
|
+
pk = guard.bytes.snapshotSource(pubRaw, PathError, "path/bad-input", who + ": trustAnchor publicKey");
|
|
2919
2974
|
}
|
|
2920
2975
|
nameRaw = entry.name;
|
|
2921
2976
|
alg = entry.algorithm;
|
|
@@ -3005,7 +3060,10 @@ function toAnchor(entry, verb) {
|
|
|
3005
3060
|
// resolve a polluted Object.prototype.rdns on a name that supplied none (and could invoke an inherited
|
|
3006
3061
|
// throwing getter) -- means a name with no own rdns is refused, never accepted on an inherited array.
|
|
3007
3062
|
var nameHasRdns = intrinsic.hasOwn(nameOut, "rdns") && intrinsic.isArray(nameOut.rdns);
|
|
3008
|
-
|
|
3063
|
+
// Gate on the NORMALIZED key `pk` (a private Buffer from snapshotSource iff publicKey was any of the four
|
|
3064
|
+
// BufferSource forms), not the raw `pubRaw` -- Buffer.isBuffer(pubRaw) would reject a Uint8Array / view /
|
|
3065
|
+
// ArrayBuffer publicKey that isByteSource + snapshotSource just accepted.
|
|
3066
|
+
if (!nameHasRdns || !Buffer.isBuffer(pk) || algStr === null) {
|
|
3009
3067
|
throw E("path/bad-input", who + ": a trustAnchor tuple must be { name: { rdns: [...] }, publicKey: Buffer, algorithm: an OID string or a SubjectPublicKeyInfo algorithm carrying an oid }");
|
|
3010
3068
|
}
|
|
3011
3069
|
// Hold the stated algorithm to a canonical dotted-decimal OID: a shape-valid but non-canonical value
|
|
@@ -3256,7 +3314,7 @@ async function _fetchAiaIssuers(current, aia) {
|
|
|
3256
3314
|
// A DNS-NAME host's private RESOLUTION can only be blocked by a transport that filters the resolved address;
|
|
3257
3315
|
// with an UNGUARDED transport (an injected fn that does not vouch blocksPrivateAddresses) fail closed -- fetch
|
|
3258
3316
|
// IP literals only (already validated above), never a hostname whose resolved address we cannot verify.
|
|
3259
|
-
if (net.isIP(u.hostname
|
|
3317
|
+
if (net.isIP(_stripBrackets(u.hostname)) === 0 && !aia.transportGuardsAddresses) continue;
|
|
3260
3318
|
u.hash = ""; // the fragment is never sent on the wire -> not part of the request/dedupe identity
|
|
3261
3319
|
if (intrinsic.setHas(aia.fetchedUrls, u.href) || intrinsic.setHas(seenThisCert, u.href)) continue; // dedupe (build-wide OR same-cert) on the normalized URL
|
|
3262
3320
|
if (seenThisCert.size >= aia.maxPerCert) break; // per-cert DISTINCT-url cap, checked BEFORE appending so maxAiaPerCert:0 collects nothing (no fetch at all)
|
package/lib/pbes2.js
CHANGED
|
@@ -157,7 +157,7 @@ function pbes2Encrypt(pwBytes, plaintext, opts, E, prefix) {
|
|
|
157
157
|
opts = opts || {};
|
|
158
158
|
var cipherName = opts.cipher || "aes256-CBC";
|
|
159
159
|
var keyBits = CONTENT_KEYBITS[O(cipherName)];
|
|
160
|
-
if (!keyBits ||
|
|
160
|
+
if (!keyBits || cipherName.indexOf("CBC") === -1) throw E(prefix + "/bad-input", "unsupported PBES2 cipher " + cipherName + " (AES-CBC only)");
|
|
161
161
|
var prf = opts.prf || "hmacWithSHA256";
|
|
162
162
|
var prfNode = prfNodeByName(prf, E, prefix);
|
|
163
163
|
var iterations = assertIterations(opts.iterations == null ? 2048 : opts.iterations, E, prefix);
|
|
@@ -200,7 +200,7 @@ function pbes2Decrypt(pwBytes, params, ciphertext, opts, E, prefix, budget) {
|
|
|
200
200
|
pb = parsePbkdf2Params(kdf[1].bytes, opts, E, prefix, true); // strictPrf: reject a non-canonical explicit default prf
|
|
201
201
|
var encOid = asn1.read.oid(encScheme[0]);
|
|
202
202
|
keyBits = CONTENT_KEYBITS[encOid];
|
|
203
|
-
if (!keyBits ||
|
|
203
|
+
if (!keyBits || (oid.name(encOid) || "").indexOf("CBC") === -1) throw E(prefix + "/unsupported-algorithm", "unsupported PBES2 encryptionScheme " + (oid.name(encOid) || encOid) + " (AES-CBC only)");
|
|
204
204
|
iv = asn1.read.octetString(encScheme[1]);
|
|
205
205
|
if (iv.length !== 16) throw E(prefix + "/bad-algorithm-parameters", "the AES-CBC IV must be 16 octets");
|
|
206
206
|
} catch (e) {
|
package/lib/pkcs12-build.js
CHANGED
|
@@ -216,7 +216,7 @@ function _p12Encode(pw) {
|
|
|
216
216
|
// A view first, so a detached backing store is a reject and never an empty password; then a copy,
|
|
217
217
|
// because everything this function returns is reported owned and gets zeroized after derivation,
|
|
218
218
|
// and handing back a view of a caller's Uint8Array would wipe the caller's own credential.
|
|
219
|
-
if (Buffer.isBuffer(pw) || pw instanceof Uint8Array) return guard.bytes.snapshot(pw, Pkcs12Error, "pkcs12/bad-input", "the password");
|
|
219
|
+
if (Buffer.isBuffer(pw) || pw instanceof Uint8Array) return guard.bytes.snapshot(pw, Pkcs12Error, "pkcs12/bad-input", "the password"); // allow:byte-source-narrow -- password on the ownership contract (string / Buffer / Uint8Array); widening passwords is a separate root decision
|
|
220
220
|
if (typeof pw !== "string") throw _err("pkcs12/bad-input", _MISSING_PASSWORD);
|
|
221
221
|
var out = Buffer.alloc(pw.length * 2 + 2); // + the 2-byte NULL terminator
|
|
222
222
|
for (var i = 0; i < pw.length; i++) {
|
|
@@ -507,7 +507,7 @@ async function _buildAuthSafeElement(sc, opts) {
|
|
|
507
507
|
var alg = sc.contentEncryptionAlgorithm || "aes-256-cbc";
|
|
508
508
|
// RFC 7292 sec. 4.1 lists only id-envelopedData for a public-key privacy safe (never id-ct-authEnvelopedData);
|
|
509
509
|
// cms.encrypt DEFAULTS to aes-256-gcm (AEAD -> AuthEnvelopedData), so force a CBC content cipher and reject GCM.
|
|
510
|
-
if (
|
|
510
|
+
if (alg !== "aes-128-cbc" && alg !== "aes-192-cbc" && alg !== "aes-256-cbc") throw _err("pkcs12/bad-input", "a public-key privacy safe requires an AES-CBC content cipher (aes-128|192|256-cbc), not " + JSON.stringify(alg) + " -- RFC 7292 sec. 4.1 admits only id-envelopedData, not GCM/AuthEnvelopedData");
|
|
511
511
|
var envCi = await cms.encrypt(safeContentsDer, sc.recipients, { contentEncryptionAlgorithm: alg, contentType: "data" });
|
|
512
512
|
return b.raw(envCi); // cms.encrypt returns the whole id-envelopedData ContentInfo DER -- splice it verbatim
|
|
513
513
|
}
|