@blamejs/pki 0.6.14 → 0.6.16
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 +27 -1
- package/lib/acme.js +59 -815
- package/lib/asn1-der.js +7 -245
- package/lib/attrcert-sign.js +12 -260
- package/lib/attrcert-verify.js +1 -7
- package/lib/byte-reader.js +0 -8
- package/lib/byte-writer.js +0 -11
- package/lib/cbor-det.js +0 -54
- package/lib/cmc-build.js +15 -357
- package/lib/cmc-verify.js +6 -239
- package/lib/cmp-build.js +41 -317
- package/lib/cmp-session.js +73 -760
- package/lib/cmp-verify.js +32 -488
- package/lib/cms-compress.js +3 -32
- package/lib/cms-decrypt.js +29 -294
- package/lib/cms-encrypt.js +14 -234
- package/lib/cms-sign.js +29 -324
- package/lib/cms-verify.js +9 -500
- package/lib/composite-kem.js +2 -102
- package/lib/composite-sig.js +1 -77
- package/lib/constants.js +0 -249
- package/lib/crl-sign.js +9 -237
- package/lib/crl-verify.js +1 -10
- package/lib/crmf-sign.js +19 -198
- package/lib/crmf-verify.js +1 -12
- package/lib/csr-sign.js +3 -34
- package/lib/csr-verify.js +1 -12
- package/lib/ct.js +28 -253
- package/lib/edwards-point.js +10 -44
- package/lib/est.js +28 -764
- package/lib/framework-error.js +4 -182
- package/lib/guard-all.js +1 -58
- package/lib/guard-async.js +1 -36
- package/lib/guard-bytes.js +6 -650
- package/lib/guard-compress.js +5 -102
- package/lib/guard-crypto.js +1 -40
- package/lib/guard-der.js +2 -93
- package/lib/guard-encoding.js +2 -60
- package/lib/guard-header.js +1 -36
- package/lib/guard-identifier.js +7 -531
- package/lib/guard-intrinsic.js +1 -150
- package/lib/guard-json.js +10 -73
- package/lib/guard-limits.js +1 -77
- package/lib/guard-list.js +1 -49
- package/lib/guard-name.js +3 -209
- package/lib/guard-parsed.js +7 -335
- package/lib/guard-range.js +1 -71
- package/lib/guard-secret.js +1 -102
- package/lib/guard-text.js +1 -64
- package/lib/guard-time.js +1 -97
- package/lib/hpke.js +3 -105
- package/lib/http-digest.js +14 -142
- package/lib/http-retry-after.js +1 -47
- package/lib/http-transport.js +21 -131
- package/lib/inspect.js +30 -284
- package/lib/ip-utils.js +6 -47
- package/lib/jose.js +7 -142
- package/lib/key.js +2 -67
- package/lib/lint.js +9 -194
- package/lib/merkle.js +1 -27
- package/lib/mime.js +9 -82
- package/lib/ocsp-request-verify.js +1 -14
- package/lib/ocsp-verify.js +1 -82
- package/lib/ocsp.js +11 -169
- package/lib/oid.js +5 -337
- package/lib/path-validate.js +103 -1492
- package/lib/pbes2.js +2 -89
- package/lib/pkcs12-build.js +31 -379
- package/lib/pki-build.js +64 -290
- package/lib/rc2.js +2 -18
- package/lib/rfc3339.js +4 -27
- package/lib/scep.js +140 -286
- package/lib/schema-all.js +0 -122
- package/lib/schema-attrcert.js +4 -146
- package/lib/schema-c509.js +153 -802
- package/lib/schema-cmc.js +5 -220
- package/lib/schema-cmp.js +2 -223
- package/lib/schema-cms.js +13 -427
- package/lib/schema-crl.js +5 -68
- package/lib/schema-crmf.js +6 -167
- package/lib/schema-csr.js +1 -54
- package/lib/schema-csrattrs.js +2 -87
- package/lib/schema-engine.js +0 -152
- package/lib/schema-ocsp.js +0 -117
- package/lib/schema-pkcs12.js +2 -93
- package/lib/schema-pkcs8.js +3 -35
- package/lib/schema-pkix.js +24 -655
- package/lib/schema-smime.js +0 -54
- package/lib/schema-tsp.js +3 -99
- package/lib/schema-x509.js +4 -63
- package/lib/shbs.js +11 -95
- package/lib/sign-scheme.js +2 -97
- package/lib/sigstore.js +9 -169
- package/lib/sleep.js +2 -8
- package/lib/smime.js +15 -408
- package/lib/tls-cert-compress.js +0 -62
- package/lib/trust.js +19 -185
- package/lib/tsp-sign.js +16 -270
- package/lib/validator-all.js +1 -30
- package/lib/validator-attcert.js +2 -46
- package/lib/validator-cose.js +2 -153
- package/lib/validator-keydesc.js +1 -37
- package/lib/validator-sig.js +1 -46
- package/lib/validator-tls.js +1 -88
- package/lib/validator-tpm.js +19 -122
- package/lib/verify-seam.js +1 -24
- package/lib/webauthn-mds.js +3 -400
- package/lib/webauthn.js +12 -833
- package/lib/webcrypto.js +5 -485
- package/lib/x509-sign.js +4 -108
- package/package.json +1 -1
- package/sbom.cdx.json +6 -6
package/lib/guard-list.js
CHANGED
|
@@ -1,86 +1,38 @@
|
|
|
1
1
|
// SPDX-License-Identifier: Apache-2.0
|
|
2
2
|
// Copyright (c) blamejs contributors
|
|
3
3
|
"use strict";
|
|
4
|
-
//
|
|
5
|
-
// @internal, so no operator-facing namespace. The documented surface is every verdict whose rule
|
|
6
|
-
// composes this guard: pki.path.validate (requiredEku, the policy tree), pki.cms.verify (the
|
|
7
|
-
// message trust verdict), the OCSP delegated-responder check, the TPM AIK purpose check, and the
|
|
8
|
-
// timestamp unknown-critical-extension gate.
|
|
9
|
-
//
|
|
10
|
-
// guard-list, membership decided by comparison and never by a prototype method.
|
|
11
|
-
//
|
|
12
|
-
// Defends the intrinsic-substitution class (CWE-349 / CWE-693: a control the caller can neutralize).
|
|
13
|
-
// Written `list.indexOf(v) !== -1` or `list.some(fn)`, a membership test asks the runtime, at the
|
|
14
|
-
// moment of the call, which function `indexOf` or `some` is. A caller who receives control after
|
|
15
|
-
// this package loads, through an option accessor or a `toJSON` or while a verb is suspended on an
|
|
16
|
-
// await, can replace it. The answer becomes whatever they chose, while every check around it still
|
|
17
|
-
// runs and still passes.
|
|
18
|
-
//
|
|
19
|
-
// The distinction that matters is what the answer IS. A scan over a local accumulator produces a
|
|
20
|
-
// value. A scan that decides whether a certificate carries a purpose, whether a policy is expected,
|
|
21
|
-
// or whether every signer is trusted IS the rule. Measured on the tree before this guard existed:
|
|
22
|
-
// with `Array.prototype.indexOf` replaced to report every value present, a certificate restricted
|
|
23
|
-
// to emailProtection satisfied a requiredEku of serverAuth, and pki.path.validate went from
|
|
24
|
-
// valid:false to valid:true.
|
|
25
|
-
//
|
|
26
|
-
// Capturing the method at load (guard-intrinsic) is the other half of the same defense, and is what
|
|
27
|
-
// a module already inside that discipline uses. This guard is for the rule sites themselves. A loop
|
|
28
|
-
// over `===` has nothing to replace, so it holds whatever the prototype says, in a module that has
|
|
29
|
-
// been swept and in one that has not.
|
|
4
|
+
// @internal
|
|
30
5
|
|
|
31
6
|
var _freeze = require("./guard-intrinsic").freeze;
|
|
32
7
|
|
|
33
|
-
// contains(list, value) -> boolean. Strict-equality membership, the `indexOf(v) !== -1` rule.
|
|
34
|
-
// A null / undefined list is honestly empty, never a throw: these run on verdict paths.
|
|
35
8
|
// @enforced-by behavioral -- `.indexOf(x) !== -1` has no rename-proof form that separates a
|
|
36
|
-
// membership RULE from its legitimate siblings: the identical token is a substring search
|
|
37
|
-
// (`authority.indexOf(":")`, `addr.indexOf("@")`) and a local index lookup, and a shape matching
|
|
38
|
-
// all of them fired on 37 sites of which most were strings. What makes a site this guard's is
|
|
39
|
-
// what the answer IS -- a decision about a certificate, a policy or a signer -- and that is a
|
|
40
|
-
// judgment no regex makes. The RED vectors are the guard: a codeSigning-only certificate still
|
|
41
|
-
// fails a serverAuth requiredEku, and an unauthorized responder is still refused, each with
|
|
42
|
-
// Array.prototype.indexOf replaced after load.
|
|
43
9
|
function contains(list, value) {
|
|
44
10
|
if (!list) return false;
|
|
45
11
|
for (var i = 0, n = list.length; i < n; i++) if (list[i] === value) return true;
|
|
46
12
|
return false;
|
|
47
13
|
}
|
|
48
14
|
|
|
49
|
-
// containsAll(list, values) -> boolean. Every one of `values` present in `list` -- the shape a
|
|
50
|
-
// "these purposes are all required" rule needs. No `values` is vacuously satisfied.
|
|
51
15
|
// @enforced-by behavioral -- the composition of contains() has no rename-proof shape of its own;
|
|
52
|
-
// the RED vectors (a codeSigning-only certificate still fails a serverAuth requiredEku with
|
|
53
|
-
// Array.prototype.indexOf replaced) are the guard.
|
|
54
16
|
function containsAll(list, values) {
|
|
55
17
|
if (!values) return true;
|
|
56
18
|
for (var i = 0, n = values.length; i < n; i++) if (!contains(list, values[i])) return false;
|
|
57
19
|
return true;
|
|
58
20
|
}
|
|
59
21
|
|
|
60
|
-
// anyMatches(list, predicate) -> boolean. The `some(fn)` rule: does any member satisfy it? The
|
|
61
|
-
// predicate is the caller's own function, so what this guard removes is the DISPATCH, not the test.
|
|
62
22
|
// @enforced-by behavioral -- `.some(` has legitimate non-rule siblings (building a list, reporting
|
|
63
|
-
// a count), so a blanket detector would false-fire; the RED vectors (a critical extension is
|
|
64
|
-
// still seen, an untrusted signer still reported) are the guard.
|
|
65
23
|
function anyMatches(list, predicate) {
|
|
66
24
|
if (!list) return false;
|
|
67
25
|
for (var i = 0, n = list.length; i < n; i++) if (predicate(list[i], i)) return true;
|
|
68
26
|
return false;
|
|
69
27
|
}
|
|
70
28
|
|
|
71
|
-
// allMatch(list, predicate) -> boolean. The `every(fn)` rule, same reasoning as anyMatches. An
|
|
72
|
-
// empty list is vacuously true, matching Array.prototype.every -- a caller that needs "non-empty
|
|
73
|
-
// AND all" tests the length itself, as pki.cms.verify does for the trust verdict.
|
|
74
29
|
// @enforced-by behavioral -- see anyMatches; the RED vector (an untrusted signer keeps
|
|
75
|
-
// trusted:false with Array.prototype.every replaced) is the guard.
|
|
76
30
|
function allMatch(list, predicate) {
|
|
77
31
|
if (!list) return true;
|
|
78
32
|
for (var i = 0, n = list.length; i < n; i++) if (!predicate(list[i], i)) return false;
|
|
79
33
|
return true;
|
|
80
34
|
}
|
|
81
35
|
|
|
82
|
-
// Frozen for the reason the family header gives: a boundary reads its guard off this object at the
|
|
83
|
-
// call, so a writable export would let a caller replace the check rather than pass it.
|
|
84
36
|
module.exports = _freeze({
|
|
85
37
|
contains: contains,
|
|
86
38
|
containsAll: containsAll,
|
package/lib/guard-name.js
CHANGED
|
@@ -1,41 +1,10 @@
|
|
|
1
1
|
// SPDX-License-Identifier: Apache-2.0
|
|
2
2
|
// Copyright (c) blamejs contributors
|
|
3
3
|
"use strict";
|
|
4
|
-
//
|
|
5
|
-
// @internal -- no operator-facing namespace. The documented surface is the
|
|
6
|
-
// consumers whose name-string integrity composes this guard (pki.path.validate
|
|
7
|
-
// DN chaining, pki.schema.x509 GeneralName / SAN decode).
|
|
8
|
-
//
|
|
9
|
-
// guard-name -- fail-closed name integrity: reject an embedded control byte in a
|
|
10
|
-
// distinguished-name / SAN string, and compare two distinguished names by their
|
|
11
|
-
// RFC 5280 sec. 7.1 canonical identity, never their raw bytes.
|
|
12
|
-
//
|
|
13
|
-
// Defends the name-truncation / display-confusion class (CVE-2009-2408): a NUL
|
|
14
|
-
// or control byte embedded in a decoded name lets an attacker make two different
|
|
15
|
-
// names compare equal (or a UI truncate at the NUL), so a cert issued for
|
|
16
|
-
// "good.example.com\0.evil.com" is treated as "good.example.com". CWE-158
|
|
17
|
-
// (improper neutralization of null byte) / CWE-20. The reject is at decode, so a
|
|
18
|
-
// truncation name never reaches a comparison or a display.
|
|
19
|
-
//
|
|
20
|
-
// Defends the DN identity-vs-bytes class (CWE-706): a distinguished name has many
|
|
21
|
-
// RFC 5280 sec. 7.1-equal DER encodings (case, whitespace, PrintableString vs
|
|
22
|
-
// UTF8String). Binding identity to raw bytes (a byte compare, or hashing
|
|
23
|
-
// name.bytes as a lookup key) silently treats two equal names as different, so
|
|
24
|
-
// certificate chaining breaks, a revocation issuer / OCSP responder fails to
|
|
25
|
-
// match, or (the mirror risk) a name constraint is escaped. Every DN identity
|
|
26
|
-
// decision routes through the one canonical comparison here.
|
|
4
|
+
// @internal
|
|
27
5
|
|
|
28
6
|
var intrinsic = require("./guard-intrinsic");
|
|
29
7
|
|
|
30
|
-
// The string operations this module is built out of, taken from the prototypes at module load and
|
|
31
|
-
// uncurried so no call site reads a property of them. Every value compared here is a primitive
|
|
32
|
-
// string, so each call below would otherwise dispatch to whatever `String.prototype` holds at the
|
|
33
|
-
// moment it runs, and those are ordinary writable properties. A `toLowerCase` that returns a
|
|
34
|
-
// constant makes every DN compare equal to every other, which is the whole of this guard's job
|
|
35
|
-
// answered by the caller: chaining accepts an unrelated issuer, a revocation entry matches a
|
|
36
|
-
// certificate it was never written for, and a name constraint stops excluding anything. The same
|
|
37
|
-
// reasoning covers the escaping helpers, where a replaced `charAt` decides what a report line says
|
|
38
|
-
// a subject was. See guard-intrinsic for why the capture alone is not enough.
|
|
39
8
|
var _charCodeAt = intrinsic.uncurry(String.prototype.charCodeAt);
|
|
40
9
|
var _charAt = intrinsic.uncurry(String.prototype.charAt);
|
|
41
10
|
var _toLowerCase = intrinsic.uncurry(String.prototype.toLowerCase);
|
|
@@ -45,29 +14,14 @@ var _lastIndexOf = intrinsic.uncurry(String.prototype.lastIndexOf);
|
|
|
45
14
|
var _numToString = intrinsic.uncurry(Number.prototype.toString);
|
|
46
15
|
var _create = intrinsic.create;
|
|
47
16
|
var _hasOwn = intrinsic.hasOwn;
|
|
48
|
-
// Already a plain function rather than a method, so it is invoked directly and has no receiver.
|
|
49
17
|
var _fromCharCode = String.fromCharCode;
|
|
50
18
|
var _String = String;
|
|
51
|
-
// The shape test that decides whether a comparison can be performed at all. Refusing is the
|
|
52
|
-
// fail-closed direction, so replacing this turns comparisons into refusals rather than into false
|
|
53
|
-
// matches, and it is captured for the same reason as the rest: what a guard does is settled before
|
|
54
|
-
// a caller runs, or it is settled by the caller.
|
|
55
19
|
var _isArray = Array.isArray;
|
|
56
|
-
// Byte identity and the shape test that gates it, both captured for the reason above: a
|
|
57
|
-
// distribution-point comparison asks whether two encodings are the same bytes, and an object
|
|
58
|
-
// supplying its own `equals` would be answering that question about itself.
|
|
59
20
|
var _isBuffer = intrinsic.isBuffer;
|
|
60
21
|
var _bufferEquals = intrinsic.bufferEquals;
|
|
61
22
|
var _getOwnPropertyDescriptor = intrinsic.getOwnPropertyDescriptor;
|
|
62
23
|
|
|
63
|
-
// assertNoControlBytes(str, E, code, label) -> str | throws E(code, ...)
|
|
64
|
-
// DirectoryString policy (a DN attribute value): reject NUL and C0 control bytes
|
|
65
|
-
// in a DECODED name string. TAB (0x09) is exempt; printable non-ASCII (a
|
|
66
|
-
// UTF8String CN carries accented / CJK characters) is allowed. `str` is assumed a
|
|
67
|
-
// string (the caller guards typeof). E is the (code, message) typed-error factory.
|
|
68
24
|
// @enforced-by behavioral -- the control-byte reject has no rename-proof code
|
|
69
|
-
// shape distinct from the ASN.1 charset readers; the CVE-2009-2408 RED vectors
|
|
70
|
-
// (a DN string with an embedded NUL / control byte rejects) are the guard.
|
|
71
25
|
function assertNoControlBytes(str, E, code, label) {
|
|
72
26
|
for (var i = 0; i < str.length; i++) {
|
|
73
27
|
var c = _charCodeAt(str, i);
|
|
@@ -78,16 +32,8 @@ function assertNoControlBytes(str, E, code, label) {
|
|
|
78
32
|
return str;
|
|
79
33
|
}
|
|
80
34
|
|
|
81
|
-
// assertPrintableIa5(buf, E, code, label) -> buf | throws E(code, ...)
|
|
82
|
-
// IA5String policy (a dNSName / rfc822Name / URI GeneralName): every byte must be
|
|
83
|
-
// printable 7-bit ASCII [0x20, 0x7e], since an embedded NUL or control byte enables the
|
|
84
|
-
// same name-truncation bypass downstream. `buf` is the raw GeneralName content.
|
|
85
35
|
// @enforced-by behavioral -- the printable-IA5 byte-range reject has no rename-proof
|
|
86
|
-
// code shape distinct from the ASN.1 IA5 reader; the CVE-2009-2408 RED vectors
|
|
87
|
-
// (a SAN with a control byte rejects) are the guard.
|
|
88
36
|
function assertPrintableIa5(buf, E, code, label) {
|
|
89
|
-
// The scan end comes from the slot, not from `buf.length`. A shorter answer stops the walk before
|
|
90
|
-
// the control byte, which is the whole defense skipped on a name that still carries it.
|
|
91
37
|
var n = intrinsic.sizeOf(buf);
|
|
92
38
|
for (var i = 0; i < n; i++) {
|
|
93
39
|
if (buf[i] < 0x20 || buf[i] > 0x7e) {
|
|
@@ -97,24 +43,11 @@ function assertPrintableIa5(buf, E, code, label) {
|
|
|
97
43
|
return buf;
|
|
98
44
|
}
|
|
99
45
|
|
|
100
|
-
// Canonical form of a single DN attribute value (RFC 5280 sec. 7.1): reject an
|
|
101
|
-
// embedded control byte (CVE-2009-2408) then case-fold and collapse internal
|
|
102
|
-
// whitespace. Every standard X.520 attribute uses caseIgnoreMatch, and this form
|
|
103
|
-
// matches OpenSSL's X509_NAME_cmp, so a chain OpenSSL accepts is not rejected. This
|
|
104
|
-
// canonicalization is the shape the guard-shape-reinlined detector keys on
|
|
105
|
-
// (declared on dnEqual): a boundary hand-rolling it is re-implementing DN identity.
|
|
106
|
-
// The collapse is a single walk, not a pattern replace. This runs on every
|
|
107
|
-
// attribute value of every name the toolkit compares, a certificate an attacker
|
|
108
|
-
// supplies included, and one pass with a running "was the last character a space"
|
|
109
|
-
// flag costs exactly the length. It also spells out which characters count as
|
|
110
|
-
// whitespace: RFC 5280 sec. 7.1 defers to X.520's caseIgnoreMatch, whose SPACE is
|
|
111
|
-
// the ASCII space, and a pattern's \s silently also folds VT, FF, NBSP and every
|
|
112
|
-
// Unicode space separator, which would equate two names X.520 keeps distinct.
|
|
113
46
|
function _isSpace(c) { return c === 0x20 || c === 0x09 || c === 0x0a || c === 0x0d; }
|
|
114
47
|
function _canonAttrValue(v, E, code, label) {
|
|
115
48
|
if (typeof v !== "string") return v;
|
|
116
49
|
assertNoControlBytes(v, E, code, label);
|
|
117
|
-
var out = "", lastWasSpace = true;
|
|
50
|
+
var out = "", lastWasSpace = true;
|
|
118
51
|
for (var i = 0; i < v.length; i++) {
|
|
119
52
|
if (_isSpace(_charCodeAt(v, i))) { lastWasSpace = true; continue; }
|
|
120
53
|
if (lastWasSpace && out.length) out += " ";
|
|
@@ -123,28 +56,10 @@ function _canonAttrValue(v, E, code, label) {
|
|
|
123
56
|
}
|
|
124
57
|
return _toLowerCase(out);
|
|
125
58
|
}
|
|
126
|
-
// rdnEqual(a, b, E, code, label) -> boolean. Canonical comparison of a single
|
|
127
|
-
// RelativeDistinguishedName (an unordered SET of type/value pairs, compared as a
|
|
128
|
-
// multiset). The RDN-level primitive a name-constraint directoryName prefix match
|
|
129
|
-
// composes; dnEqual composes it over the RDN sequence. Comparing an RDN by raw DER
|
|
130
|
-
// would treat two RFC 5280-equal names as different (or let a truncation name
|
|
131
|
-
// compare equal).
|
|
132
|
-
// Both comparands must be the RDN SEQUENCE, not the parsed Name object that carries one. This is
|
|
133
|
-
// not a style check: a parsed Name has no `length`, so `a.length !== b.length` compares undefined
|
|
134
|
-
// with undefined, the loop over `i < undefined` never runs, and the comparison returns TRUE for two
|
|
135
|
-
// unrelated names. An identity comparison that answers "equal" because it was handed the wrong
|
|
136
|
-
// shape is the worst possible failure of this guard: it is the one place a DN identity is
|
|
137
|
-
// decided, and every caller reads a true from it as proof. So a comparison that cannot be
|
|
138
|
-
// performed refuses, and the caller learns at the call site instead of trusting a fabricated
|
|
139
|
-
// match. (A caller holding a parsed Name passes its `.rdns`.)
|
|
140
59
|
function _assertSequence(a, b, E, code, label, what) {
|
|
141
60
|
if (!_isArray(a) || !_isArray(b)) {
|
|
142
61
|
throw E(code, "cannot compare " + label + ": " + what + " comparison requires the RDN sequence on both sides (pass name.rdns, not the parsed Name)");
|
|
143
62
|
}
|
|
144
|
-
// A hole is refused here rather than read below. Indexing a sparse array walks to
|
|
145
|
-
// `Array.prototype`, so a numeric property planted there supplies an attribute at the missing
|
|
146
|
-
// index and the comparison decides identity on a value the name does not carry. A real RDN
|
|
147
|
-
// sequence has no holes, so refusing costs nothing and there is no value to guess at.
|
|
148
63
|
if (_hasHole(a) || _hasHole(b)) {
|
|
149
64
|
throw E(code, "cannot compare " + label + ": " + what + " comparison requires a dense sequence on both sides");
|
|
150
65
|
}
|
|
@@ -158,9 +73,6 @@ function _hasHole(arr) {
|
|
|
158
73
|
function rdnEqual(a, b, E, code, label) {
|
|
159
74
|
_assertSequence(a, b, E, code, label, "an RDN");
|
|
160
75
|
if (a.length !== b.length) return false;
|
|
161
|
-
// A null-prototype accumulator rather than an array: a numeric property planted on
|
|
162
|
-
// `Array.prototype` reads back as "already matched" for an index nothing has matched, which skips
|
|
163
|
-
// a candidate attribute and reports two equal RDNs as different.
|
|
164
76
|
var used = _create(null);
|
|
165
77
|
for (var i = 0; i < a.length; i++) {
|
|
166
78
|
var found = false;
|
|
@@ -174,12 +86,6 @@ function rdnEqual(a, b, E, code, label) {
|
|
|
174
86
|
}
|
|
175
87
|
return true;
|
|
176
88
|
}
|
|
177
|
-
// dnEqual(rdnsA, rdnsB, E, code, label) -> boolean. RFC 5280 sec. 7.1 canonical
|
|
178
|
-
// distinguished-name comparison over the RDN sequence, the single place a DN identity
|
|
179
|
-
// is decided, so no caller binds identity to raw DER (a byte compare, or hashing
|
|
180
|
-
// name.bytes, would treat two RFC 5280-equal names as different, breaking a chain or
|
|
181
|
-
// a revocation match, or, the mirror risk, escaping a name constraint). The
|
|
182
|
-
// per-RDN canonicalization + control-byte reject is in rdnEqual / _canonAttrValue.
|
|
183
89
|
// @enforced-by guard-shape-reinlined
|
|
184
90
|
// @guard-shape replace\(/\\s\+/g,
|
|
185
91
|
function dnEqual(rdnsA, rdnsB, E, code, label) {
|
|
@@ -191,27 +97,7 @@ function dnEqual(rdnsA, rdnsB, E, code, label) {
|
|
|
191
97
|
return true;
|
|
192
98
|
}
|
|
193
99
|
|
|
194
|
-
// dpnCorresponds(a, b, E, code, label) -> boolean. Do two DistributionPointNames name the
|
|
195
|
-
// same CRL distribution point? RFC 5280 sec. 6.3.3(b)(2)(i) asks it of a certificate's
|
|
196
|
-
// DistributionPoint against a shard CRL's issuingDistributionPoint, and RFC 9483 sec. 4.3.4
|
|
197
|
-
// asks it of the dpn a CMP crlUpdate requested against the IDP of the CRL that came back.
|
|
198
|
-
// The answer is "at least one name in common", compared by BYTE-IDENTICAL DER.
|
|
199
|
-
//
|
|
200
|
-
// The comparison key is pinned by sec. 5.2.5: "The identical encoding MUST be used in the
|
|
201
|
-
// distributionPoint fields of the certificate and the CRL". This is therefore the one name
|
|
202
|
-
// comparison in the toolkit that must NOT canonicalize. dnEqual's sec. 7.1 rule applied here
|
|
203
|
-
// would equate two encodings the spec keeps distinct, and a CRL would be accepted as covering
|
|
204
|
-
// a point it was never published for. Whole-set equality is the opposite error: a point
|
|
205
|
-
// published under several names (a URI beside a directoryName) shares one of them with a
|
|
206
|
-
// reference that lists only that one, and demanding all would reject its own CRL.
|
|
207
|
-
//
|
|
208
|
-
// Mixed forms never correspond, with fullName on one side and nameRelativeToCRLIssuer on the
|
|
209
|
-
// other. Deciding that would mean resolving an RDN fragment against a CRL issuer DN this
|
|
210
|
-
// comparison is not given, so the answer is no.
|
|
211
|
-
//
|
|
212
100
|
// @enforced-by behavioral -- the rule's identity is carried by the kind strings, which the
|
|
213
|
-
// pattern walk strips before matching, so it has no rename-proof code shape. The RED vectors
|
|
214
|
-
// in guard-name.test.js and the crlUpdate vectors in cmp-session.test.js are the guard.
|
|
215
101
|
function dpnCorresponds(a, b, E, code, label) {
|
|
216
102
|
var x = _reduceDpn(a, E, code, label), y = _reduceDpn(b, E, code, label);
|
|
217
103
|
if (x.kind !== y.kind) return false;
|
|
@@ -224,25 +110,9 @@ function dpnCorresponds(a, b, E, code, label) {
|
|
|
224
110
|
return false;
|
|
225
111
|
}
|
|
226
112
|
|
|
227
|
-
// The comparand, checked and REDUCED to the values that were checked. The reason _assertSequence
|
|
228
|
-
// gives applies here in full: a comparison handed the wrong shape must refuse at the call site
|
|
229
|
-
// instead of answering. Reading `names` off a value that has none would run a loop over
|
|
230
|
-
// `i < undefined` zero times and report "no correspondence", which reads as a clean refusal of a
|
|
231
|
-
// CRL and hides that nothing was compared; reading it off a sparse array would walk to
|
|
232
|
-
// Array.prototype for the missing index and compare a name the reference never carried.
|
|
233
|
-
//
|
|
234
|
-
// Every field must be the comparand's OWN DATA property, and its value is taken FROM the property
|
|
235
|
-
// descriptor. That is one reflective read per field, so the value the shape check validated is the
|
|
236
|
-
// value the comparison is answered from: no second read is left for an accessor, or a Proxy trap,
|
|
237
|
-
// to answer differently, and a field the decoder never produces (inherited, or reached through a
|
|
238
|
-
// getter) is refused. Buffers, not caller byte-likes, for the same reason: the comparison is byte
|
|
239
|
-
// identity, and an object answering `equals` or `length` for itself would be deciding the question
|
|
240
|
-
// asked of it.
|
|
241
113
|
function _reduceDpn(d, E, code, label) {
|
|
242
114
|
function refuse(why) { return E(code, "cannot compare " + label + ": " + why); }
|
|
243
115
|
function ownValue(o, k) {
|
|
244
|
-
// FromPropertyDescriptor hands back a fresh ordinary object, so `value` here is the value the
|
|
245
|
-
// object itself reported for that key, including through a Proxy's own-descriptor trap.
|
|
246
116
|
var desc = _getOwnPropertyDescriptor(o, k);
|
|
247
117
|
if (!desc || !_hasOwn(desc, "value")) throw refuse("a distribution-point comparison requires " + k + " to be the comparand's own value (RFC 5280 sec. 6.3.3 compares decoded names)");
|
|
248
118
|
return desc.value;
|
|
@@ -259,12 +129,7 @@ function _reduceDpn(d, E, code, label) {
|
|
|
259
129
|
}
|
|
260
130
|
var names = ownValue(d, "names");
|
|
261
131
|
if (!_isArray(names)) throw refuse("a fullName distribution point must carry a sequence of encoded GeneralNames");
|
|
262
|
-
// The length is read once, into the bound the copy runs to. A hole and an inherited element are
|
|
263
|
-
// the same refusal as any other missing own value: indexing past a hole walks to Array.prototype,
|
|
264
|
-
// where a planted numeric property supplies a name the reference never carried.
|
|
265
132
|
var len = names.length;
|
|
266
|
-
// Indexed assignment, never a push: the accumulator must not be built by a method a caller can
|
|
267
|
-
// replace, since what it collects is what the comparison is answered from.
|
|
268
133
|
var taken = [];
|
|
269
134
|
for (var i = 0; i < len; i++) {
|
|
270
135
|
var n = ownValue(names, i);
|
|
@@ -274,15 +139,6 @@ function _reduceDpn(d, E, code, label) {
|
|
|
274
139
|
return { kind: kind, names: taken };
|
|
275
140
|
}
|
|
276
141
|
|
|
277
|
-
// escapeControlBytes(str) -> str. The render-side sibling of assertNoControlBytes:
|
|
278
|
-
// where a name string must still be DISPLAYED best-effort (a human-readable report,
|
|
279
|
-
// a log line) instead of rejected, neutralize every C0 control byte and DEL by
|
|
280
|
-
// rendering it as \xHH. A bare CR / LF / NUL in a decoded dNSName or DN value would
|
|
281
|
-
// otherwise forge or overwrite report lines in a terminal or log (CWE-117 output-log
|
|
282
|
-
// injection / CWE-116 improper output encoding). Non-control bytes pass through.
|
|
283
|
-
// Written as a charCodeAt scan, not a /[\x00-\x1f]/ regex: eslint's no-control-regex
|
|
284
|
-
// (correctly) refuses control characters in a regex literal, and the control-byte
|
|
285
|
-
// range test is the rename-proof shape the detector keys on regardless.
|
|
286
142
|
// @enforced-by guard-shape-reinlined
|
|
287
143
|
// @guard-shape < 0x20 \|\| \w+ === 0x7f
|
|
288
144
|
function escapeControlBytes(str) {
|
|
@@ -296,28 +152,10 @@ function escapeControlBytes(str) {
|
|
|
296
152
|
return out;
|
|
297
153
|
}
|
|
298
154
|
|
|
299
|
-
|
|
300
|
-
// attribute value (the input is the already-unwrapped semantic string): backslash-
|
|
301
|
-
// escape the always-specials , + " \ < > ; and a NUL (as \00), then the positional
|
|
302
|
-
// rules -- a trailing space as '\ ' and a leading '#' or space as '\#' / '\ '. Without
|
|
303
|
-
// this a value like `foo, CN=admin` renders as two RDNs, and a literal `#05` collides
|
|
304
|
-
// with the hexstring form, so the report misstates a subject/issuer name (CWE-116).
|
|
305
|
-
// The one place a DN attribute value is made display-safe; pki.schema.pkix's DN
|
|
306
|
-
// rendering composes it, and pki.inspect reuses that parser output (name.dn).
|
|
307
|
-
var DN_SPECIAL = { 0x2c: 1, 0x2b: 1, 0x22: 1, 0x5c: 1, 0x3c: 1, 0x3e: 1, 0x3b: 1 }; // , + " \ < > ;
|
|
308
|
-
// RFC 4514 sec. 2.4's special set is the TABLE above and the walk is a single pass.
|
|
309
|
-
// The separator escape used to be a pattern replace feeding a second loop, which
|
|
310
|
-
// meant two passes over an attacker-supplied value and a character class holding a
|
|
311
|
-
// quote and a backslash inside a regex literal, the form most easily misread by a
|
|
312
|
-
// human and, as it happened, by the codebase-patterns literal-stripper.
|
|
313
|
-
//
|
|
155
|
+
var DN_SPECIAL = { 0x2c: 1, 0x2b: 1, 0x22: 1, 0x5c: 1, 0x3c: 1, 0x3e: 1, 0x3b: 1 };
|
|
314
156
|
// @enforced-by behavioral -- the escaping has no rename-proof code shape distinct
|
|
315
|
-
// from ordinary string building; the guard-name RED vectors + the schema-pkix DN
|
|
316
|
-
// round-trip vectors (a comma / plus / leading '#' renders backslash-escaped) are the guard.
|
|
317
157
|
function escapeDnValue(v) {
|
|
318
158
|
var s = _String(v), out = "";
|
|
319
|
-
// A NUL / control octet becomes '\' + two hex digits, so an embedded CR / LF / NUL
|
|
320
|
-
// in a decoded DN value can never forge a report line when displayed.
|
|
321
159
|
for (var i = 0; i < s.length; i++) {
|
|
322
160
|
var c = _charCodeAt(s, i);
|
|
323
161
|
if (c < 0x20 || c === 0x7f) out += "\\" + (c < 16 ? "0" : "") + _toUpperCase(_numToString(c, 16));
|
|
@@ -329,33 +167,10 @@ function escapeDnValue(v) {
|
|
|
329
167
|
return out;
|
|
330
168
|
}
|
|
331
169
|
|
|
332
|
-
// emailEqual(a, b) -> "match" | "no-match" | "not-comparable". The RFC 5280 sec. 7.5
|
|
333
|
-
// sibling of dnEqual: two email addresses match iff their local-parts are an exact match
|
|
334
|
-
// AND their host-parts match under a case-insensitive ASCII comparison. The local-part is
|
|
335
|
-
// NEVER case-folded -- RFC 8398 sec. 5 states it "MUST NOT be transformed in any way, such
|
|
336
|
-
// as by doing case folding or normalization of any kind", because Alice@ and alice@ are
|
|
337
|
-
// permitted to be different mailboxes.
|
|
338
|
-
//
|
|
339
|
-
// The result is three-valued on purpose. An internationalized host-part (an `xn--` A-label
|
|
340
|
-
// or any non-ASCII octet) needs an IDNA2008 A-label/U-label transform this toolkit does not
|
|
341
|
-
// carry, so the rule cannot be applied. Returning "no-match" there would answer a question
|
|
342
|
-
// that was never asked, and a caller acting on it would reject a legitimate sender; the
|
|
343
|
-
// honest verdict is that the comparison was unavailable. A guard that cannot decide says so
|
|
344
|
-
// instead of guessing (the value is fail-CLOSED at the call site: no caller may read
|
|
345
|
-
// "not-comparable" as a pass).
|
|
346
|
-
//
|
|
347
|
-
// RFC 8398 sec. 5 also forbids wildcards outright -- "implementations MUST NOT interpret
|
|
348
|
-
// any characters as wildcards" -- so no character here is ever special. A certificate
|
|
349
|
-
// carrying `*@example.com` matches that literal string and nothing else. Name-CONSTRAINT
|
|
350
|
-
// processing (RFC 5280 sec. 4.2.1.10 as extended by RFC 8398 sec. 6), where a bare domain
|
|
351
|
-
// legitimately covers a whole subtree, is a different rule and stays in path-validate.
|
|
352
|
-
//
|
|
353
170
|
// @enforced-by guard-shape-reinlined
|
|
354
171
|
// @guard-shape lastIndexOf\("@"\)
|
|
355
172
|
function emailEqual(a, b) {
|
|
356
173
|
if (typeof a !== "string" || typeof b !== "string") return "not-comparable";
|
|
357
|
-
// Split on the LAST "@": a quoted local-part may legally contain one, and taking the
|
|
358
|
-
// first would move part of the mailbox into the domain and compare the wrong halves.
|
|
359
174
|
var ai = _lastIndexOf(a, "@"), bi = _lastIndexOf(b, "@");
|
|
360
175
|
if (ai <= 0 || bi <= 0 || ai === a.length - 1 || bi === b.length - 1) return "not-comparable";
|
|
361
176
|
var aLocal = _strSlice(a, 0, ai), bLocal = _strSlice(b, 0, bi);
|
|
@@ -365,31 +180,12 @@ function emailEqual(a, b) {
|
|
|
365
180
|
return lowerAscii(aHost) === lowerAscii(bHost) ? "match" : "no-match";
|
|
366
181
|
}
|
|
367
182
|
|
|
368
|
-
// A host-part this comparison can decide: ASCII, and carrying no A-label. Anything else
|
|
369
|
-
// needs the IDNA transform named above, so it is reported as undecidable rather than
|
|
370
|
-
// compared as raw bytes -- `xn--e1afmkfd.example` and its Unicode form are one host, and
|
|
371
|
-
// treating them as different would be a wrong answer rather than an absent one.
|
|
372
183
|
function _asciiHost(h) {
|
|
373
184
|
if (h.length === 0) return false;
|
|
374
|
-
// ASCII is the whole test, and an A-label is ASCII. Two hosts already in A-label form
|
|
375
|
-
// need no transformation to be compared -- they are the same encoding -- so rejecting
|
|
376
|
-
// them for carrying `xn--` would make sender binding unusable for every certificate with
|
|
377
|
-
// an internationalized domain, including one whose address matches exactly. The case an
|
|
378
|
-
// IDNA transform WOULD be needed for is an A-label against a U-label, and the U-label
|
|
379
|
-
// side is non-ASCII, so it is refused here anyway.
|
|
380
185
|
for (var i = 0; i < h.length; i++) if (_charCodeAt(h, i) > 0x7f) return false;
|
|
381
186
|
return true;
|
|
382
187
|
}
|
|
383
188
|
|
|
384
|
-
// lowerAscii(s) -> string
|
|
385
|
-
// ASCII-only lowercase. String.toLowerCase() is Unicode-aware and would fold characters
|
|
386
|
-
// outside ASCII (the Kelvin sign lowercases to "k"), which is precisely the mapping a
|
|
387
|
-
// name comparison does not authorize: a name folded that way is one the certificate or
|
|
388
|
-
// message does not carry, and comparing it equal to a name that was asked for is the
|
|
389
|
-
// defect. Every case-insensitive name comparison in the toolkit -- a host part, a CMP
|
|
390
|
-
// domain, an ACME identifier set -- folds through this one definition, so none of them can
|
|
391
|
-
// disagree about which characters case-fold.
|
|
392
|
-
//
|
|
393
189
|
// @enforced-by guard-shape-reinlined
|
|
394
190
|
// @guard-shape 0x41\s*&&\s*[A-Za-z_$][\w$]*\s*<=\s*0x5[aA]\s*\)\s*\?[^:]*\+\s*(?:32|0x20)\s*\)
|
|
395
191
|
function lowerAscii(s) {
|
|
@@ -401,8 +197,6 @@ function lowerAscii(s) {
|
|
|
401
197
|
return out;
|
|
402
198
|
}
|
|
403
199
|
|
|
404
|
-
// Frozen for the reason the family header gives: a boundary reads its guard off this object at the
|
|
405
|
-
// call, so a writable export would let a caller replace the check rather than pass it.
|
|
406
200
|
module.exports = intrinsic.freeze({
|
|
407
201
|
assertNoControlBytes: assertNoControlBytes, assertPrintableIa5: assertPrintableIa5,
|
|
408
202
|
dnEqual: dnEqual, rdnEqual: rdnEqual, emailEqual: emailEqual, dpnCorresponds: dpnCorresponds,
|