@blamejs/pki 0.5.30 → 0.5.32
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +28 -0
- package/README.md +2 -2
- package/lib/acme.js +347 -90
- package/lib/asn1-der.js +97 -33
- package/lib/attrcert-sign.js +1 -1
- package/lib/cmc-build.js +19 -8
- package/lib/cmc-verify.js +4 -4
- package/lib/cmp-build.js +36 -6
- package/lib/cmp-session.js +5 -5
- package/lib/cmp-verify.js +86 -16
- package/lib/cms-compress.js +2 -2
- package/lib/cms-decrypt.js +3 -3
- package/lib/cms-encrypt.js +1 -1
- package/lib/cms-sign.js +7 -7
- package/lib/cms-verify.js +9 -4
- package/lib/crl-sign.js +5 -5
- package/lib/crmf-sign.js +2 -1
- package/lib/csr-sign.js +1 -4
- package/lib/ct.js +8 -4
- package/lib/est.js +234 -34
- package/lib/framework-error.js +20 -6
- package/lib/guard-bytes.js +24 -3
- package/lib/http-digest.js +69 -16
- package/lib/http-retry-after.js +68 -24
- package/lib/http-transport.js +60 -6
- package/lib/inspect.js +13 -2
- package/lib/ip-utils.js +102 -25
- package/lib/key.js +2 -2
- package/lib/lint.js +1 -1
- package/lib/mime.js +27 -4
- package/lib/ocsp.js +7 -7
- package/lib/oid.js +16 -0
- package/lib/path-validate.js +76 -18
- package/lib/pbes2.js +2 -2
- package/lib/pkcs12-build.js +2 -2
- package/lib/pki-build.js +320 -21
- package/lib/rfc3339.js +75 -17
- package/lib/schema-c509.js +36 -9
- package/lib/schema-cmc.js +3 -3
- package/lib/schema-pkix.js +174 -25
- package/lib/schema-tsp.js +10 -1
- package/lib/sign-scheme.js +27 -7
- package/lib/sigstore.js +52 -9
- package/lib/smime.js +86 -6
- package/lib/trust.js +114 -14
- package/lib/tsp-sign.js +39 -27
- package/lib/validator-tpm.js +2 -2
- package/lib/webauthn-mds.js +24 -6
- package/lib/webauthn.js +3 -3
- package/lib/webcrypto.js +1 -1
- package/lib/x509-sign.js +9 -5
- package/package.json +1 -1
- package/sbom.cdx.json +6 -6
package/lib/pki-build.js
CHANGED
|
@@ -25,16 +25,279 @@ var _bufferFrom = intrinsic.bufferFrom;
|
|
|
25
25
|
var _bufferEquals = intrinsic.bufferEquals;
|
|
26
26
|
var oid = require("./oid");
|
|
27
27
|
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
|
+
var _packIpLiteral = ipUtils.packIpLiteral;
|
|
28
32
|
var nodeCrypto = require("crypto");
|
|
29
33
|
// The key-material comparison, taken at load like every other operation a decision here rests on.
|
|
30
34
|
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
|
+
var _charCodeAt = intrinsic.uncurry(String.prototype.charCodeAt);
|
|
44
|
+
var _stringIndexOf = intrinsic.stringIndexOf;
|
|
45
|
+
var _stringLastIndexOf = intrinsic.uncurry(String.prototype.lastIndexOf);
|
|
46
|
+
var _sliceStr = intrinsic.uncurry(String.prototype.slice);
|
|
47
|
+
var _String = intrinsic.String;
|
|
48
|
+
var _objectKeys = intrinsic.keys;
|
|
49
|
+
var _isBufferChk = intrinsic.isBuffer;
|
|
50
|
+
var _stringify = intrinsic.stringify;
|
|
31
51
|
|
|
32
|
-
|
|
52
|
+
// --- bare-string GeneralName form detection, by character-code scan (no regex) ---
|
|
53
|
+
// Each predicate answers "is this string syntactically THIS name form", reading the string one code unit
|
|
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
|
|
58
|
+
function _isHexCode(c) { return (c >= 48 && c <= 57) || (c >= 97 && c <= 102) || (c >= 65 && c <= 70); }
|
|
59
|
+
|
|
60
|
+
// RFC 3986 sec. 2.3 unreserved: ALPHA / DIGIT / "-" / "." / "_" / "~".
|
|
61
|
+
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
|
+
function _isSubDelim(c) {
|
|
64
|
+
switch (c) {
|
|
65
|
+
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
|
+
default: return false;
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
// A userinfo byte: unreserved / sub-delim / ":" (percent-encoding handled separately). No "@".
|
|
70
|
+
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, so an IPv6-literal host is refused by the shorthand and uses the object form.
|
|
74
|
+
function _isUriByte(c) {
|
|
75
|
+
return _isUnreserved(c) || _isSubDelim(c) || c === 58 || c === 47 || c === 63 || c === 35 || c === 64;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
// An RFC 5322 sec. 3.2.3 "atext" byte: ALPHA / DIGIT and a fixed set of specials, used for an unquoted
|
|
79
|
+
// email local part. "." is deliberately excluded so the dot-atom structure is enforced by the scan.
|
|
80
|
+
function _isAtextByte(c) {
|
|
81
|
+
if (_isAlphaCode(c) || _isDigitCode(c)) return true;
|
|
82
|
+
switch (c) {
|
|
83
|
+
case 33: case 35: case 36: case 37: case 38: case 39: // ! # $ % & '
|
|
84
|
+
case 42: case 43: case 45: case 47: case 61: case 63: // * + - / = ?
|
|
85
|
+
case 94: case 95: case 96: case 123: case 124: case 125: case 126: // ^ _ ` { | } ~
|
|
86
|
+
return true;
|
|
87
|
+
default: return false;
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
// IPv4 SHAPE: one or more digit groups joined by single dots (only digits and dots, no leading/trailing/
|
|
92
|
+
// doubled dot, at least one dot). This is the "looks like an address" test, not a valid-address test --
|
|
93
|
+
// packIpLiteral decides validity; a shape match that is not a valid address is refused as ambiguous.
|
|
94
|
+
function _looksLikeIPv4Shape(s) {
|
|
95
|
+
var n = s.length, i, c, sawDot = false, atomEmpty = true;
|
|
96
|
+
if (n === 0) return false;
|
|
97
|
+
for (i = 0; i < n; i += 1) {
|
|
98
|
+
c = _charCodeAt(s, i);
|
|
99
|
+
if (c === 46) { if (atomEmpty) return false; sawDot = true; atomEmpty = true; } // "."
|
|
100
|
+
else if (_isDigitCode(c)) { atomEmpty = false; }
|
|
101
|
+
else return false;
|
|
102
|
+
}
|
|
103
|
+
return sawDot && !atomEmpty;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
// Every byte in [from,to) is an allowed component byte (per `byteOk`) or opens a "%HEXHEX" triplet.
|
|
107
|
+
function _componentBytesOk(s, from, to, byteOk) {
|
|
108
|
+
var i = from, c;
|
|
109
|
+
while (i < to) {
|
|
110
|
+
c = _charCodeAt(s, i);
|
|
111
|
+
if (c === 37) { // "%" -> %HEXHEX
|
|
112
|
+
if (i + 2 >= to || !_isHexCode(_charCodeAt(s, i + 1)) || !_isHexCode(_charCodeAt(s, i + 2))) return false;
|
|
113
|
+
i += 3;
|
|
114
|
+
} else if (byteOk(c)) { i += 1; }
|
|
115
|
+
else return false;
|
|
116
|
+
}
|
|
117
|
+
return true;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
// The URI host in [from,to): RFC 5280 sec. 4.2.1.6 requires a URI SAN authority to carry a fully-qualified
|
|
121
|
+
// domain name or IP host, stricter than an RFC 3986 reg-name. So the host is either a valid IPv4 literal,
|
|
122
|
+
// or an FQDN of strict-LDH labels (the email-domain grammar: no underscore, no hyphen at a label edge) with
|
|
123
|
+
// at least one dot -- a single-label host such as "localhost" is not fully qualified and uses the object
|
|
124
|
+
// form, matching the email domain. An IPv4-SHAPED host that is not a valid address is refused as ambiguous
|
|
125
|
+
// (the same rule the top-level classifier uses), and an IPv6-literal host -- needing the excluded "[" "]"
|
|
126
|
+
// -- uses the object form.
|
|
127
|
+
function _validUriHost(s, from, to) {
|
|
128
|
+
if (to <= from) return false; // empty host
|
|
129
|
+
var host = _sliceStr(s, from, to), len = host.length;
|
|
130
|
+
if (_looksLikeIPv4Shape(host)) return _packIpLiteral(host) !== null; // IPv4 (first-match-wins)
|
|
131
|
+
var end = _charCodeAt(host, len - 1) === 46 ? len - 1 : len; // ignore an absolute-root "."
|
|
132
|
+
if (end > 253) return false; // RFC 1035 name length (root dot excluded)
|
|
133
|
+
var firstDot = _stringIndexOf(host, ".", 0);
|
|
134
|
+
if (firstDot === -1 || firstDot >= end) return false; // multi-label FQDN ("localhost" is not)
|
|
135
|
+
return _labelsValid(host, 0, len, _isHostLabelRange, true); // strict-LDH FQDN, root "." allowed
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
// The authority in [from,to) is "[ userinfo "@" ] host [ ":" port ]" (RFC 3986 sec. 3.2): at most one "@"
|
|
139
|
+
// (userinfo and reg-name each exclude it), a host validated by _validUriHost (RFC 5280 FQDN or IPv4), and
|
|
140
|
+
// an all-digit -- possibly empty -- port after the host's single ":" (a reg-name has no ":"). The userinfo
|
|
141
|
+
// bytes are validated against the RFC 3986 userinfo class.
|
|
142
|
+
function _validUriAuthority(s, from, to) {
|
|
143
|
+
var i, c, at = -1;
|
|
144
|
+
for (i = from; i < to; i += 1) {
|
|
145
|
+
c = _charCodeAt(s, i);
|
|
146
|
+
if (c === 64) { if (at !== -1) return false; at = i; } // "@" -- at most one
|
|
147
|
+
}
|
|
148
|
+
var hostStart = at === -1 ? from : at + 1;
|
|
149
|
+
if (at !== -1 && !_componentBytesOk(s, from, at, _isUserinfoByte)) return false; // userinfo
|
|
150
|
+
var colon = -1;
|
|
151
|
+
for (i = hostStart; i < to; i += 1) { if (_charCodeAt(s, i) === 58) { colon = i; break; } } // ":" -> port
|
|
152
|
+
var hostEnd = colon === -1 ? to : colon;
|
|
153
|
+
if (!_validUriHost(s, hostStart, hostEnd)) return false; // FQDN or IPv4 host
|
|
154
|
+
if (colon !== -1) {
|
|
155
|
+
for (i = colon + 1; i < to; i += 1) { if (!_isDigitCode(_charCodeAt(s, i))) return false; } // port = *DIGIT
|
|
156
|
+
}
|
|
157
|
+
return true;
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
// scheme "://" authority [ path ] [ "?" query ] [ "#" fragment ], parsed by RFC 3986 component. The
|
|
161
|
+
// authority is validated by _validUriAuthority; the remainder is path/query/fragment bytes with at most
|
|
162
|
+
// one "#" (its single fragment delimiter). A structurally malformed URL is refused; the object form is
|
|
163
|
+
// the escape for one the shorthand does not accept.
|
|
164
|
+
function _looksLikeUri(s) {
|
|
165
|
+
var n = s.length, i, c;
|
|
166
|
+
if (n === 0 || !_isAlphaCode(_charCodeAt(s, 0))) return false; // scheme starts ALPHA
|
|
167
|
+
i = 1;
|
|
168
|
+
while (i < n) {
|
|
169
|
+
c = _charCodeAt(s, i);
|
|
170
|
+
if (_isAlphaCode(c) || _isDigitCode(c) || c === 43 || c === 45 || c === 46) i += 1; // + - . in scheme
|
|
171
|
+
else break;
|
|
172
|
+
}
|
|
173
|
+
if (i + 3 > n || _charCodeAt(s, i) !== 58 || _charCodeAt(s, i + 1) !== 47 || _charCodeAt(s, i + 2) !== 47) return false;
|
|
174
|
+
i += 3;
|
|
175
|
+
var authStart = i, authEnd = n;
|
|
176
|
+
for (; i < n; i += 1) { c = _charCodeAt(s, i); if (c === 47 || c === 63 || c === 35) { authEnd = i; break; } }
|
|
177
|
+
if (authEnd === authStart) return false; // empty authority
|
|
178
|
+
if (!_validUriAuthority(s, authStart, authEnd)) return false;
|
|
179
|
+
var sawHash = false; // path / query / fragment
|
|
180
|
+
i = authEnd;
|
|
181
|
+
while (i < n) {
|
|
182
|
+
c = _charCodeAt(s, i);
|
|
183
|
+
if (c === 37) { // "%" -> %HEXHEX
|
|
184
|
+
if (i + 2 >= n || !_isHexCode(_charCodeAt(s, i + 1)) || !_isHexCode(_charCodeAt(s, i + 2))) return false;
|
|
185
|
+
i += 3;
|
|
186
|
+
continue;
|
|
187
|
+
}
|
|
188
|
+
if (c === 35) { if (sawHash) return false; sawHash = true; } // one "#" fragment delimiter
|
|
189
|
+
if (!_isUriByte(c)) return false;
|
|
190
|
+
i += 1;
|
|
191
|
+
}
|
|
192
|
+
return true;
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
// A single dNSName label over [from,to): 1-63 bytes, LDH plus a LEADING or EMBEDDED underscore, no hyphen
|
|
196
|
+
// at either edge, and no underscore at the trailing edge. The underscore tolerance is deliberate and
|
|
197
|
+
// leading/embedded only: the real forms are _acme-challenge, _dmarc, _25._tcp (a label-initial "_"); a
|
|
198
|
+
// label ending in "_" has no such use. An email DOMAIN is stricter still -- see _isHostLabelRange.
|
|
199
|
+
function _isDnsLabelRange(s, from, to) {
|
|
200
|
+
var len = to - from, i, c;
|
|
201
|
+
if (len < 1 || len > 63) return false;
|
|
202
|
+
for (i = from; i < to; i += 1) {
|
|
203
|
+
c = _charCodeAt(s, i);
|
|
204
|
+
if (_isAlphaCode(c) || _isDigitCode(c)) continue; // A-Z a-z 0-9 anywhere
|
|
205
|
+
if (i === to - 1) return false; // last byte must be alnum
|
|
206
|
+
if (c === 95) continue; // "_" leading or embedded
|
|
207
|
+
if (c === 45 && i !== from) continue; // "-" embedded (not leading)
|
|
208
|
+
return false;
|
|
209
|
+
}
|
|
210
|
+
return true;
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
// A single RFC 5321 sec. 2.3.5 hostname label over [from,to): the strict letter-digit-hyphen grammar with
|
|
214
|
+
// no hyphen at either edge and NO underscore. This is the email-domain label rule (a mail domain is a real
|
|
215
|
+
// hostname), stricter than the underscore-tolerant dNSName label above.
|
|
216
|
+
function _isHostLabelRange(s, from, to) {
|
|
217
|
+
var len = to - from, i, c, edge;
|
|
218
|
+
if (len < 1 || len > 63) return false;
|
|
219
|
+
for (i = from; i < to; i += 1) {
|
|
220
|
+
c = _charCodeAt(s, i);
|
|
221
|
+
edge = (i === from || i === to - 1);
|
|
222
|
+
if (_isAlphaCode(c) || _isDigitCode(c)) continue; // A-Z a-z 0-9
|
|
223
|
+
if (c === 45 && !edge) continue; // "-" not at an edge
|
|
224
|
+
return false;
|
|
225
|
+
}
|
|
226
|
+
return true;
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
// Every dot-separated label in [from,to) satisfies `labelFn`. Scans with indexOf; no split allocation. When
|
|
230
|
+
// `allowRootDot` is set, a single trailing "." (the absolute-FQDN root) is stripped before validating, so
|
|
231
|
+
// "example.com." checks as "example.com"; an interior empty label (a doubled or leading dot) still fails.
|
|
232
|
+
function _labelsValid(s, from, to, labelFn, allowRootDot) {
|
|
233
|
+
if (allowRootDot && to - from >= 1 && _charCodeAt(s, to - 1) === 46) to -= 1; // an absolute-root "."
|
|
234
|
+
if (to <= from) return false; // nothing (a lone ".")
|
|
235
|
+
var start = from, dot;
|
|
236
|
+
for (;;) {
|
|
237
|
+
dot = _stringIndexOf(s, ".", start);
|
|
238
|
+
var end = (dot === -1 || dot >= to) ? to : dot;
|
|
239
|
+
if (!labelFn(s, start, end)) return false;
|
|
240
|
+
if (end === to) return true;
|
|
241
|
+
start = end + 1;
|
|
242
|
+
}
|
|
243
|
+
}
|
|
33
244
|
|
|
34
|
-
// A
|
|
35
|
-
//
|
|
36
|
-
|
|
37
|
-
var
|
|
245
|
+
// A dNSName: at most 253 bytes, an optional single leading "*." wildcard, then dot-separated dNSName labels
|
|
246
|
+
// (underscore-tolerant), with an optional trailing absolute-root ".".
|
|
247
|
+
function _looksLikeDnsName(s) {
|
|
248
|
+
var n = s.length, start = 0;
|
|
249
|
+
if (n < 1) return false;
|
|
250
|
+
var end = _charCodeAt(s, n - 1) === 46 ? n - 1 : n; // exclude an absolute-root "."
|
|
251
|
+
if (end < 1 || end > 253) return false; // RFC 1035 name length (root dot excluded)
|
|
252
|
+
if (end >= 2 && _charCodeAt(s, 0) === 42 && _charCodeAt(s, 1) === 46) start = 2; // "*."
|
|
253
|
+
if (start >= end) return false;
|
|
254
|
+
return _labelsValid(s, start, n, _isDnsLabelRange, true);
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
// An rfc822Name: an unquoted RFC 5321 dot-atom local part (atext runs joined by single dots), one "@",
|
|
258
|
+
// and a dotted hostname domain (at least one dot -- an FQDN, no wildcard, strict LDH labels, no underscore).
|
|
259
|
+
function _looksLikeEmail(s) {
|
|
260
|
+
if (s.length > 254) return false; // the whole mailbox <= 254 octets (RFC 5321 sec. 4.5.3.1.3)
|
|
261
|
+
var at = _stringIndexOf(s, "@", 0);
|
|
262
|
+
if (at < 1 || at > 64 || _stringLastIndexOf(s, "@") !== at) return false; // one "@", a 1-64 octet local part (RFC 5321 sec. 4.5.3.1.1)
|
|
263
|
+
var i, c, atomEmpty = true;
|
|
264
|
+
for (i = 0; i < at; i += 1) { // local part = dot-atom
|
|
265
|
+
c = _charCodeAt(s, i);
|
|
266
|
+
if (c === 46) { if (atomEmpty) return false; atomEmpty = true; } // no leading/doubled dot
|
|
267
|
+
else if (_isAtextByte(c)) { atomEmpty = false; }
|
|
268
|
+
else return false;
|
|
269
|
+
}
|
|
270
|
+
if (atomEmpty) return false; // no trailing dot / empty
|
|
271
|
+
var dStart = at + 1, dLen = s.length - dStart;
|
|
272
|
+
if (dLen < 1 || dLen > 253 || _stringIndexOf(s, ".", dStart) === -1) return false; // domain must be dotted
|
|
273
|
+
return _labelsValid(s, dStart, s.length, _isHostLabelRange, false); // strict LDH, no underscore or root dot
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
// A well-formed dotted-decimal OID SHAPE (no regex, by the same character scan the name forms use): a first
|
|
277
|
+
// arc 0-2, then one or more arcs each introduced by "." and written without a leading zero ("0" alone, or a
|
|
278
|
+
// non-zero digit followed by more digits). Used to accept a raw OID string (an unregistered KeyPurposeId /
|
|
279
|
+
// policy OID) where a registered name would go; the encoder (encodeOidContent) remains the authoritative
|
|
280
|
+
// validator of the arc-value bounds.
|
|
281
|
+
function _isDottedOid(s) {
|
|
282
|
+
var n = s.length, i, c, d0, arcs = 0;
|
|
283
|
+
if (n < 3) return false; // shortest is "X.Y"
|
|
284
|
+
c = _charCodeAt(s, 0);
|
|
285
|
+
if (c < 48 || c > 50) return false; // first arc is a single digit 0-2
|
|
286
|
+
i = 1;
|
|
287
|
+
while (i < n) {
|
|
288
|
+
if (_charCodeAt(s, i) !== 46) return false; // every following arc is introduced by "."
|
|
289
|
+
i += 1;
|
|
290
|
+
if (i >= n) return false; // a trailing "." has no arc
|
|
291
|
+
d0 = _charCodeAt(s, i);
|
|
292
|
+
if (d0 === 48) { i += 1; } // "0" is a complete arc; a leading zero is refused
|
|
293
|
+
else if (d0 >= 49 && d0 <= 57) { i += 1; while (i < n && _isDigitCode(_charCodeAt(s, i))) i += 1; }
|
|
294
|
+
else { return false; } // an arc must start with a digit
|
|
295
|
+
arcs += 1;
|
|
296
|
+
}
|
|
297
|
+
return arcs >= 1;
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
var b = asn1.build;
|
|
38
301
|
|
|
39
302
|
// KeyUsage named-bit positions (RFC 5280 sec. 4.2.1.3); contentCommitment is the RFC 5280 rename of the
|
|
40
303
|
// X.509 nonRepudiation bit (1).
|
|
@@ -92,7 +355,7 @@ function makeBuilder(ctx) {
|
|
|
92
355
|
// A DN spec -> RDNSequence DER. A string is shorthand for a single commonName RDN; a Buffer is raw
|
|
93
356
|
// pre-encoded Name DER (validated through the parser). An empty array yields an empty RDNSequence.
|
|
94
357
|
function encodeName(spec) {
|
|
95
|
-
if (
|
|
358
|
+
if (guard.bytes.isByteSource(spec)) { var _nd = guard.bytes.snapshotSource(spec, ErrorClass, ctx.prefix + "/bad-name", "raw Name DER"); assertValidNameDer(_nd); return _nd; }
|
|
96
359
|
if (typeof spec === "string") spec = [{ commonName: spec }];
|
|
97
360
|
if (!_isArray(spec)) throw E("bad-name", "a name must be a string, an array of RDNs, or raw Name DER");
|
|
98
361
|
return b.sequence(spec.map(encodeRdn));
|
|
@@ -112,15 +375,51 @@ function makeBuilder(ctx) {
|
|
|
112
375
|
|
|
113
376
|
// ---- GeneralName encoding (RFC 5280 sec. 4.2.1.6) ----
|
|
114
377
|
function ia5Content(s) {
|
|
115
|
-
|
|
378
|
+
// Captured String / charCodeAt / bufferFrom: this converts a GeneralName value (a classified bare
|
|
379
|
+
// string or an object-form value) to the emitted IA5String bytes, so co-resident code replacing any of
|
|
380
|
+
// them after load could otherwise change which host the certificate names from the one that was checked.
|
|
381
|
+
s = _String(s);
|
|
116
382
|
for (var i = 0; i < s.length; i++) {
|
|
117
|
-
if (s
|
|
383
|
+
if (_charCodeAt(s, i) > 0x7F) throw E("bad-input", "value requires 7-bit ASCII (IA5String): " + _stringify(s));
|
|
118
384
|
}
|
|
119
|
-
return
|
|
385
|
+
return _bufferFrom(s, "latin1");
|
|
386
|
+
}
|
|
387
|
+
// A bare-string GeneralName shorthand: classify a string into exactly one form, FAIL-CLOSED. RFC 5280
|
|
388
|
+
// sec. 4.2.1.6 GeneralName is a CHOICE, so an input that does not UNAMBIGUOUSLY match one form throws
|
|
389
|
+
// rather than being guessed -- the explicit object form ({ dNSName: s } etc.) is always the escape. The
|
|
390
|
+
// per-form syntax is decided by the character-scan predicates above (no regex). Precedence resolves the
|
|
391
|
+
// overlaps of the later tests: an IP literal is also LDH-with-dots (so it is tested first), and a
|
|
392
|
+
// scheme://authority URL can carry userinfo that reads like an email (so URI is tested before rfc822Name).
|
|
393
|
+
// Form detection only -- IA5String (7-bit) enforcement stays at ia5Content, so a non-ASCII value still
|
|
394
|
+
// throws at encode.
|
|
395
|
+
function _classifyBareGeneralName(s) {
|
|
396
|
+
if (_packIpLiteral(s) !== null) return { iPAddress: s };
|
|
397
|
+
// A string of only digit groups separated by dots is an IPv4 ATTEMPT; if packIpLiteral rejected it,
|
|
398
|
+
// it is a MALFORMED address, not a dNSName. It reads two ways (an invalid IP and a syntactically
|
|
399
|
+
// valid all-numeric name), so it is ambiguous and refused rather than silently encoded as a name the
|
|
400
|
+
// caller did not intend -- a genuine all-numeric name uses the explicit { dNSName: ... } form.
|
|
401
|
+
if (_looksLikeIPv4Shape(s)) {
|
|
402
|
+
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");
|
|
403
|
+
}
|
|
404
|
+
if (_looksLikeUri(s)) return { uniformResourceIdentifier: s };
|
|
405
|
+
if (_looksLikeEmail(s)) return { rfc822Name: s };
|
|
406
|
+
if (_looksLikeDnsName(s)) return { dNSName: s };
|
|
407
|
+
var _qs = _stringify(s);
|
|
408
|
+
throw E("bad-input", "cannot classify the bare GeneralName string " + _qs +
|
|
409
|
+
" as a dNSName, rfc822Name, iPAddress, or URI; pass an explicit form object, e.g. { dNSName: " + _qs + " }");
|
|
120
410
|
}
|
|
121
411
|
function encodeGeneralName(entry) {
|
|
122
|
-
|
|
123
|
-
|
|
412
|
+
// A bare non-empty string is shorthand -- normalized to its single-key object form, fail-closed,
|
|
413
|
+
// before the object checks below; an empty string is refused here (never classified).
|
|
414
|
+
if (typeof entry === "string") {
|
|
415
|
+
if (entry === "") throw E("bad-input", "an empty GeneralName value is not permitted (RFC 5280 sec. 4.2.1.6)");
|
|
416
|
+
entry = _classifyBareGeneralName(entry);
|
|
417
|
+
}
|
|
418
|
+
if (!entry || typeof entry !== "object" || _isBufferChk(entry)) throw E("bad-input", "a GeneralName must be an object with exactly one name form");
|
|
419
|
+
// Captured Object.keys / property read: this reads which form and value the certificate will emit for a
|
|
420
|
+
// GeneralName (a freshly classified bare string, or a caller object), so a replaced Object.keys must not
|
|
421
|
+
// be able to relabel the classified result into a different, attacker-chosen form after validation.
|
|
422
|
+
var keys = _objectKeys(entry);
|
|
124
423
|
if (keys.length !== 1) throw E("bad-input", "a GeneralName entry must have exactly one form, got " + keys.length);
|
|
125
424
|
var k = keys[0], v = entry[k];
|
|
126
425
|
if (v == null || v === "") throw E("bad-input", "an empty GeneralName value is not permitted (RFC 5280 sec. 4.2.1.6)");
|
|
@@ -134,10 +433,10 @@ function makeBuilder(ctx) {
|
|
|
134
433
|
// octets by hand (a GeneralName iPAddress is a bare host address, RFC 5280 sec. 4.2.1.6).
|
|
135
434
|
var ipBuf = v;
|
|
136
435
|
if (typeof v === "string") {
|
|
137
|
-
ipBuf =
|
|
138
|
-
if (ipBuf === null) throw E("bad-input", "iPAddress string is not a valid IPv4 or IPv6 literal: " +
|
|
436
|
+
ipBuf = _packIpLiteral(v);
|
|
437
|
+
if (ipBuf === null) throw E("bad-input", "iPAddress string is not a valid IPv4 or IPv6 literal: " + _stringify(v));
|
|
139
438
|
}
|
|
140
|
-
if (!
|
|
439
|
+
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");
|
|
141
440
|
return b.contextPrimitive(7, ipBuf);
|
|
142
441
|
case "directoryName": return b.explicit(4, encodeName(v)); // Name is a CHOICE -> the context tag is EXPLICIT
|
|
143
442
|
case "otherName":
|
|
@@ -187,8 +486,8 @@ function makeBuilder(ctx) {
|
|
|
187
486
|
function _resolveOid(n, label) {
|
|
188
487
|
var dotted = O(n);
|
|
189
488
|
if (dotted != null) return dotted;
|
|
190
|
-
if (typeof n === "string" &&
|
|
191
|
-
// The
|
|
489
|
+
if (typeof n === "string" && _isDottedOid(n)) {
|
|
490
|
+
// The shape scan fixes the general form; b.oid's encoder is the authoritative X.660 arc-bounds check
|
|
192
491
|
// (a first arc 0/1 caps the second at 39; an arc must DER-encode). A failure there is this producer's
|
|
193
492
|
// bad-input, not a leaked oid/* code, so every shared-builder consumer keeps its own error contract.
|
|
194
493
|
try { b.oid(n); }
|
|
@@ -261,14 +560,14 @@ function makeBuilder(ctx) {
|
|
|
261
560
|
return nodeCrypto.createHash("sha1").update(keyBytes).digest();
|
|
262
561
|
}
|
|
263
562
|
function skiKeyId(val, spkiDer) {
|
|
264
|
-
if (
|
|
563
|
+
if (guard.bytes.isByteSource(val)) return guard.bytes.snapshotSource(val, ErrorClass, ctx.prefix + "/bad-input", "subjectKeyIdentifier");
|
|
265
564
|
if (val === true) return spkiKeyId(spkiDer);
|
|
266
|
-
throw E("bad-input", "subjectKeyIdentifier must be true (auto-derive) or a
|
|
565
|
+
throw E("bad-input", "subjectKeyIdentifier must be true (auto-derive) or a BufferSource key id");
|
|
267
566
|
}
|
|
268
567
|
|
|
269
568
|
// ---- embedded-input validators ----
|
|
270
569
|
function reqDer(v, what) {
|
|
271
|
-
if (
|
|
570
|
+
if (guard.bytes.isByteSource(v)) return guard.bytes.snapshotSource(v, ErrorClass, ctx.prefix + "/bad-input", what);
|
|
272
571
|
throw E("bad-input", what + " must be a DER Buffer");
|
|
273
572
|
}
|
|
274
573
|
// Full validation of an embedded SubjectPublicKeyInfo, walked through the parser the decoder uses.
|
|
@@ -364,8 +663,8 @@ function makeBuilder(ctx) {
|
|
|
364
663
|
} else if (typeof serial === "bigint") { v = serial; }
|
|
365
664
|
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); }
|
|
366
665
|
else if (typeof serial === "string") { try { v = BigInt(serial); } catch (e) { throw E("bad-serial", "serialNumber string must be a decimal or 0x-hex integer", e); } }
|
|
367
|
-
else if (
|
|
368
|
-
else { throw E("bad-serial", "serialNumber must be a BigInt, integer, hex string, or
|
|
666
|
+
else if (guard.bytes.isByteSource(serial)) { var _sb = guard.bytes.source(serial, ErrorClass, ctx.prefix + "/bad-serial", "serialNumber"); v = _sb.length ? BigInt("0x" + _sb.toString("hex")) : 0n; }
|
|
667
|
+
else { throw E("bad-serial", "serialNumber must be a BigInt, integer, hex string, or BufferSource"); }
|
|
369
668
|
if (v <= 0n) throw E("bad-serial", "serialNumber must be a positive integer (RFC 5280 sec. 4.1.2.2)");
|
|
370
669
|
var tlv = b.integer(v);
|
|
371
670
|
if (asn1.decode(tlv).content.length > 20) throw E("bad-serial", "serialNumber must not exceed 20 octets (RFC 5280 sec. 4.1.2.2)");
|
package/lib/rfc3339.js
CHANGED
|
@@ -13,23 +13,72 @@
|
|
|
13
13
|
* domain fault code.
|
|
14
14
|
*/
|
|
15
15
|
|
|
16
|
-
//
|
|
17
|
-
|
|
16
|
+
// Exactly `count` ASCII digits at `start`, as an integer, or null on any non-digit or short read.
|
|
17
|
+
// The grammar is decided character by character (no regex, whose match protocol re-dispatches through
|
|
18
|
+
// a replaceable `exec`); `\d` in the original is ASCII [0-9] only, matched here as 48..57.
|
|
19
|
+
function _digitsAt(v, start, count) {
|
|
20
|
+
if (start + count > v.length) return null;
|
|
21
|
+
var val = 0;
|
|
22
|
+
for (var i = 0; i < count; i++) {
|
|
23
|
+
var c = v.charCodeAt(start + i);
|
|
24
|
+
if (c < 48 || c > 57) return null;
|
|
25
|
+
val = val * 10 + (c - 48);
|
|
26
|
+
}
|
|
27
|
+
return val;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
// RFC 3339 date-time scanned into its components, or null on any grammar violation:
|
|
31
|
+
// YYYY "-" MM "-" DD [Tt] HH ":" MM ":" SS, an optional "." + one-or-more fraction digits, then a
|
|
32
|
+
// zone -- [Zz] or ([+-]) OFFHH ":" OFFMM -- with nothing after. Calendar validity is checked by the
|
|
33
|
+
// caller. `offset` carries the numeric-offset hours/minutes when present, else null.
|
|
34
|
+
function _scanDateTime(v) {
|
|
35
|
+
var n = v.length, p = 0;
|
|
36
|
+
var year = _digitsAt(v, p, 4); if (year === null) return null; p += 4;
|
|
37
|
+
if (v.charAt(p) !== "-") return null; p += 1;
|
|
38
|
+
var month = _digitsAt(v, p, 2); if (month === null) return null; p += 2;
|
|
39
|
+
if (v.charAt(p) !== "-") return null; p += 1;
|
|
40
|
+
var day = _digitsAt(v, p, 2); if (day === null) return null; p += 2;
|
|
41
|
+
var t = v.charAt(p); if (t !== "T" && t !== "t") return null; p += 1;
|
|
42
|
+
var hour = _digitsAt(v, p, 2); if (hour === null) return null; p += 2;
|
|
43
|
+
if (v.charAt(p) !== ":") return null; p += 1;
|
|
44
|
+
var min = _digitsAt(v, p, 2); if (min === null) return null; p += 2;
|
|
45
|
+
if (v.charAt(p) !== ":") return null; p += 1;
|
|
46
|
+
var sec = _digitsAt(v, p, 2); if (sec === null) return null; p += 2;
|
|
47
|
+
if (v.charAt(p) === ".") { // optional fractional second: "." + one or more digits
|
|
48
|
+
p += 1;
|
|
49
|
+
var fracStart = p;
|
|
50
|
+
while (p < n) { var fc = v.charCodeAt(p); if (fc < 48 || fc > 57) break; p += 1; }
|
|
51
|
+
if (p === fracStart) return null;
|
|
52
|
+
}
|
|
53
|
+
var z = v.charAt(p), offset = null;
|
|
54
|
+
if (z === "Z" || z === "z") {
|
|
55
|
+
p += 1;
|
|
56
|
+
} else if (z === "+" || z === "-") {
|
|
57
|
+
p += 1;
|
|
58
|
+
var offHour = _digitsAt(v, p, 2); if (offHour === null) return null; p += 2;
|
|
59
|
+
if (v.charAt(p) !== ":") return null; p += 1;
|
|
60
|
+
var offMin = _digitsAt(v, p, 2); if (offMin === null) return null; p += 2;
|
|
61
|
+
offset = { hour: offHour, min: offMin };
|
|
62
|
+
} else {
|
|
63
|
+
return null; // a zone is required
|
|
64
|
+
}
|
|
65
|
+
if (p !== n) return null; // no trailing characters
|
|
66
|
+
return { year: year, month: month, day: day, hour: hour, min: min, sec: sec, offset: offset };
|
|
67
|
+
}
|
|
18
68
|
|
|
19
69
|
// isValid(v) -> boolean: v is a syntactically well-formed AND calendar-valid RFC 3339 date-time string.
|
|
20
70
|
function isValid(v) {
|
|
21
71
|
if (typeof v !== "string") return false;
|
|
22
|
-
var
|
|
23
|
-
if (!
|
|
24
|
-
|
|
25
|
-
if (month < 1 || month > 12) return false;
|
|
72
|
+
var t = _scanDateTime(v);
|
|
73
|
+
if (!t) return false;
|
|
74
|
+
if (t.month < 1 || t.month > 12) return false;
|
|
26
75
|
// Reject a :60 leap second (Node's Date.parse returns NaN for it, so a comparison on such a value
|
|
27
76
|
// would silently pass) and any hour/minute/second out of range.
|
|
28
|
-
if (hour > 23 || min > 59 || sec > 59) return false;
|
|
29
|
-
var leap = (year % 4 === 0 && year % 100 !== 0) || year % 400 === 0;
|
|
77
|
+
if (t.hour > 23 || t.min > 59 || t.sec > 59) return false;
|
|
78
|
+
var leap = (t.year % 4 === 0 && t.year % 100 !== 0) || t.year % 400 === 0;
|
|
30
79
|
var daysInMonth = [31, leap ? 29 : 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31];
|
|
31
|
-
if (day < 1 || day > daysInMonth[month - 1]) return false;
|
|
32
|
-
if (
|
|
80
|
+
if (t.day < 1 || t.day > daysInMonth[t.month - 1]) return false;
|
|
81
|
+
if (t.offset) { if (t.offset.hour > 23 || t.offset.min > 59) return false; } // a numeric zone offset must itself be valid
|
|
33
82
|
return true;
|
|
34
83
|
}
|
|
35
84
|
|
|
@@ -44,20 +93,29 @@ function parse(v, E, code, label) {
|
|
|
44
93
|
// RFC 3339 sec. 5.6 `full-date`: a calendar date with no time component. A distinct production from
|
|
45
94
|
// the date-time above, and deliberately a separate pair of functions instead of a flag: a consumer
|
|
46
95
|
// that expects one and is handed the other has a malformed value, not a permissible variant.
|
|
47
|
-
|
|
96
|
+
// RFC 3339 full-date scanned into its components (YYYY "-" MM "-" DD, nothing after), or null.
|
|
97
|
+
function _scanFullDate(v) {
|
|
98
|
+
var p = 0;
|
|
99
|
+
var year = _digitsAt(v, p, 4); if (year === null) return null; p += 4;
|
|
100
|
+
if (v.charAt(p) !== "-") return null; p += 1;
|
|
101
|
+
var month = _digitsAt(v, p, 2); if (month === null) return null; p += 2;
|
|
102
|
+
if (v.charAt(p) !== "-") return null; p += 1;
|
|
103
|
+
var day = _digitsAt(v, p, 2); if (day === null) return null; p += 2;
|
|
104
|
+
if (p !== v.length) return null;
|
|
105
|
+
return { year: year, month: month, day: day };
|
|
106
|
+
}
|
|
48
107
|
|
|
49
108
|
// isValidDate(v) -> boolean: v is a syntactically well-formed AND calendar-valid RFC 3339 full-date.
|
|
50
109
|
// Calendar validity matters as much as shape: "2021-02-30" parses as a Date in JS by rolling over
|
|
51
110
|
// into March, so a shape-only check would silently accept a day that does not exist.
|
|
52
111
|
function isValidDate(v) {
|
|
53
112
|
if (typeof v !== "string") return false;
|
|
54
|
-
var
|
|
55
|
-
if (!
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
var leap = (year % 4 === 0 && year % 100 !== 0) || year % 400 === 0;
|
|
113
|
+
var t = _scanFullDate(v);
|
|
114
|
+
if (!t) return false;
|
|
115
|
+
if (t.month < 1 || t.month > 12) return false;
|
|
116
|
+
var leap = (t.year % 4 === 0 && t.year % 100 !== 0) || t.year % 400 === 0;
|
|
59
117
|
var daysInMonth = [31, leap ? 29 : 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31];
|
|
60
|
-
return day >= 1 && day <= daysInMonth[month - 1];
|
|
118
|
+
return t.day >= 1 && t.day <= daysInMonth[t.month - 1];
|
|
61
119
|
}
|
|
62
120
|
|
|
63
121
|
// parseDate(v, E, code, label) -> Date at UTC midnight. Anchored explicitly at UTC ("T00:00:00Z")
|
package/lib/schema-c509.js
CHANGED
|
@@ -263,16 +263,37 @@ function _specialText(node) {
|
|
|
263
263
|
// name one certificate. A natively signed certificate is exempt: sec. 3.1.4 says bytes and tag 48
|
|
264
264
|
// there "do not correspond to any predefined text string encoding and may also be used for other attribute
|
|
265
265
|
// types", so no canonical text spelling exists to hold one to.
|
|
266
|
-
|
|
267
|
-
|
|
266
|
+
// Even-length lowercase hex ("HHHH..."), decided by character so no regex match protocol -- a live,
|
|
267
|
+
// replaceable exec -- stands between the canonicalization gate and the value it inspects.
|
|
268
|
+
function _isEvenHexLower(s) {
|
|
269
|
+
var n = s.length;
|
|
270
|
+
if (n < 2 || (n & 1) !== 0) return false;
|
|
271
|
+
for (var i = 0; i < n; i++) {
|
|
272
|
+
var c = s.charCodeAt(i);
|
|
273
|
+
if (!((c >= 48 && c <= 57) || (c >= 97 && c <= 102))) return false; // 0-9 a-f
|
|
274
|
+
}
|
|
275
|
+
return true;
|
|
276
|
+
}
|
|
277
|
+
// EUI-64 text form: eight uppercase hex pairs joined by single hyphens ("HH-HH-...-HH", 23 chars).
|
|
278
|
+
function _isEui64Text(s) {
|
|
279
|
+
if (s.length !== 23) return false;
|
|
280
|
+
for (var i = 0; i < 23; i++) {
|
|
281
|
+
if ((i % 3) === 2) { if (s.charCodeAt(i) !== 45) return false; } // "-" at 2,5,...,20
|
|
282
|
+
else {
|
|
283
|
+
var c = s.charCodeAt(i);
|
|
284
|
+
if (!((c >= 48 && c <= 57) || (c >= 65 && c <= 70))) return false; // 0-9 A-F
|
|
285
|
+
}
|
|
286
|
+
}
|
|
287
|
+
return true;
|
|
288
|
+
}
|
|
268
289
|
function _assertCanonicalSpecialText(node, isNative, E) {
|
|
269
290
|
if (isNative) return;
|
|
270
291
|
if (node.majorType === 3) {
|
|
271
292
|
var t = cbor.read.textString(node);
|
|
272
|
-
if (t.length >= 2 &&
|
|
293
|
+
if (t.length >= 2 && _isEvenHexLower(t)) {
|
|
273
294
|
throw _err(E, "a text attribute value of even-length hex characters must be encoded as a CBOR byte string (draft sec. 3.1.4)");
|
|
274
295
|
}
|
|
275
|
-
if (
|
|
296
|
+
if (_isEui64Text(t)) {
|
|
276
297
|
throw _err(E, "a text attribute value in EUI-64 form must be encoded as a CBOR tag-48 MAC address (draft sec. 3.1.4)");
|
|
277
298
|
}
|
|
278
299
|
return;
|
|
@@ -1973,7 +1994,7 @@ function _reconstructDer(r, sigNode) {
|
|
|
1973
1994
|
// The signature is re-wrapped as a DER ECDSA-Sig-Value from the fixed-width r||s, so only an ECDSA
|
|
1974
1995
|
// signature algorithm is in the type-3 reconstruction covered set (an RSA/EdDSA signature is raw bytes,
|
|
1975
1996
|
// not r||s, so it is rejected). A wrong-length r||s surfaces the caller's typed code.
|
|
1976
|
-
if (
|
|
1997
|
+
if (String(r.signatureAlgorithm.name || "").toLowerCase().indexOf("ecdsa") !== 0) {
|
|
1977
1998
|
throw _err("c509/non-invertible", "type-3 signature reconstruction covers only ECDSA; got " + r.signatureAlgorithm.name);
|
|
1978
1999
|
}
|
|
1979
2000
|
// The fixed-width r||s must split at a real curve field width: P-256/384/521 = 64/96/132 bytes
|
|
@@ -2143,7 +2164,7 @@ function _encAlgorithm(alg, toInt, key) {
|
|
|
2143
2164
|
function _encSpecialText(rdn) {
|
|
2144
2165
|
if (rdn.eui64) return cbor.build.tag(48, cbor.build.byteString(rdn.eui64));
|
|
2145
2166
|
var v = String(rdn.value);
|
|
2146
|
-
if (v.length >= 2 &&
|
|
2167
|
+
if (v.length >= 2 && _isEvenHexLower(v)) return cbor.build.byteString(Buffer.from(v, "hex"));
|
|
2147
2168
|
return cbor.build.textString(v);
|
|
2148
2169
|
}
|
|
2149
2170
|
// A Name -> CBOR: null (issuer only) | a bare SpecialText single utf8 commonName | an array of RDN pairs.
|
|
@@ -2293,9 +2314,15 @@ function _encodeC509Array(r) {
|
|
|
2293
2314
|
// value (draft-20 sec. 3.2.3): an FF-FE-in-the-middle EUI-64 collapses to its 6-byte EUI-48, else the
|
|
2294
2315
|
// 8-byte EUI-64 verbatim. A non-MAC commonName returns null (encoded as text). The exact inverse of
|
|
2295
2316
|
// _macToEui64String, so the reconstruction rebuilds the identical DER commonName string.
|
|
2317
|
+
// The "-" characters removed from a string, by character scan (no regex replace protocol).
|
|
2318
|
+
function _stripHyphens(s) {
|
|
2319
|
+
var out = "";
|
|
2320
|
+
for (var i = 0; i < s.length; i++) { var ch = s.charAt(i); if (ch !== "-") out += ch; }
|
|
2321
|
+
return out;
|
|
2322
|
+
}
|
|
2296
2323
|
function _euiFromCn(value) {
|
|
2297
|
-
if (
|
|
2298
|
-
var bytes = Buffer.from(value
|
|
2324
|
+
if (!_isEui64Text(value)) return null;
|
|
2325
|
+
var bytes = Buffer.from(_stripHyphens(value), "hex");
|
|
2299
2326
|
if (bytes[3] === 0xff && bytes[4] === 0xfe) return Buffer.concat([bytes.subarray(0, 3), bytes.subarray(5)]);
|
|
2300
2327
|
return bytes;
|
|
2301
2328
|
}
|
|
@@ -2415,7 +2442,7 @@ _derToType3 = function (input, opts) {
|
|
|
2415
2442
|
// (A v3 certificate with the extensions field OMITTED is fully supported: sec. 3.1.10 encodes
|
|
2416
2443
|
// an omitted 'extensions' field as an empty CBOR array.)
|
|
2417
2444
|
if (c.version !== 3) throw _err("c509/non-invertible", "C509 covers X.509 v3 certificates; got v" + c.version);
|
|
2418
|
-
if (
|
|
2445
|
+
if (String(c.signatureAlgorithm.name || "").toLowerCase().indexOf("ecdsa") !== 0) throw _err("c509/non-invertible", "type-3 C509 encoding covers only ECDSA-signed certificates; got " + (c.signatureAlgorithm.name || "an unregistered algorithm"));
|
|
2419
2446
|
if (c.subjectPublicKeyInfo.algorithm.name !== "ecPublicKey") throw _err("c509/non-invertible", "type-3 C509 encoding covers only EC (ecPublicKey) certificates in v1; got " + (c.subjectPublicKeyInfo.algorithm.name || "an unregistered algorithm"));
|
|
2420
2447
|
var curveOid = asn1.read.oid(asn1.decode(c.subjectPublicKeyInfo.algorithm.parameters));
|
|
2421
2448
|
var curve = oid.name(curveOid);
|
package/lib/schema-cmc.js
CHANGED
|
@@ -685,7 +685,7 @@ function _finishBody(body, where) {
|
|
|
685
685
|
* d.controls.length; // 0 -- present and empty, which is legal
|
|
686
686
|
*/
|
|
687
687
|
function parsePkiData(input) {
|
|
688
|
-
var root = asn1.decode(
|
|
688
|
+
var root = asn1.decode(guard.bytes.isByteSource(input) ? guard.bytes.source(input, E, "cmc/bad-input", "the CMC DER") : Buffer.from(input));
|
|
689
689
|
var body = schema.walk(PKI_DATA, root, NS).result;
|
|
690
690
|
body.bytes = root.bytes;
|
|
691
691
|
return _finishBody(body, "pkiData");
|
|
@@ -711,7 +711,7 @@ function parsePkiData(input) {
|
|
|
711
711
|
* r.statuses.length; // 0 -- no status control means success is assumed
|
|
712
712
|
*/
|
|
713
713
|
function parsePkiResponse(input) {
|
|
714
|
-
var root = asn1.decode(
|
|
714
|
+
var root = asn1.decode(guard.bytes.isByteSource(input) ? guard.bytes.source(input, E, "cmc/bad-input", "the CMC DER") : Buffer.from(input));
|
|
715
715
|
var body = schema.walk(PKI_RESPONSE, root, NS).result;
|
|
716
716
|
body.bytes = root.bytes;
|
|
717
717
|
return _finishBody(body, "pkiResponse");
|
|
@@ -752,7 +752,7 @@ function parse(input) {
|
|
|
752
752
|
// encapContentInfo is the field this function goes on to read, so requiring it
|
|
753
753
|
// is the same question the code below already asks.
|
|
754
754
|
var parsedCms = (input && typeof input === "object" && !ArrayBuffer.isView(input) &&
|
|
755
|
-
!(input
|
|
755
|
+
!guard.bytes.isByteSource(input) && input.encapContentInfo != null)
|
|
756
756
|
? input : cms.parse(input);
|
|
757
757
|
// The CARRIER is part of the identity, not just the encapsulated content type.
|
|
758
758
|
// RFC 5272 sec. 3.2 / 4.2 admit a SignedData or an AuthenticatedData; other CMS
|