@blamejs/pki 0.6.14 → 0.6.16
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +27 -1
- package/lib/acme.js +59 -815
- package/lib/asn1-der.js +7 -245
- package/lib/attrcert-sign.js +12 -260
- package/lib/attrcert-verify.js +1 -7
- package/lib/byte-reader.js +0 -8
- package/lib/byte-writer.js +0 -11
- package/lib/cbor-det.js +0 -54
- package/lib/cmc-build.js +15 -357
- package/lib/cmc-verify.js +6 -239
- package/lib/cmp-build.js +41 -317
- package/lib/cmp-session.js +73 -760
- package/lib/cmp-verify.js +32 -488
- package/lib/cms-compress.js +3 -32
- package/lib/cms-decrypt.js +29 -294
- package/lib/cms-encrypt.js +14 -234
- package/lib/cms-sign.js +29 -324
- package/lib/cms-verify.js +9 -500
- package/lib/composite-kem.js +2 -102
- package/lib/composite-sig.js +1 -77
- package/lib/constants.js +0 -249
- package/lib/crl-sign.js +9 -237
- package/lib/crl-verify.js +1 -10
- package/lib/crmf-sign.js +19 -198
- package/lib/crmf-verify.js +1 -12
- package/lib/csr-sign.js +3 -34
- package/lib/csr-verify.js +1 -12
- package/lib/ct.js +28 -253
- package/lib/edwards-point.js +10 -44
- package/lib/est.js +28 -764
- package/lib/framework-error.js +4 -182
- package/lib/guard-all.js +1 -58
- package/lib/guard-async.js +1 -36
- package/lib/guard-bytes.js +6 -650
- package/lib/guard-compress.js +5 -102
- package/lib/guard-crypto.js +1 -40
- package/lib/guard-der.js +2 -93
- package/lib/guard-encoding.js +2 -60
- package/lib/guard-header.js +1 -36
- package/lib/guard-identifier.js +7 -531
- package/lib/guard-intrinsic.js +1 -150
- package/lib/guard-json.js +10 -73
- package/lib/guard-limits.js +1 -77
- package/lib/guard-list.js +1 -49
- package/lib/guard-name.js +3 -209
- package/lib/guard-parsed.js +7 -335
- package/lib/guard-range.js +1 -71
- package/lib/guard-secret.js +1 -102
- package/lib/guard-text.js +1 -64
- package/lib/guard-time.js +1 -97
- package/lib/hpke.js +3 -105
- package/lib/http-digest.js +14 -142
- package/lib/http-retry-after.js +1 -47
- package/lib/http-transport.js +21 -131
- package/lib/inspect.js +30 -284
- package/lib/ip-utils.js +6 -47
- package/lib/jose.js +7 -142
- package/lib/key.js +2 -67
- package/lib/lint.js +9 -194
- package/lib/merkle.js +1 -27
- package/lib/mime.js +9 -82
- package/lib/ocsp-request-verify.js +1 -14
- package/lib/ocsp-verify.js +1 -82
- package/lib/ocsp.js +11 -169
- package/lib/oid.js +5 -337
- package/lib/path-validate.js +103 -1492
- package/lib/pbes2.js +2 -89
- package/lib/pkcs12-build.js +31 -379
- package/lib/pki-build.js +64 -290
- package/lib/rc2.js +2 -18
- package/lib/rfc3339.js +4 -27
- package/lib/scep.js +140 -286
- package/lib/schema-all.js +0 -122
- package/lib/schema-attrcert.js +4 -146
- package/lib/schema-c509.js +153 -802
- package/lib/schema-cmc.js +5 -220
- package/lib/schema-cmp.js +2 -223
- package/lib/schema-cms.js +13 -427
- package/lib/schema-crl.js +5 -68
- package/lib/schema-crmf.js +6 -167
- package/lib/schema-csr.js +1 -54
- package/lib/schema-csrattrs.js +2 -87
- package/lib/schema-engine.js +0 -152
- package/lib/schema-ocsp.js +0 -117
- package/lib/schema-pkcs12.js +2 -93
- package/lib/schema-pkcs8.js +3 -35
- package/lib/schema-pkix.js +24 -655
- package/lib/schema-smime.js +0 -54
- package/lib/schema-tsp.js +3 -99
- package/lib/schema-x509.js +4 -63
- package/lib/shbs.js +11 -95
- package/lib/sign-scheme.js +2 -97
- package/lib/sigstore.js +9 -169
- package/lib/sleep.js +2 -8
- package/lib/smime.js +15 -408
- package/lib/tls-cert-compress.js +0 -62
- package/lib/trust.js +19 -185
- package/lib/tsp-sign.js +16 -270
- package/lib/validator-all.js +1 -30
- package/lib/validator-attcert.js +2 -46
- package/lib/validator-cose.js +2 -153
- package/lib/validator-keydesc.js +1 -37
- package/lib/validator-sig.js +1 -46
- package/lib/validator-tls.js +1 -88
- package/lib/validator-tpm.js +19 -122
- package/lib/verify-seam.js +1 -24
- package/lib/webauthn-mds.js +3 -400
- package/lib/webauthn.js +12 -833
- package/lib/webcrypto.js +5 -485
- package/lib/x509-sign.js +4 -108
- package/package.json +1 -1
- package/sbom.cdx.json +6 -6
package/lib/trust.js
CHANGED
|
@@ -41,8 +41,6 @@ var errors = require("./framework-error");
|
|
|
41
41
|
var asn1 = require("./asn1-der");
|
|
42
42
|
var guard = require("./guard-all");
|
|
43
43
|
var intrinsic = require("./guard-intrinsic");
|
|
44
|
-
// Own-key membership through an operation taken at load. Written out, the question reads two
|
|
45
|
-
// replaceable properties to answer one thing, and here it decides which trust bits an anchor carries.
|
|
46
44
|
var _hasOwn = intrinsic.hasOwn;
|
|
47
45
|
var _charCodeAt = intrinsic.uncurry(String.prototype.charCodeAt);
|
|
48
46
|
var _strSlice = intrinsic.uncurry(String.prototype.slice);
|
|
@@ -54,40 +52,27 @@ var LIMITS = constants.LIMITS;
|
|
|
54
52
|
|
|
55
53
|
function E(code, message, cause) { return new TrustError(code, message, cause); }
|
|
56
54
|
|
|
57
|
-
// The three NSS purposes an anchor carries; the keys double as the
|
|
58
|
-
// `opts.checkPurpose` vocabulary `pki.path.validate` consumes.
|
|
59
55
|
var PURPOSES = ["serverAuth", "emailProtection", "codeSigning"];
|
|
60
56
|
|
|
61
|
-
// CKA_TRUST_* attribute -> purpose key.
|
|
62
57
|
var PURPOSE_ATTRS = [
|
|
63
58
|
["CKA_TRUST_SERVER_AUTH", "serverAuth"],
|
|
64
59
|
["CKA_TRUST_EMAIL_PROTECTION", "emailProtection"],
|
|
65
60
|
["CKA_TRUST_CODE_SIGNING", "codeSigning"],
|
|
66
61
|
];
|
|
67
62
|
|
|
68
|
-
// The recognized CK_TRUST vocabulary. Only CKT_NSS_TRUSTED_DELEGATOR grants a
|
|
69
|
-
// purpose; the rest are recognized-but-untrusted (fail-closed: an absent or
|
|
70
|
-
// non-delegator bit never defaults to trusted). A token outside this set is a
|
|
71
|
-
// typed trust/bad-trust-value, never a silent false.
|
|
72
63
|
var CKT_RECOGNIZED = Object.create(null);
|
|
73
64
|
["CKT_NSS_TRUSTED_DELEGATOR", "CKT_NSS_TRUSTED", "CKT_NSS_MUST_VERIFY_TRUST",
|
|
74
65
|
"CKT_NSS_TRUST_UNKNOWN", "CKT_NSS_NOT_TRUSTED", "CKT_NSS_VALID_DELEGATOR"]
|
|
75
66
|
.forEach(function (t) { CKT_RECOGNIZED[t] = true; });
|
|
76
67
|
var CKT_DELEGATOR = "CKT_NSS_TRUSTED_DELEGATOR";
|
|
77
68
|
|
|
78
|
-
|
|
79
|
-
// per-type (a quoted UTF8 string, a single token, or nothing for
|
|
80
|
-
// MULTILINE_OCTAL whose payload follows on its own lines).
|
|
81
|
-
// ---- certdata scanners by character (no regex) ----------------------------------------------------
|
|
82
|
-
function _isNameChar(c) { return (c >= 65 && c <= 90) || (c >= 48 && c <= 57) || c === 95; } // A-Z 0-9 _
|
|
69
|
+
function _isNameChar(c) { return (c >= 65 && c <= 90) || (c >= 48 && c <= 57) || c === 95; }
|
|
83
70
|
function _isDigitCode(c) { return c >= 48 && c <= 57; }
|
|
84
|
-
function _isDateSepCode(c) { return c === 46 || c === 47 || c === 45; }
|
|
85
|
-
function _isOctalDigit(c) { return c >= 48 && c <= 55; }
|
|
71
|
+
function _isDateSepCode(c) { return c === 46 || c === 47 || c === 45; }
|
|
72
|
+
function _isOctalDigit(c) { return c >= 48 && c <= 55; }
|
|
86
73
|
function _isWsCode(c) { return pkix.isJsWhitespace(c); }
|
|
87
|
-
function _containsWs(s) { for (var i = 0; i < s.length; i++) { if (_isWsCode(_charCodeAt(s, i))) return true; } return false; }
|
|
74
|
+
function _containsWs(s) { for (var i = 0; i < s.length; i++) { if (_isWsCode(_charCodeAt(s, i))) return true; } return false; }
|
|
88
75
|
|
|
89
|
-
// An attribute line `CKA_<NAME> [<type> [<value>]]`, matching
|
|
90
|
-
// /^(CKA_[A-Z0-9_]+)(?:\s+(\S+)(?:\s+(.*))?)?$/. Returns [name, type|undefined, value|undefined] or null.
|
|
91
76
|
function _parseAttrLine(s) {
|
|
92
77
|
var n = s.length;
|
|
93
78
|
if (_strSlice(s, 0, 4) !== "CKA_") return null;
|
|
@@ -108,17 +93,13 @@ function _parseAttrLine(s) {
|
|
|
108
93
|
return [name, type, _strSlice(s, p)];
|
|
109
94
|
}
|
|
110
95
|
|
|
111
|
-
// A single double-quoted value with no interior quote (matching /^"([^"]*)"$/): the inner content, or
|
|
112
|
-
// null. Empty content ("\"\"") returns "" (a valid value), distinct from the null no-match.
|
|
113
96
|
function _unquote(s) {
|
|
114
97
|
var n = s.length;
|
|
115
|
-
if (n < 2 || _charCodeAt(s, 0) !== 34 || _charCodeAt(s, n - 1) !== 34) return null;
|
|
116
|
-
for (var i = 1; i < n - 1; i++) if (_charCodeAt(s, i) === 34) return null;
|
|
98
|
+
if (n < 2 || _charCodeAt(s, 0) !== 34 || _charCodeAt(s, n - 1) !== 34) return null;
|
|
99
|
+
for (var i = 1; i < n - 1; i++) if (_charCodeAt(s, i) === 34) return null;
|
|
117
100
|
return _strSlice(s, 1, n - 1);
|
|
118
101
|
}
|
|
119
102
|
|
|
120
|
-
// String.prototype.split(/\s+/): the pieces between whitespace runs (a leading/trailing run yields a
|
|
121
|
-
// "" element; "" -> [""]). Index assignment (not .push) so the scanner adds no live intrinsic read.
|
|
122
103
|
function _splitWhitespace(s) {
|
|
123
104
|
var out = [], i = 0, n = s.length, pieceStart = 0;
|
|
124
105
|
while (i < n) {
|
|
@@ -132,23 +113,21 @@ function _splitWhitespace(s) {
|
|
|
132
113
|
return out;
|
|
133
114
|
}
|
|
134
115
|
|
|
135
|
-
// Exactly three octal digits (matching /^[0-7]{3}$/).
|
|
136
116
|
function _isOctal3(d) {
|
|
137
117
|
if (d.length !== 3) return false;
|
|
138
118
|
for (var i = 0; i < 3; i++) { if (!_isOctalDigit(_charCodeAt(d, i))) return false; }
|
|
139
119
|
return true;
|
|
140
120
|
}
|
|
141
121
|
|
|
142
|
-
// String.prototype.split(/\r\n|\n|\r/): split on CRLF, LF, or CR line terminators.
|
|
143
122
|
function _splitLines(text) {
|
|
144
123
|
var out = [], start = 0, i = 0, n = text.length;
|
|
145
124
|
while (i < n) {
|
|
146
125
|
var c = _charCodeAt(text, i);
|
|
147
|
-
if (c === 13) {
|
|
126
|
+
if (c === 13) {
|
|
148
127
|
out[out.length] = _strSlice(text, start, i);
|
|
149
128
|
if (i + 1 < n && _charCodeAt(text, i + 1) === 10) i += 1;
|
|
150
129
|
i += 1; start = i;
|
|
151
|
-
} else if (c === 10) {
|
|
130
|
+
} else if (c === 10) {
|
|
152
131
|
out[out.length] = _strSlice(text, start, i);
|
|
153
132
|
i += 1; start = i;
|
|
154
133
|
} else i += 1;
|
|
@@ -157,8 +136,6 @@ function _splitLines(text) {
|
|
|
157
136
|
return out;
|
|
158
137
|
}
|
|
159
138
|
|
|
160
|
-
// A Y-M-D date with dotted/dashed/slashed separators, matching /^(\d{4})[./-](\d{1,2})[./-](\d{1,2})$/.
|
|
161
|
-
// Returns [year, month, day] (raw digit strings) or null.
|
|
162
139
|
function _parseYmd(s) {
|
|
163
140
|
var n = s.length;
|
|
164
141
|
for (var a = 0; a < 4; a++) if (!_isDigitCode(_charCodeAt(s, a))) return null;
|
|
@@ -177,15 +154,7 @@ function _parseYmd(s) {
|
|
|
177
154
|
return [year, month, day];
|
|
178
155
|
}
|
|
179
156
|
|
|
180
|
-
// ---------------------------------------------------------------------------
|
|
181
|
-
// certdata.txt lexer -- line-oriented, C.LIMITS-bounded, fail-closed
|
|
182
|
-
// ---------------------------------------------------------------------------
|
|
183
157
|
|
|
184
|
-
// Decode a MULTILINE_OCTAL body: whitespace-separated runs of `\ooo` escapes
|
|
185
|
-
// (exactly a backslash + three octal digits, \000..\377), terminated by a line
|
|
186
|
-
// that is exactly `END`. Every fault is refused before the byte lands: an
|
|
187
|
-
// escape past \377, a short or non-octal escape, a stray token, a blank line
|
|
188
|
-
// or EOF before END, or a blob past TRUST_MAX_OCTAL_BYTES.
|
|
189
158
|
function _readOctalBody(lines, idx) {
|
|
190
159
|
var out = [];
|
|
191
160
|
var cap = LIMITS.TRUST_MAX_OCTAL_BYTES;
|
|
@@ -197,14 +166,14 @@ function _readOctalBody(lines, idx) {
|
|
|
197
166
|
for (var c = 0; c < chunks.length; c++) {
|
|
198
167
|
var chunk = chunks[c];
|
|
199
168
|
for (var k = 0; k < chunk.length; k += 4) {
|
|
200
|
-
if (chunk.charCodeAt(k) !== 92
|
|
169
|
+
if (chunk.charCodeAt(k) !== 92 ) {
|
|
201
170
|
throw E("trust/bad-octal", "MULTILINE_OCTAL expects \\ooo escapes, got " + JSON.stringify(chunk.slice(k, k + 4)));
|
|
202
171
|
}
|
|
203
172
|
var d = chunk.slice(k + 1, k + 4);
|
|
204
173
|
if (!_isOctal3(d)) {
|
|
205
174
|
throw E("trust/bad-octal", "a MULTILINE_OCTAL escape is a backslash + exactly three octal digits, got " + JSON.stringify("\\" + d));
|
|
206
175
|
}
|
|
207
|
-
if (d.charCodeAt(0) > 51
|
|
176
|
+
if (d.charCodeAt(0) > 51 ) {
|
|
208
177
|
throw E("trust/bad-octal", "octal escape \\" + d + " exceeds \\377 (one byte)");
|
|
209
178
|
}
|
|
210
179
|
if (out.length >= cap) throw E("trust/bad-block", "MULTILINE_OCTAL blob exceeds LIMITS.TRUST_MAX_OCTAL_BYTES");
|
|
@@ -215,10 +184,6 @@ function _readOctalBody(lines, idx) {
|
|
|
215
184
|
throw E("trust/bad-octal", "MULTILINE_OCTAL body reached end of input before END");
|
|
216
185
|
}
|
|
217
186
|
|
|
218
|
-
// Lex the object stream: a `#` line is a comment, the preamble ends at
|
|
219
|
-
// BEGINDATA, blank lines delimit object blocks, each block is typed
|
|
220
|
-
// `CKA_* <type> <value>` attribute lines. Returns [{ attrs }] where attrs maps
|
|
221
|
-
// the attribute name to { type, value | bytes }.
|
|
222
187
|
function _lexObjects(text) {
|
|
223
188
|
var lines = _splitLines(text);
|
|
224
189
|
var n = lines.length;
|
|
@@ -243,7 +208,6 @@ function _lexObjects(text) {
|
|
|
243
208
|
var rawVal = m[2] !== undefined ? m[2].trim() : undefined;
|
|
244
209
|
if (!type) throw E("trust/bad-block", "attribute line is missing its type token: " + name);
|
|
245
210
|
if (!cur) {
|
|
246
|
-
// Refuse the block bomb before lexing another block's payloads.
|
|
247
211
|
if (objects.length >= LIMITS.TRUST_MAX_OBJECTS) throw E("trust/bad-block", "certdata object count exceeds LIMITS.TRUST_MAX_OBJECTS");
|
|
248
212
|
cur = { attrs: Object.create(null) };
|
|
249
213
|
}
|
|
@@ -272,8 +236,6 @@ function _lexObjects(text) {
|
|
|
272
236
|
if (rawVal === undefined || _containsWs(rawVal)) throw E("trust/bad-block", name + " " + type + " must carry a single token value");
|
|
273
237
|
entry = { type: type, value: rawVal };
|
|
274
238
|
} else {
|
|
275
|
-
// An unknown value type cannot be lexed safely (its value grammar is
|
|
276
|
-
// unknown), so fail closed instead of guessing past it.
|
|
277
239
|
throw E("trust/bad-block", "unrecognized attribute type " + JSON.stringify(type) + " on " + name);
|
|
278
240
|
}
|
|
279
241
|
cur.attrs[name] = entry;
|
|
@@ -283,9 +245,6 @@ function _lexObjects(text) {
|
|
|
283
245
|
return objects;
|
|
284
246
|
}
|
|
285
247
|
|
|
286
|
-
// ---------------------------------------------------------------------------
|
|
287
|
-
// certdata semantics -- pairing, distrust-after, purpose bits
|
|
288
|
-
// ---------------------------------------------------------------------------
|
|
289
248
|
|
|
290
249
|
function _requireOctal(obj, name, what) {
|
|
291
250
|
var a = obj.attrs[name];
|
|
@@ -295,11 +254,6 @@ function _requireOctal(obj, name, what) {
|
|
|
295
254
|
return a.bytes;
|
|
296
255
|
}
|
|
297
256
|
|
|
298
|
-
// The NSS distrust-after payload is the bare ASCII of a YYMMDDHHMMSSZ UTCTime
|
|
299
|
-
// (13 bytes) or a YYYYMMDDHHMMSSZ GeneralizedTime (15 bytes), with no DER tag or
|
|
300
|
-
// length. Synthesize the minimal TLV and route it through the strict asn1
|
|
301
|
-
// time reader so the Z-terminator / mandatory-seconds / component-rollover /
|
|
302
|
-
// RFC 5280 sec. 4.1.2.5.1 2050-pivot rules are enforced once, never re-derived.
|
|
303
257
|
function _strictTime(payload, code, label) {
|
|
304
258
|
var tlv;
|
|
305
259
|
if (payload.length === 13) tlv = Buffer.concat([Buffer.from([0x17, 0x0d]), payload]);
|
|
@@ -309,8 +263,6 @@ function _strictTime(payload, code, label) {
|
|
|
309
263
|
catch (e) { throw E(code, label + " does not decode as a strict time", e); }
|
|
310
264
|
}
|
|
311
265
|
|
|
312
|
-
// CKA_NSS_*_DISTRUST_AFTER: either `CK_BBOOL CK_FALSE` (no distrust-after in
|
|
313
|
-
// force) or a MULTILINE_OCTAL bare-ASCII time. Anything else fails closed.
|
|
314
266
|
function _distrustDate(obj, name) {
|
|
315
267
|
var a = obj.attrs[name];
|
|
316
268
|
if (!a) return null;
|
|
@@ -331,16 +283,13 @@ function _certEntry(obj) {
|
|
|
331
283
|
var issuer = _requireOctal(obj, "CKA_ISSUER", "certificate");
|
|
332
284
|
var serial = _requireOctal(obj, "CKA_SERIAL_NUMBER", "certificate");
|
|
333
285
|
|
|
334
|
-
// Fail-closed cross-check (the pairing key vs the parsed DER): a block whose
|
|
335
|
-
// own identity octets disagree with its certificate would let trust metadata
|
|
336
|
-
// attach to the wrong root -- a trust-elevation bug, not a tolerable skew.
|
|
337
286
|
if (!issuer.equals(cert.issuer.bytes)) {
|
|
338
287
|
throw E("trust/pairing-mismatch", "CKA_ISSUER disagrees with the certificate's issuer DER");
|
|
339
288
|
}
|
|
340
289
|
var serialContentHex;
|
|
341
290
|
try {
|
|
342
291
|
var sn = asn1.decode(serial);
|
|
343
|
-
asn1.read.integer(sn);
|
|
292
|
+
asn1.read.integer(sn);
|
|
344
293
|
serialContentHex = sn.content.toString("hex");
|
|
345
294
|
} catch (e) {
|
|
346
295
|
throw E("trust/pairing-mismatch", "CKA_SERIAL_NUMBER is not a DER INTEGER", e);
|
|
@@ -371,7 +320,7 @@ function _certEntry(obj) {
|
|
|
371
320
|
label: label ? label.value : null,
|
|
372
321
|
mozillaCaPolicy: !!(policy && policy.value === true),
|
|
373
322
|
distrustAfter: distrustAfter,
|
|
374
|
-
purposes: null,
|
|
323
|
+
purposes: null,
|
|
375
324
|
};
|
|
376
325
|
}
|
|
377
326
|
|
|
@@ -381,50 +330,28 @@ function _trustEntry(obj) {
|
|
|
381
330
|
var purposes = { serverAuth: false, emailProtection: false, codeSigning: false };
|
|
382
331
|
PURPOSE_ATTRS.forEach(function (pair) {
|
|
383
332
|
var a = obj.attrs[pair[0]];
|
|
384
|
-
if (!a) return;
|
|
333
|
+
if (!a) return;
|
|
385
334
|
if (a.type !== "CK_TRUST") throw E("trust/bad-block", pair[0] + " must be CK_TRUST");
|
|
386
335
|
purposes[pair[1]] = a.value === CKT_DELEGATOR;
|
|
387
336
|
});
|
|
388
337
|
return { key: issuer.toString("hex") + "/" + serial.toString("hex"), purposes: purposes };
|
|
389
338
|
}
|
|
390
339
|
|
|
391
|
-
// ---------------------------------------------------------------------------
|
|
392
|
-
// Anchor assembly + dedup (shared by both sources)
|
|
393
|
-
// ---------------------------------------------------------------------------
|
|
394
340
|
|
|
395
341
|
function _mkAnchor(cert, meta) {
|
|
396
342
|
var spki = cert.subjectPublicKeyInfo;
|
|
397
343
|
var entry = {
|
|
398
|
-
name: cert.subject,
|
|
399
|
-
publicKey: spki.bytes,
|
|
400
|
-
algorithm: spki.algorithm.oid,
|
|
401
|
-
parameters: spki.algorithm.parameters,
|
|
344
|
+
name: cert.subject,
|
|
345
|
+
publicKey: spki.bytes,
|
|
346
|
+
algorithm: spki.algorithm.oid,
|
|
347
|
+
parameters: spki.algorithm.parameters,
|
|
402
348
|
subjectDer: cert.subject.bytes,
|
|
403
349
|
distrustAfter: meta.distrustAfter,
|
|
404
350
|
purposes: meta.purposes,
|
|
405
351
|
label: meta.label,
|
|
406
352
|
mozillaCaPolicy: meta.mozillaCaPolicy,
|
|
407
353
|
};
|
|
408
|
-
// A trust anchor is the pair (name, key) per RFC 5280 sec. 6.1.1, so those two fields are one
|
|
409
|
-
// fact about one certificate, and they were derived here from one. `anchor()` re-derives them
|
|
410
|
-
// instead of reading them back, so an entry rebuilt with a substituted key cannot carry the
|
|
411
|
-
// store's NAME and its per-purpose trust metadata over to a key the store never vouched for.
|
|
412
|
-
// Recorded off the object, so rebuilding the entry loses the record along with the binding.
|
|
413
|
-
// Copied, not aliased. The record and the entry would otherwise hold the same Buffer and the same
|
|
414
|
-
// name object, so overwriting the entry's publicKey in place (otherSpki.copy(entry.publicKey)
|
|
415
|
-
// for an equal-length key) would overwrite the record with it, and re-deriving would hand back
|
|
416
|
-
// exactly the substituted key. A record that changes with the thing it is meant to pin is not one.
|
|
417
|
-
//
|
|
418
|
-
// The store's METADATA is recorded with them, and for the stronger reason: `purposes` IS the
|
|
419
|
-
// authorization -- it is what the purpose gate reads to decide whether this root may vouch for
|
|
420
|
-
// TLS -- and `distrustAfter` is the date that authorization ends. Pinning the key while reading
|
|
421
|
-
// the authorization off the mutable entry pins the less important half: `entry.purposes.serverAuth
|
|
422
|
-
// = true` would then produce a server-auth anchor from a root the store marked for e-mail only,
|
|
423
|
-
// and deleting a distrust date would outlast the store's own policy. Everything the anchor asserts
|
|
424
|
-
// now comes from what the store read.
|
|
425
354
|
_DERIVED_FROM.set(entry, {
|
|
426
|
-
// The RDN entries are copied too, not just the arrays holding them: a shallow slice shares
|
|
427
|
-
// every attribute object, so editing one in place would still reach the record.
|
|
428
355
|
name: _copyName(cert.subject),
|
|
429
356
|
publicKey: Buffer.from(spki.bytes),
|
|
430
357
|
algorithm: spki.algorithm.oid,
|
|
@@ -436,10 +363,6 @@ function _mkAnchor(cert, meta) {
|
|
|
436
363
|
}
|
|
437
364
|
var _DERIVED_FROM = new WeakMap();
|
|
438
365
|
|
|
439
|
-
// The per-purpose distrust dates, with fresh Date objects. A Date is mutable, so handing back the
|
|
440
|
-
// entry's own would let a consumer move the date this anchor is judged against.
|
|
441
|
-
// The three trust bits, normalized to booleans. Read by the purpose gate and handed back on every
|
|
442
|
-
// anchor, so it is built fresh from whichever source is authoritative and never shared.
|
|
443
366
|
function _copyPurposes(src) {
|
|
444
367
|
return {
|
|
445
368
|
serverAuth: !!src && src.serverAuth === true,
|
|
@@ -457,16 +380,6 @@ function _copyDistrustAfter(src) {
|
|
|
457
380
|
return out;
|
|
458
381
|
}
|
|
459
382
|
|
|
460
|
-
// A parsed Name, copied faithfully: every field the parser assigns, at every level, with nothing
|
|
461
|
-
// mutable shared. Used on both sides of the record, writing it in and handing it out, so
|
|
462
|
-
// neither direction shares an object with the caller.
|
|
463
|
-
//
|
|
464
|
-
// Faithful, not "the fields the path validator reads", because this Name is handed back on
|
|
465
|
-
// pki.trust.anchor and is the same structure pki.schema.x509.parse produces. A copy that keeps the
|
|
466
|
-
// subset one consumer needs silently degrades it for every other reader: an attribute would lose
|
|
467
|
-
// its registry `name`, so a caller walking anchor.name.rdns would see a different shape depending
|
|
468
|
-
// on whether the anchor came from a store entry or straight from the parser. Each attribute value
|
|
469
|
-
// is a string and so needs no copy of its own; the DER does.
|
|
470
383
|
function _copyName(name) {
|
|
471
384
|
if (!name || !Array.isArray(name.rdns)) return name;
|
|
472
385
|
var out = Object.assign({}, name);
|
|
@@ -501,11 +414,6 @@ function _anchorsAgree(x, y) {
|
|
|
501
414
|
_datesEqual(x.distrustAfter, y.distrustAfter);
|
|
502
415
|
}
|
|
503
416
|
|
|
504
|
-
// Dedup by (subjectDer, publicKey): one root appearing twice (a duplicated
|
|
505
|
-
// block, or certdata + CCADB merged by the operator) collapses to one anchor,
|
|
506
|
-
// while a distinct root that merely shares a subject DN has a different key and
|
|
507
|
-
// survives. Two entries for one root that disagree on trust metadata are
|
|
508
|
-
// ambiguous, so they fail closed instead of silently picking one.
|
|
509
417
|
function _dedupAnchors(anchors) {
|
|
510
418
|
var seen = Object.create(null);
|
|
511
419
|
var out = [];
|
|
@@ -520,9 +428,6 @@ function _dedupAnchors(anchors) {
|
|
|
520
428
|
return out;
|
|
521
429
|
}
|
|
522
430
|
|
|
523
|
-
// ---------------------------------------------------------------------------
|
|
524
|
-
// Public surface
|
|
525
|
-
// ---------------------------------------------------------------------------
|
|
526
431
|
|
|
527
432
|
/**
|
|
528
433
|
* @primitive pki.trust.parseCertdata
|
|
@@ -586,9 +491,6 @@ function parseCertdata(text) {
|
|
|
586
491
|
var e = _certEntry(obj);
|
|
587
492
|
var prev = byKey[e.key];
|
|
588
493
|
if (prev) {
|
|
589
|
-
// A byte-identical duplicated block collapses; two certificate
|
|
590
|
-
// objects claiming one (issuer, serial) with different content are a
|
|
591
|
-
// forged identity -- fail closed.
|
|
592
494
|
if (!prev.der.equals(e.der) || prev.label !== e.label ||
|
|
593
495
|
prev.mozillaCaPolicy !== e.mozillaCaPolicy ||
|
|
594
496
|
!_datesEqual(prev.distrustAfter, e.distrustAfter)) {
|
|
@@ -601,12 +503,11 @@ function parseCertdata(text) {
|
|
|
601
503
|
} else if (cls.value === "CKO_NSS_TRUST") {
|
|
602
504
|
trusts.push(_trustEntry(obj));
|
|
603
505
|
}
|
|
604
|
-
// Any other class (CKO_NSS_BUILTIN_ROOT_LIST, ...) is not a root: skipped.
|
|
605
506
|
});
|
|
606
507
|
|
|
607
508
|
trusts.forEach(function (t) {
|
|
608
509
|
var c = byKey[t.key];
|
|
609
|
-
if (!c) return;
|
|
510
|
+
if (!c) return;
|
|
610
511
|
if (c.purposes) {
|
|
611
512
|
if (!_purposesEqual(c.purposes, t.purposes)) {
|
|
612
513
|
throw E("trust/pairing-mismatch", "two trust objects for one certificate disagree on purpose bits");
|
|
@@ -628,9 +529,6 @@ function parseCertdata(text) {
|
|
|
628
529
|
};
|
|
629
530
|
}
|
|
630
531
|
|
|
631
|
-
// ---------------------------------------------------------------------------
|
|
632
|
-
// CCADB CSV -- a small RFC 4180 sub-lexer + header-keyed column mapping
|
|
633
|
-
// ---------------------------------------------------------------------------
|
|
634
532
|
|
|
635
533
|
var CSV_REQUIRED = [
|
|
636
534
|
"Common Name or Certificate Name",
|
|
@@ -640,10 +538,6 @@ var CSV_REQUIRED = [
|
|
|
640
538
|
"PEM Info",
|
|
641
539
|
];
|
|
642
540
|
|
|
643
|
-
// RFC 4180 field lexer: quoted fields, embedded commas / newlines, `""`
|
|
644
|
-
// escapes. Strict: a quote may only open at the start of a field, nothing may
|
|
645
|
-
// follow a closing quote but a separator, and an unterminated quote at EOF is
|
|
646
|
-
// a framing violation. Row and field ceilings are refused as they are hit.
|
|
647
541
|
function _csvRows(text) {
|
|
648
542
|
var rows = [], row = [], field = "";
|
|
649
543
|
var inQuotes = false, quoted = false, closed = false;
|
|
@@ -685,16 +579,6 @@ function _csvRows(text) {
|
|
|
685
579
|
return rows;
|
|
686
580
|
}
|
|
687
581
|
|
|
688
|
-
// `Trust Bits` is set-valued (`Websites; Email`) and CCADB spells it in two
|
|
689
|
-
// vocabularies: the Mozilla-report labels (Websites / Email / Code) and the
|
|
690
|
-
// EKU-derived trust-bit names current exports carry (Server Authentication /
|
|
691
|
-
// Secure Email / Code Signing). Both map to the same purposes. The KNOWN CCADB
|
|
692
|
-
// purposes the anchor model does not track (Client Authentication, Document
|
|
693
|
-
// Signing, Time Stamping, OCSP Signing) are tolerated and grant NOTHING -- a
|
|
694
|
-
// purpose is only ever granted by a recognized delegator token, so ignoring an
|
|
695
|
-
// unmapped one fails closed. An empty cell grants nothing; a token outside the
|
|
696
|
-
// known universe is still surfaced (a silently-skipped NEW bit would hide
|
|
697
|
-
// vocabulary drift), never guessed.
|
|
698
582
|
var _TRUST_BIT_TOKENS = {
|
|
699
583
|
"websites": "serverAuth", "server authentication": "serverAuth",
|
|
700
584
|
"email": "emailProtection", "secure email": "emailProtection",
|
|
@@ -717,11 +601,6 @@ function _trustBits(cell) {
|
|
|
717
601
|
return purposes;
|
|
718
602
|
}
|
|
719
603
|
|
|
720
|
-
// The CCADB distrust columns are DATE-only. Expand Y-M-D to the end-of-day
|
|
721
|
-
// ...T23:59:59Z instant -- the same instant NSS encodes as `...235959Z` -- by
|
|
722
|
-
// synthesizing a GeneralizedTime and routing through the strict reader (which
|
|
723
|
-
// also rejects a rolled-over calendar date). Dotted, dashed, and slashed
|
|
724
|
-
// separators are accepted (the export format has drifted between them).
|
|
725
604
|
function _csvDate(cell, label) {
|
|
726
605
|
var s = String(cell).trim();
|
|
727
606
|
if (s === "") return null;
|
|
@@ -729,9 +608,6 @@ function _csvDate(cell, label) {
|
|
|
729
608
|
if (!m) throw E("trust/bad-csv", label + " must be a Y-M-D date, got " + JSON.stringify(s));
|
|
730
609
|
var pad = function (x) { return x.length === 1 ? "0" + x : x; };
|
|
731
610
|
var payload = Buffer.from(m[0] + pad(m[1]) + pad(m[2]) + "235959Z", "latin1");
|
|
732
|
-
// _strictTime tags every rejection (a rolled-over month/day among them) with the code passed here,
|
|
733
|
-
// so the re-throw carries it unchanged; the wrap normalizes any other failure to the same typed code
|
|
734
|
-
// so a bad row is always trust/bad-csv, never an untyped exception escaping parseCcadbCsv.
|
|
735
611
|
try { return _strictTime(payload, "trust/bad-csv", label); }
|
|
736
612
|
catch (e) {
|
|
737
613
|
if (e && e.code === "trust/bad-csv") throw e;
|
|
@@ -739,9 +615,6 @@ function _csvDate(cell, label) {
|
|
|
739
615
|
}
|
|
740
616
|
}
|
|
741
617
|
|
|
742
|
-
// A CCADB `PEM Info` cell: the PEM certificate, commonly wrapped in one layer
|
|
743
|
-
// of single quotes (a spreadsheet-import guard) -- strip that layer, then
|
|
744
|
-
// parse strictly.
|
|
745
618
|
function _pemCell(cell) {
|
|
746
619
|
var s = String(cell).trim();
|
|
747
620
|
if (s.charAt(0) === "'") s = s.slice(1);
|
|
@@ -796,14 +669,10 @@ function parseCcadbCsv(text) {
|
|
|
796
669
|
var rows = _csvRows(text);
|
|
797
670
|
if (rows.length === 0) throw E("trust/bad-csv", "CSV input carries no header row");
|
|
798
671
|
|
|
799
|
-
// trim() also strips a leading U+FEFF, so a BOM-prefixed export resolves
|
|
800
|
-
// its first header cell normally.
|
|
801
672
|
var header = rows[0].map(function (h) { return h.trim(); });
|
|
802
673
|
var col = Object.create(null);
|
|
803
674
|
header.forEach(function (h, idx) {
|
|
804
675
|
if (col[h] !== undefined) {
|
|
805
|
-
// Two columns with one REQUIRED name make the mapping ambiguous (which
|
|
806
|
-
// Trust Bits column binds?) -- fail closed rather than silently pick.
|
|
807
676
|
if (CSV_REQUIRED.indexOf(h) !== -1) throw E("trust/bad-csv", "duplicate required column " + JSON.stringify(h));
|
|
808
677
|
return;
|
|
809
678
|
}
|
|
@@ -829,7 +698,7 @@ function parseCcadbCsv(text) {
|
|
|
829
698
|
distrustAfter: distrustAfter,
|
|
830
699
|
purposes: _trustBits(row[col["Trust Bits"]]),
|
|
831
700
|
label: label === "" ? null : label,
|
|
832
|
-
mozillaCaPolicy: false,
|
|
701
|
+
mozillaCaPolicy: false,
|
|
833
702
|
}));
|
|
834
703
|
}
|
|
835
704
|
return { anchors: _dedupAnchors(anchors) };
|
|
@@ -877,9 +746,6 @@ function parseCcadbCsv(text) {
|
|
|
877
746
|
* await pki.path.validate([pki.schema.x509.parse(der)],
|
|
878
747
|
* { time: new Date("2026-06-01T00:00:00Z"), trustAnchors: anchor, checkPurpose: "serverAuth" });
|
|
879
748
|
*/
|
|
880
|
-
// `purpose` is the only option, and it gates the authorization itself, so a near-miss spelling must not
|
|
881
|
-
// pass silently: a caller who wrote the plural `purposes` (or carried a sibling's word) would otherwise get
|
|
882
|
-
// the anchor with NO purpose restriction rather than an error naming the option they meant.
|
|
883
749
|
var _ANCHOR_OPTS = { purpose: 1 };
|
|
884
750
|
function anchor(entry, opts) {
|
|
885
751
|
if (!entry || typeof entry !== "object") {
|
|
@@ -887,18 +753,8 @@ function anchor(entry, opts) {
|
|
|
887
753
|
}
|
|
888
754
|
opts = guard.identifier.optionsObject(opts, E, "trust/bad-input", "pki.trust.anchor options");
|
|
889
755
|
guard.identifier.assertKnownKeys(opts, _ANCHOR_OPTS, E, "trust/bad-input", "unknown pki.trust.anchor option ");
|
|
890
|
-
// Where this entry came from a store, everything the anchor asserts is read from the record the
|
|
891
|
-
// store minted, not from the entry -- the entry is a plain object the caller holds and can write
|
|
892
|
-
// to. `purposes` is the authorization itself, so an entry whose serverAuth bit was flipped after
|
|
893
|
-
// parsing must not open the gate, and a distrust date deleted from it must not outlive the
|
|
894
|
-
// store's policy.
|
|
895
756
|
var derived = _DERIVED_FROM.get(entry);
|
|
896
757
|
var meta = derived || entry;
|
|
897
|
-
// The tuple-shape check runs on the ENTRY only when there is no record, because only then are the
|
|
898
|
-
// entry's own fields what the anchor is built from. With a record, an entry a caller has since
|
|
899
|
-
// emptied still anchors to exactly what the store read: the answer does not depend on the object
|
|
900
|
-
// the caller is holding, in either direction. Without one, the caller is asserting a bare
|
|
901
|
-
// (name, key) anchor of their own and its shape is what there is to check.
|
|
902
758
|
if (!derived && (!Buffer.isBuffer(entry.publicKey) || typeof entry.algorithm !== "string" ||
|
|
903
759
|
!entry.name || !Array.isArray(entry.name.rdns))) {
|
|
904
760
|
throw E("trust/bad-input", "anchor expects a trust-store entry ({ name, publicKey, algorithm, ... })");
|
|
@@ -911,37 +767,15 @@ function anchor(entry, opts) {
|
|
|
911
767
|
throw E("trust/purpose-not-trusted", "this root is not a trusted delegator for " + opts.purpose);
|
|
912
768
|
}
|
|
913
769
|
}
|
|
914
|
-
// Where this entry came from a store, the (name, key) pair is re-derived from the certificate it
|
|
915
|
-
// was read out of rather than read back off the entry. Both halves are one fact about one
|
|
916
|
-
// certificate, so an entry rebuilt with a substituted publicKey would otherwise carry the store's
|
|
917
|
-
// name and its per-purpose trust metadata onto a key the store never vouched for. An entry a
|
|
918
|
-
// caller built themselves has no such record and is their own assertion, which is what a bare
|
|
919
|
-
// trust-anchor tuple is.
|
|
920
|
-
//
|
|
921
|
-
// An entry carrying the store's METADATA is claiming to be a store entry. The metadata is the
|
|
922
|
-
// root program's statement -- these purposes, until this date -- and it is a statement about a
|
|
923
|
-
// KEY. Without the record there is nothing binding it to the key the entry now names, so a copy
|
|
924
|
-
// with a substituted publicKey would carry the program's word onto a key it never saw. A caller
|
|
925
|
-
// asserting a bare (name, key) anchor of their own carries no metadata and is unaffected.
|
|
926
770
|
if (!derived && (entry.purposes != null || (entry.distrustAfter && Object.keys(entry.distrustAfter).length))) {
|
|
927
771
|
throw E("trust/bad-input", "this entry carries a trust store's per-purpose metadata but is not the entry the store produced -- it has been rebuilt, and the metadata is a statement about the key the store read, not about whichever key the copy now names. Pass pki.trust.parseCertdata / parseCcadbCsv output unmodified");
|
|
928
772
|
}
|
|
929
|
-
// Copies OUT as well as in. Handing back the record's own Buffer lets a consumer write through
|
|
930
|
-
// the returned anchor into the record -- copy an equal-length SPKI over anchor.publicKey and the
|
|
931
|
-
// next anchor() call re-derives the substituted key while still carrying the store's purposes and
|
|
932
|
-
// distrust dates. Guarding only the write INTO the record leaves the same door open in the other
|
|
933
|
-
// direction, so every call hands back fresh values.
|
|
934
773
|
return {
|
|
935
774
|
name: _copyName(meta.name),
|
|
936
775
|
publicKey: Buffer.isBuffer(meta.publicKey) ? Buffer.from(meta.publicKey) : meta.publicKey,
|
|
937
776
|
algorithm: meta.algorithm,
|
|
938
777
|
parameters: Buffer.isBuffer(meta.parameters) ? Buffer.from(meta.parameters)
|
|
939
778
|
: (meta.parameters !== undefined ? meta.parameters : null),
|
|
940
|
-
// The METADATA comes from the same source as the key, and is copied for the same reason on the
|
|
941
|
-
// way out: handing back the record's own object let a consumer write
|
|
942
|
-
// `anchor(entry).purposes.serverAuth = true` and have the next call pass a gate the store never
|
|
943
|
-
// opened. `distrustAfter` holds Dates, which are mutable in the same way. Everything on this
|
|
944
|
-
// object is fresh, so two anchors from one entry share nothing.
|
|
945
779
|
distrustAfter: _copyDistrustAfter(meta.distrustAfter),
|
|
946
780
|
purposes: _copyPurposes(meta.purposes),
|
|
947
781
|
};
|