@blamejs/pki 0.8.7 → 0.8.10
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 +137 -76
- package/README.md +3 -2
- package/index.js +5 -0
- package/lib/acme.js +24 -83
- package/lib/cmc-build.js +1 -1
- package/lib/cmc-verify.js +3 -3
- package/lib/cmp-session.js +1 -1
- package/lib/cms-sign.js +3 -3
- package/lib/cms-verify.js +5 -5
- package/lib/constants.js +11 -0
- package/lib/est.js +1 -1
- package/lib/framework-error.js +6 -0
- package/lib/guard-async.js +53 -2
- package/lib/guard-encoding.js +74 -0
- package/lib/guard-identifier.js +74 -1
- package/lib/guard-intrinsic.js +2 -0
- package/lib/guard-limits.js +40 -1
- package/lib/guard-list.js +25 -0
- package/lib/guard-name.js +133 -16
- package/lib/guard-text.js +33 -1
- package/lib/http-transport.js +39 -7
- package/lib/identity-match.js +561 -0
- package/lib/ip-utils.js +52 -1
- package/lib/key.js +250 -5
- package/lib/ocsp.js +125 -22
- package/lib/oid.js +1 -1
- package/lib/path-validate.js +199 -61
- package/lib/revocation-fetch.js +772 -0
- package/lib/schema-all.js +124 -0
- package/lib/schema-attrcert.js +4 -2
- package/lib/schema-crmf.js +4 -2
- package/lib/schema-pkcs1.js +335 -0
- package/lib/schema-pkcs8.js +15 -0
- package/lib/schema-pkix.js +322 -40
- package/lib/schema-sec1.js +244 -0
- package/lib/schema-tsp.js +4 -2
- package/lib/schema-x509.js +4 -2
- package/lib/sigstore.js +35 -21
- package/lib/trust.js +2 -2
- package/lib/tsp-sign.js +21 -14
- package/lib/webauthn.js +4 -4
- package/lib/webcrypto.js +17 -5
- package/package.json +1 -1
- package/sbom.cdx.json +6 -6
package/lib/schema-pkix.js
CHANGED
|
@@ -8,6 +8,7 @@ var constants = require("./constants");
|
|
|
8
8
|
var schema = require("./schema-engine");
|
|
9
9
|
var guard = require("./guard-all");
|
|
10
10
|
var intrinsic = require("./guard-intrinsic");
|
|
11
|
+
var frameworkError = require("./framework-error");
|
|
11
12
|
var _hasOwn = intrinsic.hasOwn;
|
|
12
13
|
var _forEach = intrinsic.forEach;
|
|
13
14
|
var _map = intrinsic.map;
|
|
@@ -59,26 +60,109 @@ var ct = require("./ct");
|
|
|
59
60
|
var _PEM_BEGIN = "-----BEGIN ";
|
|
60
61
|
var _PEM_END = "-----END ";
|
|
61
62
|
var _PEM_DASHES = "-----";
|
|
63
|
+
var _BOM_LATIN1 = intrinsic.fromCharCode(0xef) + intrinsic.fromCharCode(0xbb) + intrinsic.fromCharCode(0xbf);
|
|
64
|
+
var _BOM_UTF16 = intrinsic.fromCharCode(0xfeff);
|
|
65
|
+
|
|
66
|
+
/** @internal How many characters of a byte-order mark open `text`. The mark reaches the scanner in
|
|
67
|
+
* whichever form the caller's decode produced: the three characters EF BB BF from a latin1 read or
|
|
68
|
+
* a Buffer, and the single U+FEFF from a UTF-8 one. Knowing one form and not the other means
|
|
69
|
+
* walking past the first boundary line of a file written in the other, which drops its first
|
|
70
|
+
* object without saying so. */
|
|
71
|
+
function _bomWidth(text) {
|
|
72
|
+
if (_strSlice(text, 0, 3) === _BOM_LATIN1) return 3;
|
|
73
|
+
return _charAt(text, 0) === _BOM_UTF16 ? 1 : 0;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/** @internal The offset just past the end of the line `at` sits on, and the offset of the next
|
|
77
|
+
* line's first character. A boundary is recognized at a line start only, which is what keeps prose
|
|
78
|
+
* quoting one mid-sentence from being read as a block (RFC 7468 sec. 2). */
|
|
79
|
+
function _lineEnd(text, at) {
|
|
80
|
+
var i = at;
|
|
81
|
+
while (i < text.length) {
|
|
82
|
+
var c = _charCodeAt(text, i);
|
|
83
|
+
if (c === 13 || c === 10) return i;
|
|
84
|
+
i += 1;
|
|
85
|
+
}
|
|
86
|
+
return i;
|
|
87
|
+
}
|
|
88
|
+
function _nextLineStart(text, lineEnd) {
|
|
89
|
+
if (lineEnd >= text.length) return text.length;
|
|
90
|
+
if (_charCodeAt(text, lineEnd) === 13 && _charCodeAt(text, lineEnd + 1) === 10) return lineEnd + 2;
|
|
91
|
+
return lineEnd + 1;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
/** @internal RFC 7468 sec. 3: `labelchar = %x21-2C / %x2E-7E`, every printable character except
|
|
95
|
+
* the space and the hyphen-minus, and `label = [ labelchar *( ["-" / SP] labelchar ) ]`. So a
|
|
96
|
+
* label may be empty, may carry a hyphen or a space BETWEEN two of its characters, and may not
|
|
97
|
+
* open or close with one or carry two in a row. Reading a narrower set makes a conforming file
|
|
98
|
+
* look like it holds no blocks. */
|
|
99
|
+
function _isLabelChar(c) { return (c >= 0x21 && c <= 0x2c) || (c >= 0x2e && c <= 0x7e); }
|
|
100
|
+
function _isRfc7468Label(text, from, to) {
|
|
101
|
+
if (from === to) return true;
|
|
102
|
+
var prevWasChar = false;
|
|
103
|
+
for (var i = from; i < to; i++) {
|
|
104
|
+
var c = _charCodeAt(text, i);
|
|
105
|
+
if (_isLabelChar(c)) { prevWasChar = true; continue; }
|
|
106
|
+
if (c !== 0x2d && c !== 0x20) return false;
|
|
107
|
+
if (!prevWasChar) return false;
|
|
108
|
+
if (i + 1 === to) return false;
|
|
109
|
+
prevWasChar = false;
|
|
110
|
+
}
|
|
111
|
+
return prevWasChar;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
/** @internal A boundary line: the keyword, a label, five hyphens, and the end of the line. RFC 7468
|
|
115
|
+
* sec. 3's parsing grammar ends a line with `W* eol`, so whitespace after the hyphens belongs to
|
|
116
|
+
* the line ending. Every ASCII whitespace character is taken that way, space, tab, vertical tab and
|
|
117
|
+
* form feed alike, because the cost of the two readings is not symmetric: treating one of them as
|
|
118
|
+
* content makes the object behind that boundary invisible, so a reader of one object hands back the
|
|
119
|
+
* first object of a file holding two and says nothing about the second. Anything that is not
|
|
120
|
+
* whitespace after the hyphens is content, and the line is prose. The label is everything between
|
|
121
|
+
* the keyword and the hyphens, because a label may itself carry a hyphen and only the line's end
|
|
122
|
+
* says where it stops. */
|
|
123
|
+
function _boundaryLabel(text, start, end, keyword) {
|
|
124
|
+
var p = start + keyword.length;
|
|
125
|
+
if (p > end || _strSlice(text, start, p) !== keyword) return null;
|
|
126
|
+
var lineEnd = end;
|
|
127
|
+
while (lineEnd > p) {
|
|
128
|
+
var w = _charCodeAt(text, lineEnd - 1);
|
|
129
|
+
if (w !== 0x20 && w !== 0x09 && w !== 0x0b && w !== 0x0c) break;
|
|
130
|
+
lineEnd -= 1;
|
|
131
|
+
}
|
|
132
|
+
var labelEnd = lineEnd - 5;
|
|
133
|
+
if (labelEnd < p || _strSlice(text, labelEnd, lineEnd) !== _PEM_DASHES) return null;
|
|
134
|
+
if (!_isRfc7468Label(text, p, labelEnd)) return null;
|
|
135
|
+
return _strSlice(text, p, labelEnd);
|
|
136
|
+
}
|
|
62
137
|
|
|
138
|
+
/** @internal One forward pass over the lines from `from`. The END is matched as a boundary line in
|
|
139
|
+
* its own right and its label is read off that line, rather than searching for the BEGIN label's
|
|
140
|
+
* own marker: searching for it runs a block's body on to the next matching END, swallowing whatever
|
|
141
|
+
* lies between, and rescanning from `bi + 1` after a miss walks the remainder of the file once per
|
|
142
|
+
* BEGIN line, which is quadratic on text a parse door accepts from anywhere. */
|
|
63
143
|
function _findPemBlock(text, from) {
|
|
64
|
-
var
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
144
|
+
var at = from;
|
|
145
|
+
/** @internal A byte-order mark is not part of the first line's content, so a boundary on that
|
|
146
|
+
* line is still at a line start. */
|
|
147
|
+
if (at === 0) at = _bomWidth(text);
|
|
148
|
+
while (at < text.length) {
|
|
149
|
+
var end = _lineEnd(text, at);
|
|
150
|
+
var label = _boundaryLabel(text, at, end, _PEM_BEGIN);
|
|
151
|
+
if (label === null) { at = _nextLineStart(text, end); continue; }
|
|
152
|
+
var bodyStart = _nextLineStart(text, end);
|
|
153
|
+
var scan = bodyStart;
|
|
154
|
+
while (scan < text.length) {
|
|
155
|
+
var lineEnd = _lineEnd(text, scan);
|
|
156
|
+
var endLabel = _boundaryLabel(text, scan, lineEnd, _PEM_END);
|
|
157
|
+
if (endLabel !== null) {
|
|
158
|
+
return { index: at, label: label, endLabel: endLabel,
|
|
159
|
+
body: _strSlice(text, bodyStart, scan), end: _nextLineStart(text, lineEnd) };
|
|
160
|
+
}
|
|
161
|
+
scan = _nextLineStart(text, lineEnd);
|
|
73
162
|
}
|
|
74
|
-
|
|
75
|
-
var label = _strSlice(text, ls, p);
|
|
76
|
-
var bodyStart = p + 5;
|
|
77
|
-
var endMarker = _PEM_END + label + _PEM_DASHES;
|
|
78
|
-
var ei = _strIndexOf(text, endMarker, bodyStart);
|
|
79
|
-
if (ei === -1) { searchFrom = bi + 1; continue; }
|
|
80
|
-
return { index: bi, label: label, body: _strSlice(text, bodyStart, ei), end: ei + endMarker.length };
|
|
163
|
+
return null;
|
|
81
164
|
}
|
|
165
|
+
return null;
|
|
82
166
|
}
|
|
83
167
|
|
|
84
168
|
function _stripB64Whitespace(s) {
|
|
@@ -110,11 +194,45 @@ function pemDecode(text, label, PemError) {
|
|
|
110
194
|
});
|
|
111
195
|
var m = _findPemBlock(text, 0);
|
|
112
196
|
if (!m) throw new PemError("pem/no-block", "no PEM block found");
|
|
113
|
-
if (
|
|
197
|
+
if (m.endLabel !== m.label) {
|
|
198
|
+
throw new PemError("pem/label-mismatch", "a block opened as " + _stringify(m.label) + " is closed as " + _stringify(m.endLabel) + " (RFC 7468 sec. 2 requires the same label on both boundaries)");
|
|
199
|
+
}
|
|
200
|
+
/** @internal `null` opts out of the label check. The empty string is a LABEL, which RFC 7468
|
|
201
|
+
* sec. 3 writes as `[ labelchar *( ... ) ]`, so a caller asking for it is asking for the empty
|
|
202
|
+
* label and not asking for nothing. */
|
|
203
|
+
if (label !== null && label !== undefined && m.label !== label) {
|
|
204
|
+
throw new PemError("pem/label-mismatch", "expected " + _stringify(label) + " block, got " + _stringify(m.label));
|
|
205
|
+
}
|
|
206
|
+
var rest = _countBlocksFrom(text, m.end);
|
|
207
|
+
if (rest.opened !== null) {
|
|
208
|
+
throw new PemError("pem/unterminated-block", "a block opened as " + _stringify(rest.opened) +
|
|
209
|
+
" after the first object is never closed, so what the input holds cannot be read; this verb reads one object");
|
|
210
|
+
}
|
|
211
|
+
if (rest.closed > 0) {
|
|
212
|
+
throw new PemError("pem/multiple-blocks", "the input holds " + (rest.closed + 1) +
|
|
213
|
+
" PEM blocks and this verb reads one object; read a file of several objects with pki.schema.pem.decodeBundle");
|
|
214
|
+
}
|
|
114
215
|
var b64 = _stripB64Whitespace(m.body);
|
|
115
216
|
return guard.encoding.base64(b64, null, function (c, msg) { return new PemError(c, msg); }, "pem/bad-base64", "PEM base64 body");
|
|
116
217
|
}
|
|
117
218
|
|
|
219
|
+
/** @internal What follows `from`: how many further blocks close, capped at the bundle ceiling so
|
|
220
|
+
* the count in a refusal is bounded, and the label of a block that opens and never closes. A
|
|
221
|
+
* trailing boundary with no end is an object too, and reading past it would take the first block
|
|
222
|
+
* and report nothing about what the rest of the file was trying to be. Each scan resumes where the
|
|
223
|
+
* last block ended, so the walk over the remainder is linear in the text rather than per block. */
|
|
224
|
+
function _countBlocksFrom(text, from) {
|
|
225
|
+
var n = 0, at = from, m;
|
|
226
|
+
while (n < constants.LIMITS.PEM_MAX_BLOCKS) {
|
|
227
|
+
m = _findPemBlockOrOpen(text, at);
|
|
228
|
+
if (m === null) break;
|
|
229
|
+
if (m.unterminated) return { closed: n, opened: m.label };
|
|
230
|
+
n++;
|
|
231
|
+
at = m.end;
|
|
232
|
+
}
|
|
233
|
+
return { closed: n, opened: null };
|
|
234
|
+
}
|
|
235
|
+
|
|
118
236
|
function pemDecodeAll(text, label, PemError) {
|
|
119
237
|
label = label || "CERTIFICATE";
|
|
120
238
|
text = guard.text.decode(text, constants.LIMITS.PEM_MAX_BYTES, PemError, {
|
|
@@ -124,6 +242,9 @@ function pemDecodeAll(text, label, PemError) {
|
|
|
124
242
|
var lastEnd = 0, m;
|
|
125
243
|
while ((m = _findPemBlock(text, lastEnd)) !== null) {
|
|
126
244
|
if (_hasNonWhitespace(_strSlice(text, lastEnd, m.index))) throw new PemError("pem/explanatory-text", "explanatory text is not permitted around PEM blocks (RFC 8555 sec. 9.1)");
|
|
245
|
+
if (m.endLabel !== m.label) {
|
|
246
|
+
throw new PemError("pem/label-mismatch", "a block opened as " + _stringify(m.label) + " is closed as " + _stringify(m.endLabel) + " (RFC 7468 sec. 2 requires the same label on both boundaries)");
|
|
247
|
+
}
|
|
127
248
|
if (m.label !== label) throw new PemError("pem/label-mismatch", "expected " + _stringify(label) + " block, got " + _stringify(m.label));
|
|
128
249
|
var b64 = _stripB64Whitespace(m.body);
|
|
129
250
|
_push(blocks, guard.encoding.base64(b64, null, function (c, msg) { return new PemError(c, msg); }, "pem/bad-base64", "PEM base64 body"));
|
|
@@ -157,29 +278,173 @@ function pemEncode(der, label, PemError) {
|
|
|
157
278
|
return "-----BEGIN " + label + "-----\n" + b64 + "\n-----END " + label + "-----\n";
|
|
158
279
|
}
|
|
159
280
|
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
281
|
+
/** @internal The RFC 1421 headers an OpenSSL traditional encrypted key carries between the
|
|
282
|
+
* boundary and the body. They are not base64, so a reader that does not know them reports a
|
|
283
|
+
* transfer fault and sends the operator looking for the wrong thing. */
|
|
284
|
+
function _hasLegacyHeaders(body) {
|
|
285
|
+
var at = 0;
|
|
286
|
+
while (at < body.length) {
|
|
287
|
+
var end = _lineEnd(body, at);
|
|
288
|
+
var line = _strSlice(body, at, end);
|
|
289
|
+
if (_strSlice(line, 0, 10) === "Proc-Type:" || _strSlice(line, 0, 9) === "DEK-Info:") return true;
|
|
290
|
+
if (line !== "" && _strIndexOf(line, ":") === -1) return false;
|
|
291
|
+
at = _nextLineStart(body, end);
|
|
292
|
+
}
|
|
293
|
+
return false;
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
/** @internal A bound a caller may tighten and may not raise, which is how every other cap in this
|
|
297
|
+
* file reads: the ceiling is the toolkit's and the floor is the caller's business. */
|
|
298
|
+
function _blockCap(given, ceiling, what, PemError) {
|
|
299
|
+
if (given === undefined || given === null) return ceiling;
|
|
300
|
+
if (typeof given !== "number" || !intrinsic.isSafeInteger(given) || given < 1) {
|
|
301
|
+
throw new PemError("pem/bad-input", what + " must be a positive whole number");
|
|
302
|
+
}
|
|
303
|
+
if (given > ceiling) {
|
|
304
|
+
throw new PemError("pem/bad-input", what + " may only tighten the " + ceiling + " this toolkit reads, not raise it");
|
|
305
|
+
}
|
|
306
|
+
return given;
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
/** @internal Reads every encapsulated block of one text (RFC 7468 sec. 2). Explanatory text before,
|
|
310
|
+
* between and after the blocks is text, which is the difference between this and the sec. 9.1 chain
|
|
311
|
+
* profile `pemDecodeAll` enforces. Each row carries the label that named the object, because the
|
|
312
|
+
* label is the only thing separating a CERTIFICATE from a TRUSTED CERTIFICATE or a PRIVATE KEY, and
|
|
313
|
+
* the byte offset it started at, so a fault in a file of hundreds names the one object it is about. */
|
|
314
|
+
function pemDecodeBundle(text, opts, PemError, detect) {
|
|
315
|
+
opts = opts || {};
|
|
316
|
+
var maxBlocks = _blockCap(opts.maxBlocks, constants.LIMITS.PEM_MAX_BLOCKS, "maxBlocks", PemError);
|
|
317
|
+
var maxDecoded = _blockCap(opts.maxDecodedBytes, constants.LIMITS.PEM_MAX_DECODED_BYTES, "maxDecodedBytes", PemError);
|
|
318
|
+
var route = opts.route === true;
|
|
319
|
+
text = guard.text.decode(text, constants.LIMITS.PEM_MAX_BYTES, PemError, {
|
|
320
|
+
charset: "latin1", tooLarge: "pem/too-large", badInput: "pem/bad-input", label: "PEM input",
|
|
321
|
+
});
|
|
322
|
+
|
|
323
|
+
var rows = [], at = 0, decoded = 0;
|
|
324
|
+
for (;;) {
|
|
325
|
+
var m = _findPemBlockOrOpen(text, at);
|
|
326
|
+
if (m === null) break;
|
|
327
|
+
if (m.unterminated) {
|
|
328
|
+
throw new PemError("pem/unterminated-block", "a block opened as " + _stringify(m.label) +
|
|
329
|
+
" at byte " + m.index + " is never closed (RFC 7468 sec. 2)");
|
|
330
|
+
}
|
|
331
|
+
if (m.endLabel !== m.label) {
|
|
332
|
+
throw new PemError("pem/end-mismatch", "the block at byte " + m.index + " is opened as " +
|
|
333
|
+
_stringify(m.label) + " and closed as " + _stringify(m.endLabel) + " (RFC 7468 sec. 2)");
|
|
334
|
+
}
|
|
335
|
+
if (rows.length === maxBlocks) {
|
|
336
|
+
throw new PemError("pem/too-many-blocks", "the file carries more than the " + maxBlocks + " objects this read admits");
|
|
337
|
+
}
|
|
338
|
+
if (_hasLegacyHeaders(m.body)) {
|
|
339
|
+
throw new PemError("pem/legacy-headers", "the object at byte " + m.index +
|
|
340
|
+
" carries RFC 1421 Proc-Type and DEK-Info headers, so its body is encrypted under a passphrase this reader does not take");
|
|
341
|
+
}
|
|
342
|
+
var b64 = _stripB64Whitespace(m.body);
|
|
343
|
+
var der = guard.encoding.base64(b64, null, function (c, msg) {
|
|
344
|
+
return new PemError(c, "object " + rows.length + " at byte " + m.index + ": " + msg);
|
|
345
|
+
}, "pem/bad-base64", "PEM base64 body");
|
|
346
|
+
decoded += der.length;
|
|
347
|
+
if (decoded > maxDecoded) {
|
|
348
|
+
throw new PemError("pem/too-large", "the objects of this file decode to more than the " +
|
|
349
|
+
maxDecoded + " bytes this read admits");
|
|
167
350
|
}
|
|
351
|
+
var row = { index: rows.length, offset: m.index, label: m.label, der: der };
|
|
352
|
+
if (route) {
|
|
353
|
+
var name = detect(der, m.label);
|
|
354
|
+
row.format = _labelFormat(m.label, name, m.index, PemError);
|
|
355
|
+
}
|
|
356
|
+
_push(rows, row);
|
|
357
|
+
at = m.end;
|
|
358
|
+
}
|
|
359
|
+
if (rows.length === 0) throw new PemError("pem/no-block", "no PEM block found");
|
|
360
|
+
return rows;
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
/** @internal What the label claims, against what the bytes are. Deciding silently is the case
|
|
364
|
+
* RFC 7468 sec. 2's security considerations point at, so a disagreement is a verdict. The two
|
|
365
|
+
* private-key structures RFC 5958 defines each have their own label (RFC 7468 sec. 10 and
|
|
366
|
+
* sec. 11) and their own detector, so neither is read as the other. */
|
|
367
|
+
function _labelFormat(label, detected, offset, PemError) {
|
|
368
|
+
var claimed = PEM_LABEL_FORMAT[label];
|
|
369
|
+
if (claimed === undefined) return null;
|
|
370
|
+
if (detected !== claimed) {
|
|
371
|
+
throw new PemError("pem/label-structure-mismatch", "the object at byte " + offset + " is labeled " +
|
|
372
|
+
_stringify(label) + ", which names the " + claimed + " structure, and its bytes are " +
|
|
373
|
+
(detected === null ? "no structure this toolkit reads" : "the " + detected + " structure"));
|
|
374
|
+
}
|
|
375
|
+
return claimed;
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
/** @internal The RFC 7468 sec. 5 to sec. 13 labels this toolkit has a parser for. A label outside
|
|
379
|
+
* it is a known object of an unknown kind, which is a row rather than a refusal. The legacy labels
|
|
380
|
+
* sec. 5, sec. 6 and sec. 8 say a generator SHOULD NOT emit are deliberately absent. */
|
|
381
|
+
var PEM_LABEL_FORMAT = intrinsic.assign(intrinsic.create(null), {
|
|
382
|
+
"CERTIFICATE": "x509",
|
|
383
|
+
"X509 CRL": "crl",
|
|
384
|
+
"CERTIFICATE REQUEST": "csr",
|
|
385
|
+
"NEW CERTIFICATE REQUEST": "csr",
|
|
386
|
+
"PRIVATE KEY": "pkcs8",
|
|
387
|
+
"ENCRYPTED PRIVATE KEY": "pkcs8-encrypted",
|
|
388
|
+
"RSA PRIVATE KEY": "pkcs1",
|
|
389
|
+
"RSA PUBLIC KEY": "pkcs1-public",
|
|
390
|
+
"EC PRIVATE KEY": "sec1",
|
|
391
|
+
"CMS": "cms",
|
|
392
|
+
"ATTRIBUTE CERTIFICATE": "attrcert",
|
|
393
|
+
"PKCS12": "pkcs12",
|
|
394
|
+
});
|
|
395
|
+
|
|
396
|
+
/** @internal One block, or the fact that one was opened and never closed. `_findPemBlock` answers
|
|
397
|
+
* null for both "no more blocks" and "a block that does not end", and a reader of a whole file has
|
|
398
|
+
* to tell those apart to name the fault. */
|
|
399
|
+
function _findPemBlockOrOpen(text, from) {
|
|
400
|
+
var m = _findPemBlock(text, from);
|
|
401
|
+
if (m !== null) return m;
|
|
402
|
+
var at = from;
|
|
403
|
+
if (at === 0) at = _bomWidth(text);
|
|
404
|
+
while (at < text.length) {
|
|
405
|
+
var end = _lineEnd(text, at);
|
|
406
|
+
var label = _boundaryLabel(text, at, end, _PEM_BEGIN);
|
|
407
|
+
if (label !== null) return { unterminated: true, index: at, label: label };
|
|
408
|
+
at = _nextLineStart(text, end);
|
|
409
|
+
}
|
|
410
|
+
return null;
|
|
411
|
+
}
|
|
412
|
+
|
|
413
|
+
/** @internal Writes an ordered list of objects to one text. Each label goes through the same
|
|
414
|
+
* validation the single-object writer applies, so a label carrying a boundary cannot put a block
|
|
415
|
+
* into the file that the caller never named. */
|
|
416
|
+
function pemEncodeBundle(objects, PemError) {
|
|
417
|
+
if (!intrinsic.isArray(objects)) {
|
|
418
|
+
throw new PemError("pem/bad-input", "encodeBundle takes an array of { label, der } objects");
|
|
419
|
+
}
|
|
420
|
+
var n = objects.length, out = "";
|
|
421
|
+
if (n === 0) throw new PemError("pem/bad-input", "encodeBundle takes at least one object");
|
|
422
|
+
if (n > constants.LIMITS.PEM_MAX_BLOCKS) {
|
|
423
|
+
throw new PemError("pem/too-many-blocks", "more than the " + constants.LIMITS.PEM_MAX_BLOCKS + " objects this writer admits");
|
|
424
|
+
}
|
|
425
|
+
for (var i = 0; i < n; i++) {
|
|
426
|
+
var o = objects[i];
|
|
427
|
+
if (o === null || typeof o !== "object") {
|
|
428
|
+
throw new PemError("pem/bad-input", "object " + i + " must be { label, der }");
|
|
429
|
+
}
|
|
430
|
+
out += pemEncode(o.der, o.label, PemError);
|
|
168
431
|
}
|
|
169
|
-
if (i > runStart) out += _strSlice(s, runStart, i);
|
|
170
432
|
return out;
|
|
171
433
|
}
|
|
172
434
|
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
435
|
+
/** @internal The entity doors (a signer certificate, a TSA certificate, a CMS message given as PEM)
|
|
436
|
+
* read through the same scanner every parse door reads through, so the two cannot disagree about
|
|
437
|
+
* the same bytes. `null` means the file holds no block under `label`, which is what lets a caller
|
|
438
|
+
* try a second label. Every other fault is the caller's own `E(code, message)`, so the verb keeps
|
|
439
|
+
* its error code while the message names what the file actually is. */
|
|
440
|
+
function pemDecodeLenient(text, label, E, code) {
|
|
441
|
+
try {
|
|
442
|
+
return pemDecode(text, label, frameworkError.PemError);
|
|
443
|
+
} catch (e) {
|
|
444
|
+
if (e && (e.code === "pem/no-block" || e.code === "pem/label-mismatch")) return null;
|
|
445
|
+
if (typeof E === "function") throw E(code, "the PEM input is not one " + label + " object: " + e.message);
|
|
446
|
+
throw e;
|
|
447
|
+
}
|
|
183
448
|
}
|
|
184
449
|
|
|
185
450
|
/** @internal Every refusal below is built from opts, so opts is checked before the first one can be
|
|
@@ -196,27 +461,42 @@ function _requireCoerceOpts(opts) {
|
|
|
196
461
|
}
|
|
197
462
|
}
|
|
198
463
|
|
|
464
|
+
/** @internal The largest input either arm of `coerceToDer` could go on to accept: PEM text for the
|
|
465
|
+
* armored arm, DER for the other. Past it neither arm can succeed, so the size is decided first. */
|
|
466
|
+
var _COERCE_MAX_BYTES = constants.LIMITS.PEM_MAX_BYTES > constants.LIMITS.DER_MAX_BYTES
|
|
467
|
+
? constants.LIMITS.PEM_MAX_BYTES : constants.LIMITS.DER_MAX_BYTES;
|
|
468
|
+
|
|
199
469
|
function coerceToDer(input, opts) {
|
|
200
470
|
_requireCoerceOpts(opts);
|
|
201
471
|
if (typeof input === "string") return pemDecode(input, opts.pemLabel, opts.PemError);
|
|
202
472
|
if (guard.bytes.isByteSource(input)) {
|
|
203
473
|
var buf = guard.bytes.source(input, opts.ErrorClass, opts.prefix + "/bad-input", "parse");
|
|
474
|
+
/** @internal The ceiling is applied before the armor probe rather than after it. The probe
|
|
475
|
+
* walks until the first byte no text file carries, which on DER is its first byte and on
|
|
476
|
+
* printable input is the whole of it, so a buffer past every cap either arm could accept
|
|
477
|
+
* would otherwise be read end to end on the way to being refused for its size. */
|
|
478
|
+
guard.limits.byteCap(buf, _COERCE_MAX_BYTES, function (c, m) { return new opts.ErrorClass(c, m); },
|
|
479
|
+
opts.prefix + "/too-large", "parse input");
|
|
204
480
|
return _isPemArmor(buf) ? pemDecode(buf, opts.pemLabel, opts.PemError) : buf;
|
|
205
481
|
}
|
|
206
482
|
throw new opts.ErrorClass(opts.prefix + "/bad-input", "parse expects a DER BufferSource (Buffer, typed array, DataView, or ArrayBuffer) or a PEM string");
|
|
207
483
|
}
|
|
208
484
|
|
|
485
|
+
/** @internal Whether a buffer is armored text rather than DER. Read from a fixed window at the
|
|
486
|
+
* front, the answer is wrong for a file whose explanatory text is longer than the window, and the
|
|
487
|
+
* caller then hands PEM to the DER decoder and reports the DER as malformed. So the whole buffer is
|
|
488
|
+
* walked, and it costs nothing on DER: the walk stops at the first byte no text file carries, which
|
|
489
|
+
* for a DER SEQUENCE is its first byte. */
|
|
209
490
|
function _isPemArmor(buf) {
|
|
210
|
-
var
|
|
211
|
-
var
|
|
212
|
-
if (idx === -1) return false;
|
|
213
|
-
for (var i = 0; i < idx; i++) {
|
|
491
|
+
var n = buf.length;
|
|
492
|
+
for (var i = 0; i < n; i++) {
|
|
214
493
|
var c = buf[i];
|
|
215
494
|
var textByte = (c >= 0x20 && c < 0x7f) || c === 0x09 || c === 0x0a || c === 0x0d ||
|
|
216
495
|
c === 0xef || c === 0xbb || c === 0xbf;
|
|
217
496
|
if (!textByte) return false;
|
|
497
|
+
if (c === 0x2d && _bufToString(_subarray(buf, i, i + 10), "latin1") === "-----BEGIN") return true;
|
|
218
498
|
}
|
|
219
|
-
return
|
|
499
|
+
return false;
|
|
220
500
|
}
|
|
221
501
|
|
|
222
502
|
/** @internal The codec's resource refusals, as distinct from its malformed-input refusals. A
|
|
@@ -1585,6 +1865,8 @@ module.exports = {
|
|
|
1585
1865
|
dnsNameProblem: dnsNameProblem,
|
|
1586
1866
|
pemDecode: pemDecode,
|
|
1587
1867
|
pemDecodeAll: pemDecodeAll,
|
|
1868
|
+
pemDecodeBundle: pemDecodeBundle,
|
|
1869
|
+
pemEncodeBundle: pemEncodeBundle,
|
|
1588
1870
|
pemDecodeLenient: pemDecodeLenient,
|
|
1589
1871
|
pemEncode: pemEncode,
|
|
1590
1872
|
isJsWhitespace: _isJsWhitespace,
|