@blamejs/pki 0.6.13 → 0.6.15
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 +22 -1
- package/README.md +1 -1
- package/index.js +1 -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 +59 -233
- 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/pki-build.js
CHANGED
|
@@ -1,45 +1,21 @@
|
|
|
1
1
|
// SPDX-License-Identifier: Apache-2.0
|
|
2
2
|
// Copyright (c) blamejs contributors
|
|
3
3
|
"use strict";
|
|
4
|
-
//
|
|
5
|
-
// @internal -- the ENCODE-direction sibling of schema-pkix.js (which is decode-only). `makeBuilder(ctx)`
|
|
6
|
-
// binds a domain namespace once and returns the shared PKIX producing primitives every signer module
|
|
7
|
-
// composes: the distinguished-name encoder, the GeneralName + RFC 5280 sec. 4.2.1 extension-value
|
|
8
|
-
// encoders, the embedded-input validators (a raw Name / a SubjectPublicKeyInfo / a pre-encoded Extension
|
|
9
|
-
// run through the same parser the decoder uses), the sign-scheme bridge, and the post-sign signature
|
|
10
|
-
// self-check (the key-match / proof-of-possession verify). Each is parameterized on the CALLER's error
|
|
11
|
-
// class + code prefix, so pki.x509.sign keeps x509/* codes and pki.csr.sign keeps csr/*.
|
|
4
|
+
// @internal
|
|
12
5
|
|
|
13
6
|
var asn1 = require("./asn1-der");
|
|
14
7
|
var schema = require("./schema-engine");
|
|
15
8
|
var compositeSig = require("./composite-sig");
|
|
16
9
|
var guard = require("./guard-all");
|
|
17
10
|
var intrinsic = require("./guard-intrinsic");
|
|
18
|
-
// Each of these doors refuses a caller argument that is not a list. Read live, a replacement
|
|
19
|
-
// answering true lets a non-list through the check written to stop it.
|
|
20
11
|
var _isArray = intrinsic.isArray;
|
|
21
|
-
// Taken at load for the reason every capture here is: the question "do these two encodings name the
|
|
22
|
-
// same key" decides whether a proof of possession is accepted, and a caller who replaces the copy or
|
|
23
|
-
// the comparison afterwards decides it instead.
|
|
24
12
|
var _bufferFrom = intrinsic.bufferFrom;
|
|
25
13
|
var _bufferEquals = intrinsic.bufferEquals;
|
|
26
14
|
var oid = require("./oid");
|
|
27
15
|
var ipUtils = require("./ip-utils");
|
|
28
|
-
// packIpLiteral decides a GeneralName form (a bare string -> iPAddress or not), so its binding is taken at
|
|
29
|
-
// load: calling it through the exports object would be a live property read, and code that reassigned that
|
|
30
|
-
// property could steer the form decision without touching a regex.
|
|
31
16
|
var _packIpLiteral = ipUtils.packIpLiteral;
|
|
32
17
|
var nodeCrypto = require("crypto");
|
|
33
|
-
// The key-material comparison, taken at load like every other operation a decision here rests on.
|
|
34
18
|
var _keyEquals = intrinsic.uncurry(nodeCrypto.KeyObject.prototype.equals);
|
|
35
|
-
// The bare-string GeneralName classifier (_classifyBareGeneralName) decides which name FORM a SAN string
|
|
36
|
-
// becomes. It uses no regular expression: a regex is matched through a live, replaceable protocol
|
|
37
|
-
// (RegExp.prototype.exec / test, String.prototype.match / split consult the regex's own `exec` and the
|
|
38
|
-
// `[Symbol.match]` / `[Symbol.split]` hooks, and RegExp.prototype.compile can mutate a pattern in place),
|
|
39
|
-
// so co-resident code could steer a form decision without our seeing it -- and the structural URI/email
|
|
40
|
-
// rules a regex cannot state cleanly are what kept drawing edge-case escapes. Instead every form is
|
|
41
|
-
// decided by scanning the string one character code at a time through these captured string primitives,
|
|
42
|
-
// which read the value directly and have no per-character dispatch to replace.
|
|
43
19
|
var _charCodeAt = intrinsic.uncurry(String.prototype.charCodeAt);
|
|
44
20
|
var _stringIndexOf = intrinsic.stringIndexOf;
|
|
45
21
|
var _stringLastIndexOf = intrinsic.uncurry(String.prototype.lastIndexOf);
|
|
@@ -49,67 +25,50 @@ var _objectKeys = intrinsic.keys;
|
|
|
49
25
|
var _isBufferChk = intrinsic.isBuffer;
|
|
50
26
|
var _stringify = intrinsic.stringify;
|
|
51
27
|
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
// at a time; a caller cannot steer the answer by replacing a prototype method. The classifier below tries
|
|
55
|
-
// the forms in a fixed precedence and refuses anything none of them accepts (the object form is the escape).
|
|
56
|
-
function _isAlphaCode(c) { return (c >= 65 && c <= 90) || (c >= 97 && c <= 122); } // A-Z a-z
|
|
57
|
-
function _isDigitCode(c) { return c >= 48 && c <= 57; } // 0-9
|
|
28
|
+
function _isAlphaCode(c) { return (c >= 65 && c <= 90) || (c >= 97 && c <= 122); }
|
|
29
|
+
function _isDigitCode(c) { return c >= 48 && c <= 57; }
|
|
58
30
|
function _isHexCode(c) { return (c >= 48 && c <= 57) || (c >= 97 && c <= 102) || (c >= 65 && c <= 70); }
|
|
59
31
|
|
|
60
|
-
// RFC 3986 sec. 2.3 unreserved: ALPHA / DIGIT / "-" / "." / "_" / "~".
|
|
61
32
|
function _isUnreserved(c) { return _isAlphaCode(c) || _isDigitCode(c) || c === 45 || c === 46 || c === 95 || c === 126; }
|
|
62
|
-
// RFC 3986 sec. 2.2 sub-delims: ! $ & ' ( ) * + , ; =
|
|
63
33
|
function _isSubDelim(c) {
|
|
64
34
|
switch (c) {
|
|
65
35
|
case 33: case 36: case 38: case 39: case 40: case 41: case 42: case 43: case 44: case 59: case 61: return true;
|
|
66
36
|
default: return false;
|
|
67
37
|
}
|
|
68
38
|
}
|
|
69
|
-
// A userinfo byte: unreserved / sub-delim / ":" (percent-encoding handled separately). No "@".
|
|
70
39
|
function _isUserinfoByte(c) { return _isUnreserved(c) || _isSubDelim(c) || c === 58; }
|
|
71
|
-
// A path/query/fragment byte: unreserved / sub-delim / a non-bracket gen-delim (":" "/" "?" "#" "@").
|
|
72
|
-
// "%" is handled separately (it must open a "%HEXHEX" triplet); the IP-literal brackets "[" "]" are
|
|
73
|
-
// excluded here because they are authority syntax -- a bracketed IPv6 host is validated in
|
|
74
|
-
// _validUriAuthority (RFC 3986 sec. 3.2.2), never in the path.
|
|
75
40
|
function _isUriByte(c) {
|
|
76
41
|
return _isUnreserved(c) || _isSubDelim(c) || c === 58 || c === 47 || c === 63 || c === 35 || c === 64;
|
|
77
42
|
}
|
|
78
43
|
|
|
79
|
-
// An RFC 5322 sec. 3.2.3 "atext" byte: ALPHA / DIGIT and a fixed set of specials, used for an unquoted
|
|
80
|
-
// email local part. "." is deliberately excluded so the dot-atom structure is enforced by the scan.
|
|
81
44
|
function _isAtextByte(c) {
|
|
82
45
|
if (_isAlphaCode(c) || _isDigitCode(c)) return true;
|
|
83
46
|
switch (c) {
|
|
84
|
-
case 33: case 35: case 36: case 37: case 38: case 39:
|
|
85
|
-
case 42: case 43: case 45: case 47: case 61: case 63:
|
|
86
|
-
case 94: case 95: case 96: case 123: case 124: case 125: case 126:
|
|
47
|
+
case 33: case 35: case 36: case 37: case 38: case 39:
|
|
48
|
+
case 42: case 43: case 45: case 47: case 61: case 63:
|
|
49
|
+
case 94: case 95: case 96: case 123: case 124: case 125: case 126:
|
|
87
50
|
return true;
|
|
88
51
|
default: return false;
|
|
89
52
|
}
|
|
90
53
|
}
|
|
91
54
|
|
|
92
|
-
// IPv4 SHAPE: one or more digit groups joined by single dots (only digits and dots, no leading/trailing/
|
|
93
|
-
// doubled dot, at least one dot). This is the "looks like an address" test, not a valid-address test --
|
|
94
|
-
// packIpLiteral decides validity; a shape match that is not a valid address is refused as ambiguous.
|
|
95
55
|
function _looksLikeIPv4Shape(s) {
|
|
96
56
|
var n = s.length, i, c, sawDot = false, atomEmpty = true;
|
|
97
57
|
if (n === 0) return false;
|
|
98
58
|
for (i = 0; i < n; i += 1) {
|
|
99
59
|
c = _charCodeAt(s, i);
|
|
100
|
-
if (c === 46) { if (atomEmpty) return false; sawDot = true; atomEmpty = true; }
|
|
60
|
+
if (c === 46) { if (atomEmpty) return false; sawDot = true; atomEmpty = true; }
|
|
101
61
|
else if (_isDigitCode(c)) { atomEmpty = false; }
|
|
102
62
|
else return false;
|
|
103
63
|
}
|
|
104
64
|
return sawDot && !atomEmpty;
|
|
105
65
|
}
|
|
106
66
|
|
|
107
|
-
// Every byte in [from,to) is an allowed component byte (per `byteOk`) or opens a "%HEXHEX" triplet.
|
|
108
67
|
function _componentBytesOk(s, from, to, byteOk) {
|
|
109
68
|
var i = from, c;
|
|
110
69
|
while (i < to) {
|
|
111
70
|
c = _charCodeAt(s, i);
|
|
112
|
-
if (c === 37) {
|
|
71
|
+
if (c === 37) {
|
|
113
72
|
if (i + 2 >= to || !_isHexCode(_charCodeAt(s, i + 1)) || !_isHexCode(_charCodeAt(s, i + 2))) return false;
|
|
114
73
|
i += 3;
|
|
115
74
|
} else if (byteOk(c)) { i += 1; }
|
|
@@ -118,143 +77,113 @@ function _componentBytesOk(s, from, to, byteOk) {
|
|
|
118
77
|
return true;
|
|
119
78
|
}
|
|
120
79
|
|
|
121
|
-
// The URI host in [from,to): RFC 5280 sec. 4.2.1.6 requires a URI SAN authority to carry a fully-qualified
|
|
122
|
-
// domain name or IP host, stricter than an RFC 3986 reg-name. So the host is either a valid IPv4 literal,
|
|
123
|
-
// or an FQDN of strict-LDH labels (the email-domain grammar: no underscore, no hyphen at a label edge) with
|
|
124
|
-
// at least one dot -- a single-label host such as "localhost" is not fully qualified and uses the object
|
|
125
|
-
// form, matching the email domain. An IPv4-SHAPED host that is not a valid address is refused as ambiguous
|
|
126
|
-
// (the same rule the top-level classifier uses). An IPv6-literal host is written in "[" "]" and validated
|
|
127
|
-
// by _validUriIp6Host from _validUriAuthority, never reaching this unbracketed-host check.
|
|
128
80
|
function _validUriHost(s, from, to) {
|
|
129
|
-
if (to <= from) return false;
|
|
81
|
+
if (to <= from) return false;
|
|
130
82
|
var host = _sliceStr(s, from, to), len = host.length;
|
|
131
|
-
if (_looksLikeIPv4Shape(host)) return _packIpLiteral(host) !== null;
|
|
132
|
-
var end = _charCodeAt(host, len - 1) === 46 ? len - 1 : len;
|
|
133
|
-
if (end > 253) return false;
|
|
83
|
+
if (_looksLikeIPv4Shape(host)) return _packIpLiteral(host) !== null;
|
|
84
|
+
var end = _charCodeAt(host, len - 1) === 46 ? len - 1 : len;
|
|
85
|
+
if (end > 253) return false;
|
|
134
86
|
var firstDot = _stringIndexOf(host, ".", 0);
|
|
135
|
-
if (firstDot === -1 || firstDot >= end) return false;
|
|
136
|
-
return _labelsValid(host, 0, len, _isHostLabelRange, true);
|
|
87
|
+
if (firstDot === -1 || firstDot >= end) return false;
|
|
88
|
+
return _labelsValid(host, 0, len, _isHostLabelRange, true);
|
|
137
89
|
}
|
|
138
90
|
|
|
139
|
-
// A bracketed IPv6-literal URI host (RFC 3986 sec. 3.2.2): the bytes between "[" and "]" must be a valid
|
|
140
|
-
// IPv6 address (16 packed octets), never an IPv4 literal (which uses the unbracketed IPv4address form). The
|
|
141
|
-
// bracket is the ONLY way to write an IPv6 host in a URI, and an IPv6 host is a valid iPAddress per RFC 5280
|
|
142
|
-
// sec. 4.2.1.6, so the shorthand accepts it rather than forcing the object form.
|
|
143
91
|
function _validUriIp6Host(s, from, to) {
|
|
144
|
-
if (to <= from) return false;
|
|
92
|
+
if (to <= from) return false;
|
|
145
93
|
var packed = _packIpLiteral(_sliceStr(s, from, to));
|
|
146
|
-
return packed !== null && intrinsic.sizeOf(packed) === 16;
|
|
94
|
+
return packed !== null && intrinsic.sizeOf(packed) === 16;
|
|
147
95
|
}
|
|
148
96
|
|
|
149
|
-
// The authority in [from,to) is "[ userinfo "@" ] host [ ":" port ]" (RFC 3986 sec. 3.2): at most one "@"
|
|
150
|
-
// (userinfo and reg-name each exclude it), a host that is an RFC 5280 FQDN or IPv4 (_validUriHost) or a
|
|
151
|
-
// bracketed IPv6 literal (_validUriIp6Host), and an all-digit -- possibly empty -- port after the host's
|
|
152
|
-
// ":". A bracketed host is taken as ONE unit before the port, so a ":" inside the IPv6 literal is not read
|
|
153
|
-
// as the port separator. The userinfo bytes are validated against the RFC 3986 userinfo class.
|
|
154
97
|
function _validUriAuthority(s, from, to) {
|
|
155
98
|
var i, c, at = -1;
|
|
156
99
|
for (i = from; i < to; i += 1) {
|
|
157
100
|
c = _charCodeAt(s, i);
|
|
158
|
-
if (c === 64) { if (at !== -1) return false; at = i; }
|
|
101
|
+
if (c === 64) { if (at !== -1) return false; at = i; }
|
|
159
102
|
}
|
|
160
103
|
var hostStart = at === -1 ? from : at + 1;
|
|
161
|
-
if (at !== -1 && !_componentBytesOk(s, from, at, _isUserinfoByte)) return false;
|
|
162
|
-
if (hostStart < to && _charCodeAt(s, hostStart) === 91) {
|
|
104
|
+
if (at !== -1 && !_componentBytesOk(s, from, at, _isUserinfoByte)) return false;
|
|
105
|
+
if (hostStart < to && _charCodeAt(s, hostStart) === 91) {
|
|
163
106
|
var close = -1;
|
|
164
|
-
for (i = hostStart + 1; i < to; i += 1) { if (_charCodeAt(s, i) === 93) { close = i; break; } }
|
|
165
|
-
if (close === -1) return false;
|
|
166
|
-
if (!_validUriIp6Host(s, hostStart + 1, close)) return false;
|
|
107
|
+
for (i = hostStart + 1; i < to; i += 1) { if (_charCodeAt(s, i) === 93) { close = i; break; } }
|
|
108
|
+
if (close === -1) return false;
|
|
109
|
+
if (!_validUriIp6Host(s, hostStart + 1, close)) return false;
|
|
167
110
|
var after = close + 1;
|
|
168
|
-
if (after === to) return true;
|
|
169
|
-
if (_charCodeAt(s, after) !== 58) return false;
|
|
170
|
-
for (i = after + 1; i < to; i += 1) { if (!_isDigitCode(_charCodeAt(s, i))) return false; }
|
|
111
|
+
if (after === to) return true;
|
|
112
|
+
if (_charCodeAt(s, after) !== 58) return false;
|
|
113
|
+
for (i = after + 1; i < to; i += 1) { if (!_isDigitCode(_charCodeAt(s, i))) return false; }
|
|
171
114
|
return true;
|
|
172
115
|
}
|
|
173
116
|
var colon = -1;
|
|
174
|
-
for (i = hostStart; i < to; i += 1) { if (_charCodeAt(s, i) === 58) { colon = i; break; } }
|
|
117
|
+
for (i = hostStart; i < to; i += 1) { if (_charCodeAt(s, i) === 58) { colon = i; break; } }
|
|
175
118
|
var hostEnd = colon === -1 ? to : colon;
|
|
176
|
-
if (!_validUriHost(s, hostStart, hostEnd)) return false;
|
|
119
|
+
if (!_validUriHost(s, hostStart, hostEnd)) return false;
|
|
177
120
|
if (colon !== -1) {
|
|
178
|
-
for (i = colon + 1; i < to; i += 1) { if (!_isDigitCode(_charCodeAt(s, i))) return false; }
|
|
121
|
+
for (i = colon + 1; i < to; i += 1) { if (!_isDigitCode(_charCodeAt(s, i))) return false; }
|
|
179
122
|
}
|
|
180
123
|
return true;
|
|
181
124
|
}
|
|
182
125
|
|
|
183
|
-
// scheme "://" authority [ path ] [ "?" query ] [ "#" fragment ], parsed by RFC 3986 component. The
|
|
184
|
-
// authority is validated by _validUriAuthority; the remainder is path/query/fragment bytes with at most
|
|
185
|
-
// one "#" (its single fragment delimiter). A structurally malformed URL is refused; the object form is
|
|
186
|
-
// the escape for one the shorthand does not accept.
|
|
187
126
|
function _looksLikeUri(s) {
|
|
188
127
|
var n = s.length, i, c;
|
|
189
|
-
if (n === 0 || !_isAlphaCode(_charCodeAt(s, 0))) return false;
|
|
128
|
+
if (n === 0 || !_isAlphaCode(_charCodeAt(s, 0))) return false;
|
|
190
129
|
i = 1;
|
|
191
130
|
while (i < n) {
|
|
192
131
|
c = _charCodeAt(s, i);
|
|
193
|
-
if (_isAlphaCode(c) || _isDigitCode(c) || c === 43 || c === 45 || c === 46) i += 1;
|
|
132
|
+
if (_isAlphaCode(c) || _isDigitCode(c) || c === 43 || c === 45 || c === 46) i += 1;
|
|
194
133
|
else break;
|
|
195
134
|
}
|
|
196
135
|
if (i + 3 > n || _charCodeAt(s, i) !== 58 || _charCodeAt(s, i + 1) !== 47 || _charCodeAt(s, i + 2) !== 47) return false;
|
|
197
136
|
i += 3;
|
|
198
137
|
var authStart = i, authEnd = n;
|
|
199
138
|
for (; i < n; i += 1) { c = _charCodeAt(s, i); if (c === 47 || c === 63 || c === 35) { authEnd = i; break; } }
|
|
200
|
-
if (authEnd === authStart) return false;
|
|
139
|
+
if (authEnd === authStart) return false;
|
|
201
140
|
if (!_validUriAuthority(s, authStart, authEnd)) return false;
|
|
202
|
-
var sawHash = false;
|
|
141
|
+
var sawHash = false;
|
|
203
142
|
i = authEnd;
|
|
204
143
|
while (i < n) {
|
|
205
144
|
c = _charCodeAt(s, i);
|
|
206
|
-
if (c === 37) {
|
|
145
|
+
if (c === 37) {
|
|
207
146
|
if (i + 2 >= n || !_isHexCode(_charCodeAt(s, i + 1)) || !_isHexCode(_charCodeAt(s, i + 2))) return false;
|
|
208
147
|
i += 3;
|
|
209
148
|
continue;
|
|
210
149
|
}
|
|
211
|
-
if (c === 35) { if (sawHash) return false; sawHash = true; }
|
|
150
|
+
if (c === 35) { if (sawHash) return false; sawHash = true; }
|
|
212
151
|
if (!_isUriByte(c)) return false;
|
|
213
152
|
i += 1;
|
|
214
153
|
}
|
|
215
154
|
return true;
|
|
216
155
|
}
|
|
217
156
|
|
|
218
|
-
// A single dNSName label over [from,to): 1-63 bytes, LDH plus a LEADING or EMBEDDED underscore, no hyphen
|
|
219
|
-
// at either edge, and no underscore at the trailing edge. The underscore tolerance is deliberate and
|
|
220
|
-
// leading/embedded only: the real forms are _acme-challenge, _dmarc, _25._tcp (a label-initial "_"); a
|
|
221
|
-
// label ending in "_" has no such use. An email DOMAIN is stricter still -- see _isHostLabelRange.
|
|
222
157
|
function _isDnsLabelRange(s, from, to) {
|
|
223
158
|
var len = to - from, i, c;
|
|
224
159
|
if (len < 1 || len > 63) return false;
|
|
225
160
|
for (i = from; i < to; i += 1) {
|
|
226
161
|
c = _charCodeAt(s, i);
|
|
227
|
-
if (_isAlphaCode(c) || _isDigitCode(c)) continue;
|
|
228
|
-
if (i === to - 1) return false;
|
|
229
|
-
if (c === 95) continue;
|
|
230
|
-
if (c === 45 && i !== from) continue;
|
|
162
|
+
if (_isAlphaCode(c) || _isDigitCode(c)) continue;
|
|
163
|
+
if (i === to - 1) return false;
|
|
164
|
+
if (c === 95) continue;
|
|
165
|
+
if (c === 45 && i !== from) continue;
|
|
231
166
|
return false;
|
|
232
167
|
}
|
|
233
168
|
return true;
|
|
234
169
|
}
|
|
235
170
|
|
|
236
|
-
// A single RFC 5321 sec. 2.3.5 hostname label over [from,to): the strict letter-digit-hyphen grammar with
|
|
237
|
-
// no hyphen at either edge and NO underscore. This is the email-domain label rule (a mail domain is a real
|
|
238
|
-
// hostname), stricter than the underscore-tolerant dNSName label above.
|
|
239
171
|
function _isHostLabelRange(s, from, to) {
|
|
240
172
|
var len = to - from, i, c, edge;
|
|
241
173
|
if (len < 1 || len > 63) return false;
|
|
242
174
|
for (i = from; i < to; i += 1) {
|
|
243
175
|
c = _charCodeAt(s, i);
|
|
244
176
|
edge = (i === from || i === to - 1);
|
|
245
|
-
if (_isAlphaCode(c) || _isDigitCode(c)) continue;
|
|
246
|
-
if (c === 45 && !edge) continue;
|
|
177
|
+
if (_isAlphaCode(c) || _isDigitCode(c)) continue;
|
|
178
|
+
if (c === 45 && !edge) continue;
|
|
247
179
|
return false;
|
|
248
180
|
}
|
|
249
181
|
return true;
|
|
250
182
|
}
|
|
251
183
|
|
|
252
|
-
// Every dot-separated label in [from,to) satisfies `labelFn`. Scans with indexOf; no split allocation. When
|
|
253
|
-
// `allowRootDot` is set, a single trailing "." (the absolute-FQDN root) is stripped before validating, so
|
|
254
|
-
// "example.com." checks as "example.com"; an interior empty label (a doubled or leading dot) still fails.
|
|
255
184
|
function _labelsValid(s, from, to, labelFn, allowRootDot) {
|
|
256
|
-
if (allowRootDot && to - from >= 1 && _charCodeAt(s, to - 1) === 46) to -= 1;
|
|
257
|
-
if (to <= from) return false;
|
|
185
|
+
if (allowRootDot && to - from >= 1 && _charCodeAt(s, to - 1) === 46) to -= 1;
|
|
186
|
+
if (to <= from) return false;
|
|
258
187
|
var start = from, dot;
|
|
259
188
|
for (;;) {
|
|
260
189
|
dot = _stringIndexOf(s, ".", start);
|
|
@@ -265,56 +194,47 @@ function _labelsValid(s, from, to, labelFn, allowRootDot) {
|
|
|
265
194
|
}
|
|
266
195
|
}
|
|
267
196
|
|
|
268
|
-
// A dNSName: at most 253 bytes, an optional single leading "*." wildcard, then dot-separated dNSName labels
|
|
269
|
-
// (underscore-tolerant), with an optional trailing absolute-root ".".
|
|
270
197
|
function _looksLikeDnsName(s) {
|
|
271
198
|
var n = s.length, start = 0;
|
|
272
199
|
if (n < 1) return false;
|
|
273
|
-
var end = _charCodeAt(s, n - 1) === 46 ? n - 1 : n;
|
|
274
|
-
if (end < 1 || end > 253) return false;
|
|
275
|
-
if (end >= 2 && _charCodeAt(s, 0) === 42 && _charCodeAt(s, 1) === 46) start = 2;
|
|
200
|
+
var end = _charCodeAt(s, n - 1) === 46 ? n - 1 : n;
|
|
201
|
+
if (end < 1 || end > 253) return false;
|
|
202
|
+
if (end >= 2 && _charCodeAt(s, 0) === 42 && _charCodeAt(s, 1) === 46) start = 2;
|
|
276
203
|
if (start >= end) return false;
|
|
277
204
|
return _labelsValid(s, start, n, _isDnsLabelRange, true);
|
|
278
205
|
}
|
|
279
206
|
|
|
280
|
-
// An rfc822Name: an unquoted RFC 5321 dot-atom local part (atext runs joined by single dots), one "@",
|
|
281
|
-
// and a dotted hostname domain (at least one dot -- an FQDN, no wildcard, strict LDH labels, no underscore).
|
|
282
207
|
function _looksLikeEmail(s) {
|
|
283
|
-
if (s.length > 254) return false;
|
|
208
|
+
if (s.length > 254) return false;
|
|
284
209
|
var at = _stringIndexOf(s, "@", 0);
|
|
285
|
-
if (at < 1 || at > 64 || _stringLastIndexOf(s, "@") !== at) return false;
|
|
210
|
+
if (at < 1 || at > 64 || _stringLastIndexOf(s, "@") !== at) return false;
|
|
286
211
|
var i, c, atomEmpty = true;
|
|
287
|
-
for (i = 0; i < at; i += 1) {
|
|
212
|
+
for (i = 0; i < at; i += 1) {
|
|
288
213
|
c = _charCodeAt(s, i);
|
|
289
|
-
if (c === 46) { if (atomEmpty) return false; atomEmpty = true; }
|
|
214
|
+
if (c === 46) { if (atomEmpty) return false; atomEmpty = true; }
|
|
290
215
|
else if (_isAtextByte(c)) { atomEmpty = false; }
|
|
291
216
|
else return false;
|
|
292
217
|
}
|
|
293
|
-
if (atomEmpty) return false;
|
|
218
|
+
if (atomEmpty) return false;
|
|
294
219
|
var dStart = at + 1, dLen = s.length - dStart;
|
|
295
|
-
if (dLen < 1 || dLen > 253 || _stringIndexOf(s, ".", dStart) === -1) return false;
|
|
296
|
-
return _labelsValid(s, dStart, s.length, _isHostLabelRange, false);
|
|
220
|
+
if (dLen < 1 || dLen > 253 || _stringIndexOf(s, ".", dStart) === -1) return false;
|
|
221
|
+
return _labelsValid(s, dStart, s.length, _isHostLabelRange, false);
|
|
297
222
|
}
|
|
298
223
|
|
|
299
|
-
// A well-formed dotted-decimal OID SHAPE (no regex, by the same character scan the name forms use): a first
|
|
300
|
-
// arc 0-2, then one or more arcs each introduced by "." and written without a leading zero ("0" alone, or a
|
|
301
|
-
// non-zero digit followed by more digits). Used to accept a raw OID string (an unregistered KeyPurposeId /
|
|
302
|
-
// policy OID) where a registered name would go; the encoder (encodeOidContent) remains the authoritative
|
|
303
|
-
// validator of the arc-value bounds.
|
|
304
224
|
function _isDottedOid(s) {
|
|
305
225
|
var n = s.length, i, c, d0, arcs = 0;
|
|
306
|
-
if (n < 3) return false;
|
|
226
|
+
if (n < 3) return false;
|
|
307
227
|
c = _charCodeAt(s, 0);
|
|
308
|
-
if (c < 48 || c > 50) return false;
|
|
228
|
+
if (c < 48 || c > 50) return false;
|
|
309
229
|
i = 1;
|
|
310
230
|
while (i < n) {
|
|
311
|
-
if (_charCodeAt(s, i) !== 46) return false;
|
|
231
|
+
if (_charCodeAt(s, i) !== 46) return false;
|
|
312
232
|
i += 1;
|
|
313
|
-
if (i >= n) return false;
|
|
233
|
+
if (i >= n) return false;
|
|
314
234
|
d0 = _charCodeAt(s, i);
|
|
315
|
-
if (d0 === 48) { i += 1; }
|
|
235
|
+
if (d0 === 48) { i += 1; }
|
|
316
236
|
else if (d0 >= 49 && d0 <= 57) { i += 1; while (i < n && _isDigitCode(_charCodeAt(s, i))) i += 1; }
|
|
317
|
-
else { return false; }
|
|
237
|
+
else { return false; }
|
|
318
238
|
arcs += 1;
|
|
319
239
|
}
|
|
320
240
|
return arcs >= 1;
|
|
@@ -322,63 +242,37 @@ function _isDottedOid(s) {
|
|
|
322
242
|
|
|
323
243
|
var b = asn1.build;
|
|
324
244
|
|
|
325
|
-
// KeyUsage named-bit positions (RFC 5280 sec. 4.2.1.3); contentCommitment is the RFC 5280 rename of the
|
|
326
|
-
// X.509 nonRepudiation bit (1).
|
|
327
245
|
var KU_BIT = {
|
|
328
246
|
digitalSignature: 0, nonRepudiation: 1, contentCommitment: 1, keyEncipherment: 2,
|
|
329
247
|
dataEncipherment: 3, keyAgreement: 4, keyCertSign: 5, cRLSign: 6, encipherOnly: 7, decipherOnly: 8,
|
|
330
248
|
};
|
|
331
249
|
|
|
332
|
-
// A caller-supplied array a builder is about to map over: reject a sparse hole or a nullish entry as a
|
|
333
|
-
// typed error. Array.prototype.map SKIPS a hole, which would otherwise reach Buffer.concat as a native
|
|
334
|
-
// ERR_INVALID_ARG_TYPE and, on a huge sparse length, traverse the empty slots. Tests OWN-property presence
|
|
335
|
-
// (intrinsic.hasOwn) at each index BEFORE reading it, so a polluted Array.prototype[i] cannot slip a hole
|
|
336
|
-
// past the check, and fails fast at the first bad index so the length alone can never force a walk. Takes
|
|
337
|
-
// the caller's error factory E (invoked E(fullCode, msg)) so a builder WITHOUT a makeBuilder instance
|
|
338
|
-
// routes its caller arrays through the same choke point; makeBuilder's own reqDenseArray delegates here.
|
|
339
250
|
function reqDenseArrayImpl(list, what, E, code) {
|
|
340
251
|
if (!_isArray(list)) throw E(code, what + " must be an array");
|
|
341
252
|
var n = list.length;
|
|
342
|
-
// Snapshot each own index into a FRESH dense array and return THAT, never `list`. Reading list[i] can
|
|
343
|
-
// fire a caller-defined indexed accessor that mutates `list` (extending it with holes past the captured
|
|
344
|
-
// n, or deleting an already-checked index); returning `list` would then hand the caller's map a
|
|
345
|
-
// newly-sparse array and the native error this guard exists to prevent. The copy captures the value as
|
|
346
|
-
// read, so a later mutation of `list` cannot reintroduce a hole into what the caller iterates.
|
|
347
253
|
var out = [];
|
|
348
254
|
for (var i = 0; i < n; i++) {
|
|
349
255
|
if (!intrinsic.hasOwn(list, i)) throw E(code, what + "[" + i + "] is missing (a sparse array is not allowed)");
|
|
350
256
|
var v = list[i];
|
|
351
257
|
if (v === undefined || v === null) throw E(code, what + "[" + i + "] is missing (a nullish array entry is not allowed)");
|
|
352
|
-
// defineProperty, not `out[i] = v`: a plain assignment invokes an inherited Array.prototype[i] SETTER
|
|
353
|
-
// ([[Set]]) if one is polluted in, which could leave `out` sparse; [[DefineOwnProperty]] always makes
|
|
354
|
-
// an own data property, so the snapshot is dense regardless of prototype pollution.
|
|
355
258
|
intrinsic.defineProperty(out, i, { value: v, writable: true, enumerable: true, configurable: true });
|
|
356
259
|
}
|
|
357
260
|
return out;
|
|
358
261
|
}
|
|
359
262
|
|
|
360
|
-
// makeBuilder(ctx) -> the bound producing primitives. ctx = { ErrorClass, prefix, O, NS, NAME_SCHEMA,
|
|
361
|
-
// SPKI_SCHEMA, EXT_DECODERS }. `E(kind, msg, cause)` builds a `<prefix>/<kind>` typed error.
|
|
362
263
|
function makeBuilder(ctx) {
|
|
363
264
|
var ErrorClass = ctx.ErrorClass, O = ctx.O, NS = ctx.NS;
|
|
364
265
|
var NAME_SCHEMA = ctx.NAME_SCHEMA, SPKI_SCHEMA = ctx.SPKI_SCHEMA;
|
|
365
266
|
function E(kind, message, cause) { return new ErrorClass(ctx.prefix + "/" + kind, message, cause); }
|
|
366
267
|
function code(kind) { return ctx.prefix + "/" + kind; }
|
|
367
|
-
// A full-code error factory (guard.* helpers invoke E as `E(fullCode, msg)`, not `E(kind, msg)`).
|
|
368
268
|
function rawErr(fullCode, message, cause) { return new ErrorClass(fullCode, message, cause); }
|
|
369
269
|
|
|
370
|
-
// RFC 5280 sec. 4.1.2.5 -- UTCTime for 1950..2049, GeneralizedTime otherwise (UTCTime's two-digit year
|
|
371
|
-
// only represents that window). The shared cutover so a certificate / CRMF / any validity encoder cannot
|
|
372
|
-
// drift on the boundary. `which` labels the instant in a validation error.
|
|
373
270
|
function timeDer(date, which) {
|
|
374
271
|
guard.time.assertValid(date, rawErr, code("bad-input"), which);
|
|
375
272
|
var y = date.getUTCFullYear();
|
|
376
273
|
return (y >= 1950 && y <= 2049) ? b.utcTime(date) : b.generalizedTime(date);
|
|
377
274
|
}
|
|
378
275
|
|
|
379
|
-
// ---- distinguished name encoding (RFC 5280 sec. 4.1.2.4) ----
|
|
380
|
-
// countryName is a PrintableString SIZE(2), emailAddress an IA5String; every other new-name attribute
|
|
381
|
-
// is a UTF8String (Teletex/BMP/Universal are backward-compat only and never emitted).
|
|
382
276
|
function atvString(attrName, value) {
|
|
383
277
|
if (attrName === "countryName") {
|
|
384
278
|
if (String(value).length !== 2) throw E("bad-name", "countryName must be a two-letter ISO 3166 code (PrintableString SIZE(2))");
|
|
@@ -389,7 +283,6 @@ function makeBuilder(ctx) {
|
|
|
389
283
|
}
|
|
390
284
|
function encodeAtv(attrName, value) {
|
|
391
285
|
if (value == null || value === "") throw E("bad-name", "the " + attrName + " attribute value must be a non-empty string");
|
|
392
|
-
// For an unrecognized name oid.byName returns undefined and does not throw, so reject explicitly.
|
|
393
286
|
var typeOid = O(attrName);
|
|
394
287
|
if (typeOid == null) throw E("bad-name", "unknown distinguished-name attribute " + JSON.stringify(attrName));
|
|
395
288
|
var valueTlv;
|
|
@@ -403,15 +296,12 @@ function makeBuilder(ctx) {
|
|
|
403
296
|
if (!keys.length) throw E("bad-name", "an RDN must carry at least one attribute");
|
|
404
297
|
return b.set(keys.map(function (k) { return encodeAtv(k, rdnSpec[k]); }));
|
|
405
298
|
}
|
|
406
|
-
// A DN spec -> RDNSequence DER. A string is shorthand for a single commonName RDN; a Buffer is raw
|
|
407
|
-
// pre-encoded Name DER (validated through the parser). An empty array yields an empty RDNSequence.
|
|
408
299
|
function encodeName(spec) {
|
|
409
300
|
if (guard.bytes.isByteSource(spec)) { var _nd = guard.bytes.snapshotSource(spec, ErrorClass, ctx.prefix + "/bad-name", "raw Name DER"); assertValidNameDer(_nd); return _nd; }
|
|
410
301
|
if (typeof spec === "string") spec = [{ commonName: spec }];
|
|
411
302
|
if (!_isArray(spec)) throw E("bad-name", "a name must be a string, an array of RDNs, or raw Name DER");
|
|
412
303
|
return b.sequence(spec.map(encodeRdn));
|
|
413
304
|
}
|
|
414
|
-
// Full validation of a raw Name DER: walk it through the exact RDNSequence parser the decoder uses.
|
|
415
305
|
function assertValidNameDer(der) {
|
|
416
306
|
var node;
|
|
417
307
|
try { node = asn1.decode(der); }
|
|
@@ -424,31 +314,15 @@ function makeBuilder(ctx) {
|
|
|
424
314
|
}
|
|
425
315
|
function isEmptyName(nameDer) { return asn1.decode(nameDer).children.length === 0; }
|
|
426
316
|
|
|
427
|
-
// ---- GeneralName encoding (RFC 5280 sec. 4.2.1.6) ----
|
|
428
317
|
function ia5Content(s) {
|
|
429
|
-
// Captured String / charCodeAt / bufferFrom: this converts a GeneralName value (a classified bare
|
|
430
|
-
// string or an object-form value) to the emitted IA5String bytes, so co-resident code replacing any of
|
|
431
|
-
// them after load could otherwise change which host the certificate names from the one that was checked.
|
|
432
318
|
s = _String(s);
|
|
433
319
|
for (var i = 0; i < s.length; i++) {
|
|
434
320
|
if (_charCodeAt(s, i) > 0x7F) throw E("bad-input", "value requires 7-bit ASCII (IA5String): " + _stringify(s));
|
|
435
321
|
}
|
|
436
322
|
return _bufferFrom(s, "latin1");
|
|
437
323
|
}
|
|
438
|
-
// A bare-string GeneralName shorthand: classify a string into exactly one form, FAIL-CLOSED. RFC 5280
|
|
439
|
-
// sec. 4.2.1.6 GeneralName is a CHOICE, so an input that does not UNAMBIGUOUSLY match one form throws
|
|
440
|
-
// rather than being guessed -- the explicit object form ({ dNSName: s } etc.) is always the escape. The
|
|
441
|
-
// per-form syntax is decided by the character-scan predicates above (no regex). Precedence resolves the
|
|
442
|
-
// overlaps of the later tests: an IP literal is also LDH-with-dots (so it is tested first), and a
|
|
443
|
-
// scheme://authority URL can carry userinfo that reads like an email (so URI is tested before rfc822Name).
|
|
444
|
-
// Form detection only -- IA5String (7-bit) enforcement stays at ia5Content, so a non-ASCII value still
|
|
445
|
-
// throws at encode.
|
|
446
324
|
function _classifyBareGeneralName(s) {
|
|
447
325
|
if (_packIpLiteral(s) !== null) return { iPAddress: s };
|
|
448
|
-
// A string of only digit groups separated by dots is an IPv4 ATTEMPT; if packIpLiteral rejected it,
|
|
449
|
-
// it is a MALFORMED address, not a dNSName. It reads two ways (an invalid IP and a syntactically
|
|
450
|
-
// valid all-numeric name), so it is ambiguous and refused rather than silently encoded as a name the
|
|
451
|
-
// caller did not intend -- a genuine all-numeric name uses the explicit { dNSName: ... } form.
|
|
452
326
|
if (_looksLikeIPv4Shape(s)) {
|
|
453
327
|
throw E("bad-input", "the bare GeneralName string " + _stringify(s) + " looks like an IPv4 address but is not a valid one; pass { iPAddress: ... } for an address or { dNSName: ... } for a name");
|
|
454
328
|
}
|
|
@@ -460,16 +334,11 @@ function makeBuilder(ctx) {
|
|
|
460
334
|
" as a dNSName, rfc822Name, iPAddress, or URI; pass an explicit form object, e.g. { dNSName: " + _qs + " }");
|
|
461
335
|
}
|
|
462
336
|
function encodeGeneralName(entry) {
|
|
463
|
-
// A bare non-empty string is shorthand -- normalized to its single-key object form, fail-closed,
|
|
464
|
-
// before the object checks below; an empty string is refused here (never classified).
|
|
465
337
|
if (typeof entry === "string") {
|
|
466
338
|
if (entry === "") throw E("bad-input", "an empty GeneralName value is not permitted (RFC 5280 sec. 4.2.1.6)");
|
|
467
339
|
entry = _classifyBareGeneralName(entry);
|
|
468
340
|
}
|
|
469
341
|
if (!entry || typeof entry !== "object" || _isBufferChk(entry)) throw E("bad-input", "a GeneralName must be an object with exactly one name form");
|
|
470
|
-
// Captured Object.keys / property read: this reads which form and value the certificate will emit for a
|
|
471
|
-
// GeneralName (a freshly classified bare string, or a caller object), so a replaced Object.keys must not
|
|
472
|
-
// be able to relabel the classified result into a different, attacker-chosen form after validation.
|
|
473
342
|
var keys = _objectKeys(entry);
|
|
474
343
|
if (keys.length !== 1) throw E("bad-input", "a GeneralName entry must have exactly one form, got " + keys.length);
|
|
475
344
|
var k = keys[0], v = entry[k];
|
|
@@ -479,9 +348,6 @@ function makeBuilder(ctx) {
|
|
|
479
348
|
case "dNSName": return b.contextPrimitive(2, ia5Content(v));
|
|
480
349
|
case "uniformResourceIdentifier": case "uri": return b.contextPrimitive(6, ia5Content(v));
|
|
481
350
|
case "iPAddress":
|
|
482
|
-
// Accept a dotted-quad / colon-hex string (packed to its 4/16 network octets) as well as a
|
|
483
|
-
// pre-packed Buffer, so an iPAddress SAN reads like dNSName/URI and the caller does not pack
|
|
484
|
-
// octets by hand (a GeneralName iPAddress is a bare host address, RFC 5280 sec. 4.2.1.6).
|
|
485
351
|
var ipBuf = v;
|
|
486
352
|
if (typeof v === "string") {
|
|
487
353
|
ipBuf = _packIpLiteral(v);
|
|
@@ -489,29 +355,14 @@ function makeBuilder(ctx) {
|
|
|
489
355
|
}
|
|
490
356
|
if (!_isBufferChk(ipBuf) || (ipBuf.length !== 4 && ipBuf.length !== 16)) throw E("bad-input", "iPAddress must be a 4- or 16-octet Buffer or an IPv4/IPv6 string");
|
|
491
357
|
return b.contextPrimitive(7, ipBuf);
|
|
492
|
-
case "directoryName": return b.explicit(4, encodeName(v));
|
|
358
|
+
case "directoryName": return b.explicit(4, encodeName(v));
|
|
493
359
|
case "otherName":
|
|
494
|
-
// otherName ::= SEQUENCE { type-id OBJECT IDENTIFIER, value [0] EXPLICIT ANY }, tagged
|
|
495
|
-
// [0] IMPLICIT (RFC 5280 sec. 4.2.1.6). The value is EXPLICIT because ANY carries no tag
|
|
496
|
-
// of its own, so the wrapper is what makes the encoding unambiguous -- the same shape the
|
|
497
|
-
// decoder requires, and without it an SmtpUTF8Mailbox (RFC 8398 sec. 3) could be read but
|
|
498
|
-
// never written.
|
|
499
360
|
if (typeof v !== "object" || Buffer.isBuffer(v)) throw E("bad-input", "otherName must be an object { typeId, value }");
|
|
500
361
|
if (typeof v.typeId !== "string" || !v.typeId) throw E("bad-input", "otherName requires a `typeId` OID string");
|
|
501
362
|
if (!Buffer.isBuffer(v.value) || v.value.length === 0) {
|
|
502
363
|
throw E("bad-input", "otherName requires a `value` Buffer holding one DER element");
|
|
503
364
|
}
|
|
504
|
-
// The value is spliced in raw and this verb SIGNS the result, so it is strictly
|
|
505
|
-
// validated rather than taken on the caller's word. Framing alone is not DER: a
|
|
506
|
-
// BOOLEAN whose content octet is 0x01, a NumericString holding "@", or a SET in no
|
|
507
|
-
// canonical order all frame cleanly and would ship inside a [0] EXPLICIT wrapper
|
|
508
|
-
// under a real signature. guard.der.tlv is the one place that rule lives.
|
|
509
365
|
guard.der.tlv(v.value, E, "bad-input", "otherName `value`");
|
|
510
|
-
// [0] IMPLICIT on a SEQUENCE replaces the SEQUENCE tag, so the content is the two
|
|
511
|
-
// members concatenated -- build.contextConstructed takes content bytes, not elements.
|
|
512
|
-
// The type-id encode is wrapped so a malformed OID reports in the CALLER's namespace
|
|
513
|
-
// (x509/bad-input, csr/bad-input, ...) like every other raw-OID path here, rather than
|
|
514
|
-
// surfacing the codec's own oid/* code from a shared builder the caller never named.
|
|
515
366
|
var typeIdDer;
|
|
516
367
|
try { typeIdDer = b.oid(v.typeId); }
|
|
517
368
|
catch (e) { throw E("bad-input", "invalid otherName type-id OID " + JSON.stringify(v.typeId) + " (violates the X.660 arc bounds)", e); }
|
|
@@ -520,7 +371,6 @@ function makeBuilder(ctx) {
|
|
|
520
371
|
}
|
|
521
372
|
}
|
|
522
373
|
|
|
523
|
-
// ---- extension-value encoders (the inverse of certExtensionDecoders) ----
|
|
524
374
|
function extKeyUsage(names) {
|
|
525
375
|
if (!_isArray(names) || !names.length) throw E("bad-input", "keyUsage must assert at least one bit (RFC 5280 sec. 4.2.1.3)");
|
|
526
376
|
var positions = names.map(function (n) {
|
|
@@ -530,17 +380,10 @@ function makeBuilder(ctx) {
|
|
|
530
380
|
});
|
|
531
381
|
return b.namedBitString(positions);
|
|
532
382
|
}
|
|
533
|
-
// Resolve an OID token to a dotted-decimal OID: a registered name via the registry, OR a raw dotted-OID
|
|
534
|
-
// string accepted directly (an unregistered KeyPurposeId / policy OID for BIMI, document-signing or a
|
|
535
|
-
// vendor purpose is a common valid input). A token that is neither fails closed (never silently accept a
|
|
536
|
-
// typo'd name); a dotted string that passes the shape check is still arc-validated by b.oid's encoder.
|
|
537
383
|
function _resolveOid(n, label) {
|
|
538
384
|
var dotted = O(n);
|
|
539
385
|
if (dotted != null) return dotted;
|
|
540
386
|
if (typeof n === "string" && _isDottedOid(n)) {
|
|
541
|
-
// The shape scan fixes the general form; b.oid's encoder is the authoritative X.660 arc-bounds check
|
|
542
|
-
// (a first arc 0/1 caps the second at 39; an arc must DER-encode). A failure there is this producer's
|
|
543
|
-
// bad-input, not a leaked oid/* code, so every shared-builder consumer keeps its own error contract.
|
|
544
387
|
try { b.oid(n); }
|
|
545
388
|
catch (e) { throw E("bad-input", "invalid " + label + " OID " + JSON.stringify(n) + " (violates the X.660 arc bounds)", e); }
|
|
546
389
|
return n;
|
|
@@ -559,7 +402,7 @@ function makeBuilder(ctx) {
|
|
|
559
402
|
}
|
|
560
403
|
function extBasicConstraints(spec) {
|
|
561
404
|
var children = [];
|
|
562
|
-
if (spec.cA === true) children.push(b.boolean(true));
|
|
405
|
+
if (spec.cA === true) children.push(b.boolean(true));
|
|
563
406
|
if (spec.pathLen != null) children.push(b.integer(pathLen(spec.pathLen)));
|
|
564
407
|
return b.sequence(children);
|
|
565
408
|
}
|
|
@@ -568,12 +411,7 @@ function makeBuilder(ctx) {
|
|
|
568
411
|
return BigInt(v);
|
|
569
412
|
}
|
|
570
413
|
function extSki(keyid) { return b.octetString(keyid); }
|
|
571
|
-
function extAki(keyid) { return b.sequence([b.contextPrimitive(0, keyid)]); }
|
|
572
|
-
// GeneralNames ::= SEQUENCE SIZE(1..MAX) OF GeneralName. With no implicitTag it is a universal SEQUENCE
|
|
573
|
-
// (subjectAltName / issuerAltName / IssuerSerial.issuer). With an implicitTag it is a context-constructed
|
|
574
|
-
// [n] whose tag REPLACES the SEQUENCE tag (RFC 5755 IMPLICIT TAGS: Holder.entityName [1],
|
|
575
|
-
// RoleSyntax.roleAuthority [0], IetfAttrSyntax.policyAuthority [0]); the [n] node's children ARE the
|
|
576
|
-
// GeneralName members, no inner SEQUENCE wrapper.
|
|
414
|
+
function extAki(keyid) { return b.sequence([b.contextPrimitive(0, keyid)]); }
|
|
577
415
|
function encodeGeneralNames(entries, implicitTag) {
|
|
578
416
|
if (!_isArray(entries) || !entries.length) throw E("bad-input", "a GeneralNames must carry at least one GeneralName");
|
|
579
417
|
var members = entries.map(encodeGeneralName);
|
|
@@ -591,7 +429,6 @@ function makeBuilder(ctx) {
|
|
|
591
429
|
return b.sequence([b.oid(pOid)]);
|
|
592
430
|
}));
|
|
593
431
|
}
|
|
594
|
-
// Wrap a value in Extension ::= SEQUENCE { extnID, critical?, extnValue }; a FALSE critical is omitted.
|
|
595
432
|
function ext(oidStr, critical, valueDer) {
|
|
596
433
|
var children = [b.oid(oidStr)];
|
|
597
434
|
if (critical) children.push(b.boolean(true));
|
|
@@ -599,15 +436,8 @@ function makeBuilder(ctx) {
|
|
|
599
436
|
return b.sequence(children);
|
|
600
437
|
}
|
|
601
438
|
|
|
602
|
-
// The SHA-1 subjectKeyIdentifier (RFC 5280 sec. 4.2.1.2 method 1): SHA-1 of the subjectPublicKey BIT
|
|
603
|
-
// STRING content, taken past the unused-bits octet. Hashing the whole SPKI or the BIT STRING TLV
|
|
604
|
-
// gives a different, wrong identifier.
|
|
605
439
|
function spkiKeyId(spkiDer) {
|
|
606
440
|
var keyBytes = asn1.read.bitString(asn1.decode(spkiDer).children[1]).bytes;
|
|
607
|
-
// RFC 5280 sec. 4.2.1.2 method 1 DEFINES the subjectKeyIdentifier as the SHA-1 of the
|
|
608
|
-
// subjectPublicKey; this is a key identifier, not a signature or a collision-resistance use,
|
|
609
|
-
// and the algorithm is fixed by the standard.
|
|
610
|
-
// nosemgrep: pki-weak-hash-md5-sha1
|
|
611
441
|
return nodeCrypto.createHash("sha1").update(keyBytes).digest();
|
|
612
442
|
}
|
|
613
443
|
function skiKeyId(val, spkiDer) {
|
|
@@ -616,27 +446,16 @@ function makeBuilder(ctx) {
|
|
|
616
446
|
throw E("bad-input", "subjectKeyIdentifier must be true (auto-derive) or a BufferSource key id");
|
|
617
447
|
}
|
|
618
448
|
|
|
619
|
-
// ---- embedded-input validators ----
|
|
620
449
|
function reqDer(v, what) {
|
|
621
450
|
if (guard.bytes.isByteSource(v)) return guard.bytes.snapshotSource(v, ErrorClass, ctx.prefix + "/bad-input", what);
|
|
622
451
|
throw E("bad-input", what + " must be a DER Buffer");
|
|
623
452
|
}
|
|
624
|
-
// The bound 2-arg form every makeBuilder consumer calls (`_b.reqDenseArray(list, what)`); it delegates
|
|
625
|
-
// to the standalone guard above with this builder's namespace, so the density rule lives in ONE place.
|
|
626
453
|
function reqDenseArray(list, what) { return reqDenseArrayImpl(list, what, rawErr, ctx.prefix + "/bad-input"); }
|
|
627
|
-
// A caller-supplied DER a builder validates with a PEM-accepting parser and then embeds verbatim with
|
|
628
|
-
// b.raw. Those parsers unwrap PEM from a byte source, so a PEM-armored input (a Buffer straight from
|
|
629
|
-
// fs.readFileSync) passes the parse, but b.raw embeds the raw ASCII armor -- which the enclosing
|
|
630
|
-
// structure's own parse rejects only later, after protection is computed, and with the wrong code.
|
|
631
|
-
// Require a DER SEQUENCE first (a Certificate / CRL / CSR / PKIMessage / CertReqMessages is one; PEM
|
|
632
|
-
// opens with 0x2D), so a PEM or non-DER input is a typed bad-input up front and the bytes the parse
|
|
633
|
-
// checks are the bytes embedded.
|
|
634
454
|
function reqDerSequence(v, what) {
|
|
635
455
|
var der = reqDer(v, what);
|
|
636
456
|
if (der.length === 0 || der[0] !== 0x30) throw E("bad-input", what + " must be DER (a SEQUENCE), not PEM or other bytes");
|
|
637
457
|
return der;
|
|
638
458
|
}
|
|
639
|
-
// Full validation of an embedded SubjectPublicKeyInfo, walked through the parser the decoder uses.
|
|
640
459
|
function assertValidSpki(spkiDer, what) {
|
|
641
460
|
var node;
|
|
642
461
|
try { node = asn1.decode(spkiDer); }
|
|
@@ -647,8 +466,6 @@ function makeBuilder(ctx) {
|
|
|
647
466
|
throw E("bad-input", what + " is not a well-formed SubjectPublicKeyInfo", e);
|
|
648
467
|
}
|
|
649
468
|
}
|
|
650
|
-
// A pre-encoded Extension ::= SEQUENCE { extnID OID, critical BOOLEAN OPTIONAL, extnValue OCTET STRING };
|
|
651
|
-
// an explicit critical=FALSE is non-canonical (DER DEFAULT) and rejected.
|
|
652
469
|
function assertValidExtension(der, idx) {
|
|
653
470
|
var n;
|
|
654
471
|
try { n = asn1.decode(der); }
|
|
@@ -665,11 +482,6 @@ function makeBuilder(ctx) {
|
|
|
665
482
|
var last = n.children[n.children.length - 1];
|
|
666
483
|
if (last.tagNumber !== asn1.TAGS.OCTET_STRING || last.tagClass !== "universal") throw E("bad-input", "pre-encoded extension [" + idx + "] extnValue must be an OCTET STRING");
|
|
667
484
|
}
|
|
668
|
-
// The requested v3 Extensions (a bare SEQUENCE OF Extension) from an object of the recognized keys, or
|
|
669
|
-
// an array of pre-encoded Extension DER (shape- + recognized-value-validated). Shared by every enrollment
|
|
670
|
-
// request that carries requested extensions (a PKCS#10 extensionRequest attribute, a CRMF CertTemplate
|
|
671
|
-
// extensions [9]); a request REQUESTS extensions, so there are no CA cross-field gates. `spki` feeds the
|
|
672
|
-
// subjectKeyIdentifier auto-derive.
|
|
673
485
|
var BC_KEYS = { cA: 1, pathLen: 1, critical: 1 };
|
|
674
486
|
var REQ_EXT_KEYS = {
|
|
675
487
|
subjectAltName: 1, keyUsage: 1, keyUsageCritical: 1, extendedKeyUsage: 1, extendedKeyUsageCritical: 1,
|
|
@@ -701,8 +513,6 @@ function makeBuilder(ctx) {
|
|
|
701
513
|
});
|
|
702
514
|
var out = [];
|
|
703
515
|
if (extSpec.subjectKeyIdentifier != null) {
|
|
704
|
-
// Auto-deriving the SKI (subjectKeyIdentifier: true) hashes the subject public key, so it needs one;
|
|
705
|
-
// reject the request when no public key is available (supply a Buffer key id instead).
|
|
706
516
|
if (extSpec.subjectKeyIdentifier === true && spki == null) throw E("bad-input", "subjectKeyIdentifier auto-derive (true) requires the public key -- supply a Buffer key id, or include the public key");
|
|
707
517
|
out.push(ext(O("subjectKeyIdentifier"), false, extSki(skiKeyId(extSpec.subjectKeyIdentifier, spki))));
|
|
708
518
|
}
|
|
@@ -715,16 +525,12 @@ function makeBuilder(ctx) {
|
|
|
715
525
|
return b.sequence(out);
|
|
716
526
|
}
|
|
717
527
|
|
|
718
|
-
// A positive, non-zero INTEGER of at most 20 content octets (RFC 5280 sec. 4.1.2.2 / RFC 5755 sec.
|
|
719
|
-
// 4.2.5 -- the identical serial rule for a certificate and an attribute certificate). A random 20-octet
|
|
720
|
-
// positive serial is generated when none is supplied. Accepts a BigInt, a safe integer, a decimal /
|
|
721
|
-
// 0x-hex string, or a raw magnitude Buffer.
|
|
722
528
|
function serialInteger(serial) {
|
|
723
529
|
var v;
|
|
724
530
|
if (serial == null) {
|
|
725
531
|
var rnd = nodeCrypto.randomBytes(20);
|
|
726
|
-
rnd[0] &= 0x7f;
|
|
727
|
-
if (rnd[0] === 0) rnd[0] = 0x01;
|
|
532
|
+
rnd[0] &= 0x7f;
|
|
533
|
+
if (rnd[0] === 0) rnd[0] = 0x01;
|
|
728
534
|
v = BigInt("0x" + rnd.toString("hex"));
|
|
729
535
|
} else if (typeof serial === "bigint") { v = serial; }
|
|
730
536
|
else if (typeof serial === "number") { if (!Number.isSafeInteger(serial)) throw E("bad-serial", "serialNumber number must be a safe integer (pass a BigInt, hex string, or Buffer for a value above 2^53-1)"); v = BigInt(serial); }
|
|
@@ -736,7 +542,6 @@ function makeBuilder(ctx) {
|
|
|
736
542
|
if (asn1.decode(tlv).content.length > 20) throw E("bad-serial", "serialNumber must not exceed 20 octets (RFC 5280 sec. 4.1.2.2)");
|
|
737
543
|
return tlv;
|
|
738
544
|
}
|
|
739
|
-
// Synthesize the parsed-cert shape resolveSignScheme reads (subjectPublicKeyInfo.algorithm) from a raw SPKI.
|
|
740
545
|
function certLikeFromSpki(spkiDer) {
|
|
741
546
|
var spki = asn1.decode(spkiDer);
|
|
742
547
|
if (!spki.children || !spki.children.length) throw E("bad-input", "the signing key SPKI is not a SubjectPublicKeyInfo");
|
|
@@ -747,11 +552,6 @@ function makeBuilder(ctx) {
|
|
|
747
552
|
return { subjectPublicKeyInfo: { algorithm: { oid: keyOid, parameters: alg.children.length > 1 ? alg.children[1].bytes : undefined } } };
|
|
748
553
|
}
|
|
749
554
|
|
|
750
|
-
// Confirm the produced signature verifies under `spki`. This works for any key type (a PKCS#8
|
|
751
|
-
// key, a WebCrypto CryptoKey, or any signer), where deriving-and-comparing the public key cannot
|
|
752
|
-
// (a non-extractable CryptoKey has no exportable public half). This is the x509 chain self-check
|
|
753
|
-
// AND the CSR proof of possession. Composite verifies both components. Returns a promise for
|
|
754
|
-
// composite, sync-throws for classical.
|
|
755
555
|
function assertSignatureVerifies(preimage, sig, spki, scheme) {
|
|
756
556
|
if (scheme.composite) {
|
|
757
557
|
return compositeSig.compositeVerify(spki, sig, preimage, scheme.composite, ErrorClass, code("unsupported-algorithm"), code("bad-input")).then(function (r) {
|
|
@@ -767,27 +567,16 @@ function makeBuilder(ctx) {
|
|
|
767
567
|
else if (s.name === "RSA-PSS") ok = nodeCrypto.verify(scheme.digest, preimage, { key: pub, padding: nodeCrypto.constants.RSA_PKCS1_PSS_PADDING, saltLength: s.saltLength }, sig);
|
|
768
568
|
else if (s.name === "RSASSA-PKCS1-v1_5") ok = nodeCrypto.verify(scheme.digest, preimage, pub, sig);
|
|
769
569
|
// allow:eddsa-verify-without-loworder-gate -- a self-check that OUR just-produced signature verifies
|
|
770
|
-
|
|
771
|
-
// low-order-point gate (a forged-signature defense) does not apply.
|
|
772
|
-
else ok = nodeCrypto.verify(null, preimage, pub, sig); // Ed25519 / Ed448 / ML-DSA / SLH-DSA
|
|
570
|
+
else ok = nodeCrypto.verify(null, preimage, pub, sig);
|
|
773
571
|
} catch (e) { throw E("bad-input", "the signature self-check could not run against the public key", e); }
|
|
774
572
|
if (!ok) throw E("bad-input", "the signing key does not correspond to the public key -- the signature would not verify");
|
|
775
573
|
}
|
|
776
574
|
|
|
777
|
-
// Do two SubjectPublicKeyInfo encodings name the same key? Bytes first, because that is the answer
|
|
778
|
-
// almost always and it costs nothing. When they differ the question is still open, since one key has
|
|
779
|
-
// more than one legal SPKI: an EC point may be carried compressed or uncompressed, so a P-256 key
|
|
780
|
-
// written the compressed way is byte-unequal to the same key written the other. Comparing the DER
|
|
781
|
-
// alone would refuse a proof of possession over a key the requester genuinely holds, while the
|
|
782
|
-
// sibling arms of the same verb accept that template. The fallback asks the key layer, which
|
|
783
|
-
// compares key material and still answers false for a different key, a different curve, or a
|
|
784
|
-
// different algorithm. A key neither side can import is equal to nothing, so the refusal stands.
|
|
785
575
|
function samePublicKey(spkiA, spkiB) {
|
|
786
576
|
var a = _bufferFrom(spkiA), bb = _bufferFrom(spkiB);
|
|
787
577
|
if (_bufferEquals(a, bb)) return true;
|
|
788
578
|
var ka, kb;
|
|
789
579
|
// allow:swallow-unverified an SPKI the key layer cannot import matches nothing, which is the
|
|
790
|
-
// refusal the caller wants; the caller raises its own typed error on the false.
|
|
791
580
|
try {
|
|
792
581
|
ka = nodeCrypto.createPublicKey({ key: a, format: "der", type: "spki" });
|
|
793
582
|
kb = nodeCrypto.createPublicKey({ key: bb, format: "der", type: "spki" });
|
|
@@ -810,31 +599,16 @@ function makeBuilder(ctx) {
|
|
|
810
599
|
};
|
|
811
600
|
}
|
|
812
601
|
|
|
813
|
-
// The raw issuer / subject Name TLV of a parsed X.509 certificate (byte-identical). tbs field layout:
|
|
814
|
-
// [version?] serial(0) signature(1) issuer(2) validity(3) subject(4). Used where a producer must chain a
|
|
815
|
-
// name to an existing certificate exactly -- an issued certificate's issuer, a CMS SignerInfo issuer, an
|
|
816
|
-
// attribute certificate's issuerName / holder baseCertificateID.
|
|
817
602
|
function tbsNameField(cert, which) {
|
|
818
603
|
var tbs = asn1.decode(cert.tbsBytes);
|
|
819
604
|
var hasVersion = tbs.children[0].tagClass === "context" && tbs.children[0].tagNumber === 0;
|
|
820
605
|
return tbs.children[(hasVersion ? 1 : 0) + (which === "subject" ? 4 : 2)].bytes;
|
|
821
606
|
}
|
|
822
607
|
|
|
823
|
-
// The serialNumber of a parsed X.509 certificate, read from the SIGNED bytes. Same tbs layout as
|
|
824
|
-
// above, and the same reason: where a producer binds an identity to an existing certificate, that
|
|
825
|
-
// identity is issuer AND serial together. Deriving one from the bytes and reading the other from
|
|
826
|
-
// the object lets the halves name different certificates, producing a Holder whose issuer is
|
|
827
|
-
// genuine and whose serial is whatever the caller wrote.
|
|
828
608
|
function tbsSerialNumber(cert) {
|
|
829
609
|
var tbs = asn1.decode(cert.tbsBytes);
|
|
830
610
|
var hasVersion = tbs.children[0].tagClass === "context" && tbs.children[0].tagNumber === 0;
|
|
831
611
|
return asn1.read.integer(tbs.children[hasVersion ? 1 : 0]);
|
|
832
612
|
}
|
|
833
613
|
|
|
834
|
-
// Frozen so a co-resident cannot reassign an exported guard (e.g. reqDenseArray) to bypass it: the
|
|
835
|
-
// standalone consumers read reqDenseArray off this object at call time, so the object holding it must be
|
|
836
|
-
// immutable, the way the guard-* modules freeze their surfaces.
|
|
837
|
-
// Frozen so a co-resident cannot reassign an exported guard (e.g. reqDenseArray) to bypass it: the
|
|
838
|
-
// standalone consumers read reqDenseArray off this object at call time, so the object holding it must be
|
|
839
|
-
// immutable, the way the guard-* modules freeze their surfaces.
|
|
840
614
|
module.exports = intrinsic.freeze({ makeBuilder: makeBuilder, reqDenseArray: reqDenseArrayImpl, KU_BIT: KU_BIT, tbsNameField: tbsNameField, tbsSerialNumber: tbsSerialNumber });
|