@blamejs/pki 0.8.9 → 0.8.11
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 +32 -0
- package/README.md +5 -5
- package/lib/acme.js +9 -5
- package/lib/attrcert-sign.js +14 -15
- package/lib/constants.js +5 -0
- package/lib/guard-parsed.js +1 -1
- package/lib/inspect.js +59 -0
- package/lib/lint.js +3 -3
- package/lib/schema-all.js +10 -5
- package/lib/schema-attrcert.js +108 -13
- package/lib/schema-crl.js +136 -38
- package/lib/schema-crmf.js +91 -6
- package/lib/schema-csr.js +116 -5
- package/lib/schema-pkix.js +494 -4
- package/lib/schema-x509.js +97 -3
- package/lib/sigstore.js +35 -21
- package/lib/tsp-sign.js +20 -13
- package/package.json +1 -1
- package/sbom.cdx.json +6 -6
package/lib/schema-crl.js
CHANGED
|
@@ -36,11 +36,6 @@ var PemError = frameworkError.PemError;
|
|
|
36
36
|
var TAGS = asn1.TAGS;
|
|
37
37
|
|
|
38
38
|
|
|
39
|
-
var CRL_REASONS = pkix.CRL_REASON_NAMES;
|
|
40
|
-
|
|
41
|
-
var OID_CRL_NUMBER = oid.byName("cRLNumber");
|
|
42
|
-
var OID_REASON_CODE = oid.byName("reasonCode");
|
|
43
|
-
var OID_INVALIDITY_DATE = oid.byName("invalidityDate");
|
|
44
39
|
|
|
45
40
|
var NS = pkix.makeNS("crl", CrlError, oid);
|
|
46
41
|
|
|
@@ -51,29 +46,23 @@ var TIME = pkix.time(NS);
|
|
|
51
46
|
|
|
52
47
|
var CRL_VERSION = pkix.versionReader(NS, { "1": 2 });
|
|
53
48
|
|
|
49
|
+
/** @internal The decoders are the shared scope registry's, so which extensions a CRL profiles at
|
|
50
|
+
* which scope is stated once rather than in an if/else chain here. What stays here is this
|
|
51
|
+
* module's own wrapping: every fault becomes `crl/bad-extension-value` naming the extension, and
|
|
52
|
+
* an OID no scope profiles keeps its raw value. The signature and the `undefined`-means-both
|
|
53
|
+
* reading are unchanged, because `lib/lint.js` calls this with one argument. */
|
|
54
|
+
var CRL_EXT_DECODERS = pkix.crlExtensionDecoders(NS);
|
|
55
|
+
|
|
54
56
|
function decodeExt(ext, scope, ctx) {
|
|
55
57
|
var value = ext.value;
|
|
56
|
-
var
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
value
|
|
61
|
-
if (value < 0n) throw new Error("cRLNumber must be non-negative (INTEGER 0..MAX)");
|
|
62
|
-
} else if (ext.oid === OID_REASON_CODE && atEntry) {
|
|
63
|
-
var reason = asn1.read.enumerated(pkix.decodeNested(ext.value, ctx, "a reasonCode extension value"));
|
|
64
|
-
if (!_hasOwn(CRL_REASONS, reason.toString())) throw new Error("undefined CRLReason " + reason.toString());
|
|
65
|
-
value = Number(reason);
|
|
66
|
-
} else if (ext.oid === OID_INVALIDITY_DATE && atEntry) {
|
|
67
|
-
var n = pkix.decodeNested(ext.value, ctx, "an invalidityDate extension value");
|
|
68
|
-
if (n.tagClass !== "universal" || n.tagNumber !== TAGS.GENERALIZED_TIME) {
|
|
69
|
-
throw new Error("invalidityDate must be a GeneralizedTime");
|
|
70
|
-
}
|
|
71
|
-
value = asn1.read.time(n);
|
|
58
|
+
var decoder = CRL_EXT_DECODERS.at(ext.oid, scope);
|
|
59
|
+
if (decoder !== null) {
|
|
60
|
+
try { value = decoder(ext.value, ctx); }
|
|
61
|
+
catch (e) {
|
|
62
|
+
throw NS.E("crl/bad-extension-value", "malformed " + (ext.name || ext.oid) + " extension value: " + ((e && e.message) || String(e)), e);
|
|
72
63
|
}
|
|
73
|
-
} catch (e) {
|
|
74
|
-
throw NS.E("crl/bad-extension-value", "malformed " + (ext.name || ext.oid) + " extension value: " + ((e && e.message) || String(e)), e);
|
|
75
64
|
}
|
|
76
|
-
return { oid: ext.oid, name: ext.name, critical: ext.critical, value: value };
|
|
65
|
+
return { oid: ext.oid, name: ext.name, critical: ext.critical, value: value, valueBytes: ext.value };
|
|
77
66
|
}
|
|
78
67
|
|
|
79
68
|
var REVOKED_ENTRY = schema.seq([
|
|
@@ -169,6 +158,10 @@ var CERTIFICATE_LIST = pkix.signedEnvelope(NS, TBS_CERTLIST, {
|
|
|
169
158
|
* validated on the way in; a malformed CertificateList / TBSCertList throws a
|
|
170
159
|
* typed `CrlError` (`crl/*`) and a leaf-level codec fault surfaces as `asn1/*`.
|
|
171
160
|
*
|
|
161
|
+
* Each extension record is `{ oid, name, critical, value, valueBytes }`. `valueBytes` is the raw
|
|
162
|
+
* `extnValue` octets and `value` is the decoded form for the three extensions this parse decodes in
|
|
163
|
+
* place, `cRLNumber`, `reasonCode` and `invalidityDate`, and the same octets for every other.
|
|
164
|
+
*
|
|
172
165
|
* @example
|
|
173
166
|
* var pair = await pki.key.generate("Ed25519");
|
|
174
167
|
* var key = await pki.key.export(pair.privateKey);
|
|
@@ -245,19 +238,11 @@ function matches(root) {
|
|
|
245
238
|
return schema.isUniversalOneOf(pos, [TAGS.UTC_TIME, TAGS.GENERALIZED_TIME]);
|
|
246
239
|
}
|
|
247
240
|
|
|
248
|
-
/** @internal The criticality RFC 5280 sec. 5.3 fixes for each CRL entry extension,
|
|
249
|
-
*
|
|
250
|
-
*
|
|
251
|
-
var ENTRY_EXTENSION_CRITICALITY_ROWS = [
|
|
252
|
-
["reasonCode", false, "RFC 5280 sec. 5.3.1"], ["invalidityDate", false, "RFC 5280 sec. 5.3.2"],
|
|
253
|
-
["certificateIssuer", true, "RFC 5280 sec. 5.3.3"],
|
|
254
|
-
];
|
|
241
|
+
/** @internal The criticality RFC 5280 sec. 5.3 fixes for each CRL entry extension, from the shared
|
|
242
|
+
* scope registry: the CRL signer, the OCSP signer (sec. 4.4.5 carries the same extensions into a
|
|
243
|
+
* SingleResponse) and the linter all read this one table. */
|
|
255
244
|
function entryExtensionCriticality(oidRegistry) {
|
|
256
|
-
|
|
257
|
-
intrinsic.forEach(ENTRY_EXTENSION_CRITICALITY_ROWS, function (r) {
|
|
258
|
-
table[oidRegistry.byName(r[0])] = { critical: r[1], citation: r[2] };
|
|
259
|
-
});
|
|
260
|
-
return table;
|
|
245
|
+
return pkix.crlEntryFixedCriticality(oidRegistry);
|
|
261
246
|
}
|
|
262
247
|
/** @internal Reads a CRL entry extension's value the way this parser reads it, for a consumer that
|
|
263
248
|
* meets one outside a CRL (an OCSP SingleResponse, RFC 6960 sec. 4.4.5): reasonCode and
|
|
@@ -272,19 +257,132 @@ function readEntryExtension(ext, ctx) {
|
|
|
272
257
|
catch (e) {
|
|
273
258
|
throw NS.E("crl/bad-extension-value", "malformed certificateIssuer extension value: " + ((e && e.message) || intrinsic.String(e)), e);
|
|
274
259
|
}
|
|
275
|
-
return { oid: ext.oid, name: ext.name, critical: ext.critical, value: names };
|
|
260
|
+
return { oid: ext.oid, name: ext.name, critical: ext.critical, value: names, valueBytes: ext.value };
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
/** @internal The three scopes a CRL reader meets, as the shared door reads them. `ocsp-single`
|
|
264
|
+
* carries the entry tables under its own key because RFC 6960 sec. 4.4.5 puts a CRL entry
|
|
265
|
+
* extension in an OCSP SingleResponse, where the row should say where it was read rather than
|
|
266
|
+
* claim it came off a CRL entry. */
|
|
267
|
+
function crlDoorTable(arm) {
|
|
268
|
+
return function (ctx) { return pkix.crlExtensionDecoders(ctx).doorScope[arm]; };
|
|
276
269
|
}
|
|
270
|
+
var CRL_SCOPES = intrinsic.assign(intrinsic.create(null), {
|
|
271
|
+
"crl": { decoders: crlDoorTable("crl"), criticality: pkix.crlFixedCriticality(oid) },
|
|
272
|
+
"crl-entry": { decoders: crlDoorTable("entry"), criticality: pkix.crlEntryFixedCriticality(oid) },
|
|
273
|
+
"ocsp-single": { decoders: crlDoorTable("entry"), criticality: pkix.crlEntryFixedCriticality(oid) },
|
|
274
|
+
});
|
|
275
|
+
|
|
276
|
+
/** @internal The parse decodes cRLNumber, reasonCode and invalidityDate in place, so its records
|
|
277
|
+
* carry the decoded value. The door reads the raw octets the record kept beside it, and decodes
|
|
278
|
+
* them itself under this call's caps and this call's scope. */
|
|
279
|
+
function rawRecords(list) {
|
|
280
|
+
return intrinsic.map(list || [], function (e) {
|
|
281
|
+
return { oid: e.oid, name: e.name, critical: e.critical, value: e.valueBytes };
|
|
282
|
+
});
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
var DOOR = pkix.extensionDoor({
|
|
286
|
+
ns: NS, ErrorClass: CrlError, key: "crl", kind: "crl", what: "the CRL",
|
|
287
|
+
parse: parse, scopes: CRL_SCOPES,
|
|
288
|
+
segments: function (crl) {
|
|
289
|
+
var out = [{ list: rawRecords(crl.crlExtensions), scope: "crl" }];
|
|
290
|
+
var revoked = crl.revokedCertificates || [];
|
|
291
|
+
for (var i = 0; i < revoked.length; i++) {
|
|
292
|
+
intrinsic.push(out, { list: rawRecords(revoked[i].crlEntryExtensions), scope: "crl-entry", containerIndex: i });
|
|
293
|
+
}
|
|
294
|
+
return out;
|
|
295
|
+
},
|
|
296
|
+
});
|
|
297
|
+
|
|
298
|
+
/**
|
|
299
|
+
* @primitive pki.schema.crl.decodeExtensions
|
|
300
|
+
* @signature pki.schema.crl.decodeExtensions(crlOrBytes, opts?) -> rows
|
|
301
|
+
* @since 0.8.11
|
|
302
|
+
* @status stable
|
|
303
|
+
* @spec RFC 5280 sec. 5.2, RFC 5280 sec. 5.3
|
|
304
|
+
* @related pki.schema.crl.parse, pki.schema.x509.decodeExtensions, pki.lint.crl
|
|
305
|
+
*
|
|
306
|
+
* Read a CRL's extensions as decoded records: the CRL's own extensions first, then each revoked
|
|
307
|
+
* entry's. The argument is the object `parse` returned or the bytes `parse` takes.
|
|
308
|
+
*
|
|
309
|
+
* One array spans both scopes rather than two, because RFC 5280 sec. 5.3 makes an unreadable
|
|
310
|
+
* critical ENTRY extension a verdict about the whole CRL, and a caller handed two lists can read
|
|
311
|
+
* one and not the other. Each row names where it was read: `scope` is `"crl"` or `"crl-entry"`, and
|
|
312
|
+
* `containerIndex` is the position of the revoked entry an entry row came from, or `null` for the
|
|
313
|
+
* CRL's own.
|
|
314
|
+
*
|
|
315
|
+
* Each row is `{ oid, name, critical, value, scope, index, containerIndex, state, decoded, code,
|
|
316
|
+
* profile }`. `state` is the discriminator: `"decoded"`, `"unrecognized"` for an OID no decoder is
|
|
317
|
+
* registered for AT THAT SCOPE, and `"undecodable"`. A `cRLNumber` on a revoked entry and a
|
|
318
|
+
* `reasonCode` on the CRL are each unrecognized rather than undecodable, because neither scope
|
|
319
|
+
* profiles the other's extension.
|
|
320
|
+
*
|
|
321
|
+
* @opts
|
|
322
|
+
* - `strict` (boolean) -- throw the decoder's own typed error instead of reporting an
|
|
323
|
+
* undecodable value. It does not turn an unrecognized OID into a throw.
|
|
324
|
+
* - `maxBytes` / `maxDepth` / `maxItems` (integer) -- decode caps for this call. Each may only
|
|
325
|
+
* tighten the built-in ceiling, and each bounds the decoding this call performs.
|
|
326
|
+
* @example
|
|
327
|
+
* var pair = await pki.key.generate("Ed25519");
|
|
328
|
+
* var key = await pki.key.export(pair.privateKey);
|
|
329
|
+
* var caCert = await pki.x509.sign({ subject: "Issuing CA", subjectPublicKey: await pki.key.export(pair.publicKey),
|
|
330
|
+
* notBefore: new Date("2026-01-01T00:00:00Z"), notAfter: new Date("2036-01-01T00:00:00Z"),
|
|
331
|
+
* extensions: { basicConstraints: { cA: true }, keyUsage: ["keyCertSign", "cRLSign"] } }, { key: key });
|
|
332
|
+
* var crlDer = await pki.crl.sign({ thisUpdate: new Date("2026-01-01T00:00:00Z"), crlNumber: 7n,
|
|
333
|
+
* revoked: [{ serialNumber: 0x0a3fn, revocationDate: new Date("2026-01-15T00:00:00Z"), reason: "keyCompromise" }] },
|
|
334
|
+
* { cert: caCert, key: key });
|
|
335
|
+
* var rows = pki.schema.crl.decodeExtensions(crlDer);
|
|
336
|
+
* rows.filter(function (r) { return r.scope === "crl-entry" && r.name === "reasonCode"; })[0].decoded; // -> 1
|
|
337
|
+
*/
|
|
338
|
+
var decodeExtensions = DOOR.decodeExtensions;
|
|
339
|
+
|
|
340
|
+
/**
|
|
341
|
+
* @primitive pki.schema.crl.decodeExtension
|
|
342
|
+
* @signature pki.schema.crl.decodeExtension(ext, opts) -> row
|
|
343
|
+
* @since 0.8.11
|
|
344
|
+
* @status stable
|
|
345
|
+
* @spec RFC 5280 sec. 5.2, RFC 5280 sec. 5.3, RFC 6960 sec. 4.4.5
|
|
346
|
+
* @related pki.schema.crl.decodeExtensions
|
|
347
|
+
*
|
|
348
|
+
* Read one CRL extension record as a decoded row. `opts.scope` is required and says which table to
|
|
349
|
+
* read it against, because the same identifier means different things at the three scopes a CRL
|
|
350
|
+
* reader meets. The argument is `{ oid, name, critical, value }`, with `value` the raw `extnValue`
|
|
351
|
+
* octets, which a `parse` result carries as `valueBytes`.
|
|
352
|
+
*
|
|
353
|
+
* @opts
|
|
354
|
+
* - `scope` (string, required) -- `"crl"` for a CRL's own extension, `"crl-entry"` for a revoked
|
|
355
|
+
* entry's, `"ocsp-single"` for one carried in an OCSP `SingleResponse` (RFC 6960 sec. 4.4.5).
|
|
356
|
+
* - `strict` (boolean) -- throw the decoder's own typed error instead of reporting an
|
|
357
|
+
* undecodable value.
|
|
358
|
+
* - `maxBytes` / `maxDepth` / `maxItems` (integer) -- decode caps for this call.
|
|
359
|
+
* @example
|
|
360
|
+
* var pair = await pki.key.generate("Ed25519");
|
|
361
|
+
* var key = await pki.key.export(pair.privateKey);
|
|
362
|
+
* var caCert = await pki.x509.sign({ subject: "Issuing CA", subjectPublicKey: await pki.key.export(pair.publicKey),
|
|
363
|
+
* notBefore: new Date("2026-01-01T00:00:00Z"), notAfter: new Date("2036-01-01T00:00:00Z"),
|
|
364
|
+
* extensions: { basicConstraints: { cA: true }, keyUsage: ["keyCertSign", "cRLSign"] } }, { key: key });
|
|
365
|
+
* var crlDer = await pki.crl.sign({ thisUpdate: new Date("2026-01-01T00:00:00Z"), crlNumber: 7n,
|
|
366
|
+
* revoked: [{ serialNumber: 0x0a3fn, revocationDate: new Date("2026-01-15T00:00:00Z"), reason: "keyCompromise" }] },
|
|
367
|
+
* { cert: caCert, key: key });
|
|
368
|
+
* var e = pki.schema.crl.parse(crlDer).revokedCertificates[0].crlEntryExtensions[0];
|
|
369
|
+
* pki.schema.crl.decodeExtension({ oid: e.oid, name: e.name, critical: e.critical, value: e.valueBytes },
|
|
370
|
+
* { scope: "crl-entry" }).decoded; // -> 1
|
|
371
|
+
*/
|
|
372
|
+
var decodeExtension = DOOR.decodeExtension;
|
|
277
373
|
|
|
278
374
|
module.exports = {
|
|
279
375
|
parse: parse,
|
|
280
376
|
pemDecode: pemDecode,
|
|
281
377
|
pemEncode: pemEncode,
|
|
282
378
|
matches: matches,
|
|
379
|
+
decodeExtensions: decodeExtensions,
|
|
380
|
+
decodeExtension: decodeExtension,
|
|
283
381
|
/** @internal The reader this parser applies to a CRL entry extension (reasonCode, invalidityDate),
|
|
284
382
|
* for a consumer that meets the same extension elsewhere: RFC 6960 sec. 4.4.5 carries every CRL
|
|
285
383
|
* entry extension into an OCSP SingleResponse, and the linter reads it with this rather than a
|
|
286
384
|
* second one. Not on the curated `pki.schema.crl` surface. */
|
|
287
|
-
|
|
385
|
+
decodeExt: decodeExt,
|
|
288
386
|
entryExtensionCriticality: entryExtensionCriticality,
|
|
289
387
|
readEntryExtension: readEntryExtension,
|
|
290
388
|
};
|
package/lib/schema-crmf.js
CHANGED
|
@@ -40,6 +40,7 @@ var pkix = require("./schema-pkix");
|
|
|
40
40
|
var cms = require("./schema-cms");
|
|
41
41
|
var oid = require("./oid");
|
|
42
42
|
var frameworkError = require("./framework-error");
|
|
43
|
+
var intrinsic = require("./guard-intrinsic");
|
|
43
44
|
|
|
44
45
|
var CrmfError = frameworkError.CrmfError;
|
|
45
46
|
var PemError = frameworkError.PemError;
|
|
@@ -140,7 +141,7 @@ var POPO_SIGNING_KEY = schema.seq([
|
|
|
140
141
|
},
|
|
141
142
|
});
|
|
142
143
|
|
|
143
|
-
var POPOPRIVKEY_METHODS =
|
|
144
|
+
var POPOPRIVKEY_METHODS = intrinsic.assign(intrinsic.create(null), { 0: "thisMessage", 1: "subsequentMessage", 2: "dhMAC", 3: "agreeMAC", 4: "encryptedKey" });
|
|
144
145
|
function popoPrivKey(type) {
|
|
145
146
|
return schema.decode(function (n, ctx) {
|
|
146
147
|
if (!n.children || n.children.length !== 1) {
|
|
@@ -203,7 +204,7 @@ var CONTROL = schema.seq([
|
|
|
203
204
|
return { type: t, name: ctx.oid.name(t) || null, value: m.fields.value.node.bytes };
|
|
204
205
|
},
|
|
205
206
|
});
|
|
206
|
-
function mapControls(m) { return m.items
|
|
207
|
+
function mapControls(m) { return intrinsic.map(m.items, function (it) { return it.value.result; }); }
|
|
207
208
|
var CONTROLS = schema.seqOf(CONTROL, { assert: "sequence", min: 1, code: "crmf/bad-controls", what: "Controls", build: mapControls });
|
|
208
209
|
var REG_INFO = schema.seqOf(CONTROL, { assert: "sequence", min: 1, code: "crmf/bad-reg-info", what: "regInfo", build: mapControls });
|
|
209
210
|
|
|
@@ -261,7 +262,7 @@ var CERT_REQUEST = schema.seq([
|
|
|
261
262
|
}
|
|
262
263
|
return {
|
|
263
264
|
certReqId: m.fields.certReqId.value,
|
|
264
|
-
certReqIdHex: m.fields.certReqId.node.content
|
|
265
|
+
certReqIdHex: intrinsic.bufToString(m.fields.certReqId.node.content, "hex"),
|
|
265
266
|
certTemplate: tpl,
|
|
266
267
|
controls: m.fields.controls.present ? m.fields.controls.value.result : null,
|
|
267
268
|
certReqBytes: m.node.bytes,
|
|
@@ -287,7 +288,7 @@ var CERT_REQ_MSG = schema.seq([
|
|
|
287
288
|
throw ctx.E("crmf/bad-popo", "poposkInput MUST be present when the CertTemplate lacks subject or publicKey (RFC 4211 sec. 4.1)");
|
|
288
289
|
}
|
|
289
290
|
if (popo.poposkInput !== null && certReq.certTemplate.publicKey !== null &&
|
|
290
|
-
!popo.poposkInput.publicKey
|
|
291
|
+
!intrinsic.bufferEquals(popo.poposkInput.publicKey, certReq.certTemplate.publicKey.bytes)) {
|
|
291
292
|
throw ctx.E("crmf/bad-popo", "POPOSigningKeyInput publicKey MUST equal the CertTemplate publicKey (RFC 4211 sec. 4.1)");
|
|
292
293
|
}
|
|
293
294
|
}
|
|
@@ -305,7 +306,7 @@ var CERT_REQ_MSG = schema.seq([
|
|
|
305
306
|
|
|
306
307
|
var CERT_REQ_MESSAGES = schema.seqOf(CERT_REQ_MSG, {
|
|
307
308
|
assert: "sequence", min: 1, code: "crmf/bad-cert-req-messages", what: "CertReqMessages",
|
|
308
|
-
build: function (m) { return { messages: m.items
|
|
309
|
+
build: function (m) { return { messages: intrinsic.map(m.items, function (it) { return it.value.result; }) }; },
|
|
309
310
|
});
|
|
310
311
|
|
|
311
312
|
|
|
@@ -422,7 +423,7 @@ function matches(root) {
|
|
|
422
423
|
function walkCertReqMessages(node, opts, outer) {
|
|
423
424
|
var ctx = schema.withResources(NS, outer);
|
|
424
425
|
if (opts && (opts.allowSigningAlg || opts.allowV1Version)) {
|
|
425
|
-
ctx =
|
|
426
|
+
ctx = intrinsic.assign({}, ctx);
|
|
426
427
|
if (opts.allowSigningAlg) ctx.allowSigningAlg = true;
|
|
427
428
|
if (opts.allowV1Version) ctx.allowV1Version = true;
|
|
428
429
|
}
|
|
@@ -432,11 +433,95 @@ function walkCertTemplate(node, outer) {
|
|
|
432
433
|
return schema.walk(CERT_TEMPLATE, node, schema.withResources(NS, outer)).result;
|
|
433
434
|
}
|
|
434
435
|
|
|
436
|
+
/** @internal A CertTemplate asks for certificate extensions, so the certificate table reads them.
|
|
437
|
+
* As with a PKCS#10 request, criticality is part of what is asked for and RFC 4211 sec. 5 leaves
|
|
438
|
+
* the decision to the CA; the criticality RFC 5280 fixes is reported all the same. */
|
|
439
|
+
var CRMF_SCOPES = intrinsic.assign(intrinsic.create(null), {
|
|
440
|
+
"crmf-template": {
|
|
441
|
+
decoders: function (ctx) { return pkix.certExtensionDecoders(ctx).byOid; },
|
|
442
|
+
criticality: pkix.certFixedCriticality(oid),
|
|
443
|
+
},
|
|
444
|
+
});
|
|
445
|
+
|
|
446
|
+
var DOOR = pkix.extensionDoor({
|
|
447
|
+
ns: NS, ErrorClass: CrmfError, key: "crmf", kind: "crmf", what: "the certificate request messages",
|
|
448
|
+
parse: parse, scopes: CRMF_SCOPES, defaultScope: "crmf-template",
|
|
449
|
+
segments: function (parsed) {
|
|
450
|
+
var messages = parsed.messages || [];
|
|
451
|
+
var out = [];
|
|
452
|
+
for (var i = 0; i < messages.length; i++) {
|
|
453
|
+
var template = messages[i].certReq && messages[i].certReq.certTemplate;
|
|
454
|
+
intrinsic.push(out, { list: (template && template.extensions) || [], scope: "crmf-template", containerIndex: i });
|
|
455
|
+
}
|
|
456
|
+
return out;
|
|
457
|
+
},
|
|
458
|
+
});
|
|
459
|
+
|
|
460
|
+
/**
|
|
461
|
+
* @primitive pki.schema.crmf.decodeExtensions
|
|
462
|
+
* @signature pki.schema.crmf.decodeExtensions(messagesOrBytes, opts?) -> rows
|
|
463
|
+
* @since 0.8.11
|
|
464
|
+
* @status stable
|
|
465
|
+
* @spec RFC 4211 sec. 5, RFC 5280 sec. 4.2
|
|
466
|
+
* @related pki.schema.crmf.parse, pki.schema.csr.decodeExtensions
|
|
467
|
+
*
|
|
468
|
+
* Read the extensions the certificate templates in a `CertReqMessages` ask for, as decoded records.
|
|
469
|
+
* The argument is the object `parse` returned or the bytes `parse` takes, and the rows of every
|
|
470
|
+
* message are returned in one array, each naming the message it came from in `containerIndex`.
|
|
471
|
+
*
|
|
472
|
+
* Each row is `{ oid, name, critical, value, scope, index, containerIndex, state, decoded, code,
|
|
473
|
+
* profile }`, with `scope` `"crmf-template"`. Criticality here is part of the request: RFC 4211
|
|
474
|
+
* sec. 5 leaves which requested extensions to honor to the CA, and `profile` reports what RFC 5280
|
|
475
|
+
* would fix for the certificate the request would produce.
|
|
476
|
+
*
|
|
477
|
+
* @opts
|
|
478
|
+
* - `strict` (boolean) -- throw the decoder's own typed error instead of reporting an
|
|
479
|
+
* undecodable value. It does not turn an unrecognized OID into a throw.
|
|
480
|
+
* - `maxBytes` / `maxDepth` / `maxItems` (integer) -- decode caps for this call.
|
|
481
|
+
* @example
|
|
482
|
+
* var pair = await pki.key.generate("Ed25519");
|
|
483
|
+
* var der = await pki.crmf.build(
|
|
484
|
+
* { certReqId: 0, certTemplate: { subject: "req.example", publicKey: await pki.key.export(pair.publicKey),
|
|
485
|
+
* extensions: { subjectAltName: [{ dNSName: "req.example" }] } } },
|
|
486
|
+
* { key: await pki.key.export(pair.privateKey) });
|
|
487
|
+
* var rows = pki.schema.crmf.decodeExtensions(der);
|
|
488
|
+
* rows[0].scope + " " + rows[0].name; // -> "crmf-template subjectAltName"
|
|
489
|
+
*/
|
|
490
|
+
var decodeExtensions = DOOR.decodeExtensions;
|
|
491
|
+
|
|
492
|
+
/**
|
|
493
|
+
* @primitive pki.schema.crmf.decodeExtension
|
|
494
|
+
* @signature pki.schema.crmf.decodeExtension(ext, opts?) -> row
|
|
495
|
+
* @since 0.8.11
|
|
496
|
+
* @status stable
|
|
497
|
+
* @spec RFC 4211 sec. 5
|
|
498
|
+
* @related pki.schema.crmf.decodeExtensions
|
|
499
|
+
*
|
|
500
|
+
* Read one requested extension record as a decoded row, at scope `"crmf-template"`. The argument is
|
|
501
|
+
* one element of a `certTemplate`'s `extensions`, as `{ oid, name, critical, value }`.
|
|
502
|
+
*
|
|
503
|
+
* @opts
|
|
504
|
+
* - `strict` (boolean) -- throw the decoder's own typed error instead of reporting an
|
|
505
|
+
* undecodable value.
|
|
506
|
+
* - `maxBytes` / `maxDepth` / `maxItems` (integer) -- decode caps for this call.
|
|
507
|
+
* @example
|
|
508
|
+
* var pair = await pki.key.generate("Ed25519");
|
|
509
|
+
* var der = await pki.crmf.build(
|
|
510
|
+
* { certReqId: 0, certTemplate: { subject: "req.example", publicKey: await pki.key.export(pair.publicKey),
|
|
511
|
+
* extensions: { subjectAltName: [{ dNSName: "req.example" }] } } },
|
|
512
|
+
* { key: await pki.key.export(pair.privateKey) });
|
|
513
|
+
* var msgs = pki.schema.crmf.parse(der);
|
|
514
|
+
* pki.schema.crmf.decodeExtension(msgs.messages[0].certReq.certTemplate.extensions[0]).name; // -> "subjectAltName"
|
|
515
|
+
*/
|
|
516
|
+
var decodeExtension = DOOR.decodeExtension;
|
|
517
|
+
|
|
435
518
|
module.exports = {
|
|
436
519
|
parse: parse,
|
|
437
520
|
pemDecode: pemDecode,
|
|
438
521
|
pemEncode: pemEncode,
|
|
439
522
|
matches: matches,
|
|
523
|
+
decodeExtensions: decodeExtensions,
|
|
524
|
+
decodeExtension: decodeExtension,
|
|
440
525
|
walkCertReqMessages: walkCertReqMessages,
|
|
441
526
|
walkCertTemplate: walkCertTemplate,
|
|
442
527
|
certReqMessagesSchema: CERT_REQ_MESSAGES,
|
package/lib/schema-csr.js
CHANGED
|
@@ -34,6 +34,7 @@ var schema = require("./schema-engine");
|
|
|
34
34
|
var pkix = require("./schema-pkix");
|
|
35
35
|
var oid = require("./oid");
|
|
36
36
|
var frameworkError = require("./framework-error");
|
|
37
|
+
var intrinsic = require("./guard-intrinsic");
|
|
37
38
|
|
|
38
39
|
var CsrError = frameworkError.CsrError;
|
|
39
40
|
var PemError = frameworkError.PemError;
|
|
@@ -52,16 +53,16 @@ var DIRECTORY_STRING_TAGS = [
|
|
|
52
53
|
asn1.TAGS.UTF8_STRING, asn1.TAGS.PRINTABLE_STRING, asn1.TAGS.TELETEX_STRING,
|
|
53
54
|
asn1.TAGS.UNIVERSAL_STRING, asn1.TAGS.BMP_STRING,
|
|
54
55
|
];
|
|
55
|
-
var RECOGNIZED_ATTRIBUTE_VALUE =
|
|
56
|
+
var RECOGNIZED_ATTRIBUTE_VALUE = intrinsic.create(null);
|
|
56
57
|
RECOGNIZED_ATTRIBUTE_VALUE[oid.byName("extensionRequest")] = function (node, ctx) {
|
|
57
58
|
try { return { extensions: schema.walk(EXTENSIONS, node, ctx).result }; }
|
|
58
59
|
catch (e) {
|
|
59
60
|
throw ctx.E("csr/bad-attribute-value",
|
|
60
|
-
"extensionRequest value must be a well-formed Extensions SEQUENCE (RFC 2985 sec. 5.4.2): " + ((e && e.message) || String(e)), e);
|
|
61
|
+
"extensionRequest value must be a well-formed Extensions SEQUENCE (RFC 2985 sec. 5.4.2): " + ((e && e.message) || intrinsic.String(e)), e);
|
|
61
62
|
}
|
|
62
63
|
};
|
|
63
64
|
RECOGNIZED_ATTRIBUTE_VALUE[oid.byName("challengePassword")] = function (node, ctx) {
|
|
64
|
-
if (node.tagClass !== "universal" ||
|
|
65
|
+
if (node.tagClass !== "universal" || intrinsic.indexOf(DIRECTORY_STRING_TAGS, node.tagNumber) === -1) {
|
|
65
66
|
throw ctx.E("csr/bad-attribute-value", "challengePassword must be a DirectoryString (RFC 2985 sec. 5.4.1)");
|
|
66
67
|
}
|
|
67
68
|
var s;
|
|
@@ -80,7 +81,7 @@ var CERTIFICATION_REQUEST_INFO = schema.seq([
|
|
|
80
81
|
], {
|
|
81
82
|
assert: "sequence", code: "csr/bad-cri", what: "certificationRequestInfo",
|
|
82
83
|
build: function (m, ctx) {
|
|
83
|
-
var attributes = m.fields.attributes.value.items
|
|
84
|
+
var attributes = intrinsic.map(m.fields.attributes.value.items, function (it) {
|
|
84
85
|
var a = it.value.result;
|
|
85
86
|
var checkValue = RECOGNIZED_ATTRIBUTE_VALUE[a.type];
|
|
86
87
|
if (checkValue) {
|
|
@@ -89,7 +90,7 @@ var CERTIFICATION_REQUEST_INFO = schema.seq([
|
|
|
89
90
|
throw ctx.E("csr/bad-attribute-value", (a.name || a.type) + " is a SINGLE VALUE attribute (RFC 2985 sec. 5.4)");
|
|
90
91
|
}
|
|
91
92
|
var enriched = checkValue(valueItems[0].node, ctx);
|
|
92
|
-
if (enriched) {
|
|
93
|
+
if (enriched) { intrinsic.forEach(intrinsic.keys(enriched), function (k) { a[k] = enriched[k]; }); }
|
|
93
94
|
}
|
|
94
95
|
return a;
|
|
95
96
|
});
|
|
@@ -205,10 +206,120 @@ function matches(root) {
|
|
|
205
206
|
schema.isContext(k[3], 0);
|
|
206
207
|
}
|
|
207
208
|
|
|
209
|
+
/** @internal A request asks for certificate extensions, so the certificate table reads them. What
|
|
210
|
+
* the scope changes is the verdict a caller draws: RFC 2985 sec. 5.4.2 makes criticality part of
|
|
211
|
+
* the REQUEST, and which requested extensions to honor, critical or not, is the CA's decision. The
|
|
212
|
+
* criticality profile is reported all the same, because a CA honoring a request against it would
|
|
213
|
+
* issue a non-conforming certificate. */
|
|
214
|
+
var CSR_SCOPES = intrinsic.assign(intrinsic.create(null), {
|
|
215
|
+
"csr-requested": {
|
|
216
|
+
decoders: function (ctx) { return pkix.certExtensionDecoders(ctx).byOid; },
|
|
217
|
+
criticality: pkix.certFixedCriticality(oid),
|
|
218
|
+
},
|
|
219
|
+
});
|
|
220
|
+
var OID_EXTENSION_REQUEST = oid.byName("extensionRequest");
|
|
221
|
+
|
|
222
|
+
function _err(code, message, cause) { return new CsrError(code, message, cause); }
|
|
223
|
+
|
|
224
|
+
var DOOR = pkix.extensionDoor({
|
|
225
|
+
ns: NS, ErrorClass: CsrError, key: "csr", kind: "csr", what: "the certification request",
|
|
226
|
+
parse: parse, scopes: CSR_SCOPES, defaultScope: "csr-requested", groups: true,
|
|
227
|
+
segments: function (csr, read) {
|
|
228
|
+
var attrs = csr.attributes || [];
|
|
229
|
+
var out = [];
|
|
230
|
+
for (var i = 0; i < attrs.length; i++) {
|
|
231
|
+
if (attrs[i].type !== OID_EXTENSION_REQUEST) continue;
|
|
232
|
+
intrinsic.push(out, { list: attrs[i].extensions || [], scope: "csr-requested", containerIndex: i });
|
|
233
|
+
}
|
|
234
|
+
if (!read.groups && out.length > 1) {
|
|
235
|
+
throw _err("csr/ambiguous-extension-request", "this request carries " + out.length +
|
|
236
|
+
" extensionRequest attributes, and which of them a CA should honor is not stated anywhere; " +
|
|
237
|
+
"pass { groups: true } to read each one separately");
|
|
238
|
+
}
|
|
239
|
+
return out;
|
|
240
|
+
},
|
|
241
|
+
group: function (rows, segments) {
|
|
242
|
+
var out = [], byIndex = intrinsic.create(null);
|
|
243
|
+
for (var s = 0; s < segments.length; s++) {
|
|
244
|
+
var g = { attributeIndex: segments[s].containerIndex, extensions: [] };
|
|
245
|
+
byIndex[segments[s].containerIndex] = g;
|
|
246
|
+
intrinsic.push(out, g);
|
|
247
|
+
}
|
|
248
|
+
for (var r = 0; r < rows.length; r++) intrinsic.push(byIndex[rows[r].containerIndex].extensions, rows[r]);
|
|
249
|
+
return out;
|
|
250
|
+
},
|
|
251
|
+
});
|
|
252
|
+
|
|
253
|
+
/**
|
|
254
|
+
* @primitive pki.schema.csr.decodeExtensions
|
|
255
|
+
* @signature pki.schema.csr.decodeExtensions(csrOrBytes, opts?) -> rows
|
|
256
|
+
* @since 0.8.11
|
|
257
|
+
* @status stable
|
|
258
|
+
* @spec RFC 2985 sec. 5.4.2, RFC 5280 sec. 4.2
|
|
259
|
+
* @related pki.schema.csr.parse, pki.schema.x509.decodeExtensions
|
|
260
|
+
*
|
|
261
|
+
* Read the extensions a certification request asks for, as decoded records. This is the verb a CA
|
|
262
|
+
* calls to see what a request wants before deciding what to issue. The argument is the object
|
|
263
|
+
* `parse` returned or the bytes `parse` takes.
|
|
264
|
+
*
|
|
265
|
+
* The rows come from the request's one `extensionRequest` attribute. A request carrying more than
|
|
266
|
+
* one is refused with `csr/ambiguous-extension-request`, because nothing says which of them a CA
|
|
267
|
+
* should honor; pass `groups: true` to read each attribute separately instead.
|
|
268
|
+
*
|
|
269
|
+
* Each row is `{ oid, name, critical, value, scope, index, containerIndex, state, decoded, code,
|
|
270
|
+
* profile }`, with `scope` `"csr-requested"` and `containerIndex` the position of the attribute the
|
|
271
|
+
* row came from. Criticality here is part of the request, not a verdict: RFC 2985 sec. 5.4.2 leaves
|
|
272
|
+
* which requested extensions to honor to the CA, and `profile` reports what RFC 5280 would fix for
|
|
273
|
+
* the certificate that request would produce.
|
|
274
|
+
*
|
|
275
|
+
* @opts
|
|
276
|
+
* - `groups` (boolean) -- return `[{ attributeIndex, extensions }]`, one entry per
|
|
277
|
+
* `extensionRequest` attribute, instead of one flat table. This is how a non-conforming request
|
|
278
|
+
* carrying several is read.
|
|
279
|
+
* - `strict` (boolean) -- throw the decoder's own typed error instead of reporting an
|
|
280
|
+
* undecodable value. It does not turn an unrecognized OID into a throw.
|
|
281
|
+
* - `maxBytes` / `maxDepth` / `maxItems` (integer) -- decode caps for this call.
|
|
282
|
+
* @example
|
|
283
|
+
* var pair = await pki.key.generate("Ed25519");
|
|
284
|
+
* var csrDer = await pki.csr.sign({ subject: "req.example", subjectPublicKey: await pki.key.export(pair.publicKey),
|
|
285
|
+
* extensionRequest: { subjectAltName: [{ dNSName: "req.example" }] } },
|
|
286
|
+
* { key: await pki.key.export(pair.privateKey) });
|
|
287
|
+
* var rows = pki.schema.csr.decodeExtensions(csrDer);
|
|
288
|
+
* rows.filter(function (r) { return r.name === "subjectAltName"; })[0].state; // -> "decoded"
|
|
289
|
+
*/
|
|
290
|
+
var decodeExtensions = DOOR.decodeExtensions;
|
|
291
|
+
|
|
292
|
+
/**
|
|
293
|
+
* @primitive pki.schema.csr.decodeExtension
|
|
294
|
+
* @signature pki.schema.csr.decodeExtension(ext, opts?) -> row
|
|
295
|
+
* @since 0.8.11
|
|
296
|
+
* @status stable
|
|
297
|
+
* @spec RFC 2985 sec. 5.4.2
|
|
298
|
+
* @related pki.schema.csr.decodeExtensions
|
|
299
|
+
*
|
|
300
|
+
* Read one requested extension record as a decoded row, at scope `"csr-requested"`. The argument is
|
|
301
|
+
* one element of an `extensionRequest` attribute's `extensions`, as `{ oid, name, critical, value }`.
|
|
302
|
+
*
|
|
303
|
+
* @opts
|
|
304
|
+
* - `strict` (boolean) -- throw the decoder's own typed error instead of reporting an
|
|
305
|
+
* undecodable value.
|
|
306
|
+
* - `maxBytes` / `maxDepth` / `maxItems` (integer) -- decode caps for this call.
|
|
307
|
+
* @example
|
|
308
|
+
* var pair = await pki.key.generate("Ed25519");
|
|
309
|
+
* var csrDer = await pki.csr.sign({ subject: "req.example", subjectPublicKey: await pki.key.export(pair.publicKey),
|
|
310
|
+
* extensionRequest: { subjectAltName: [{ dNSName: "req.example" }] } },
|
|
311
|
+
* { key: await pki.key.export(pair.privateKey) });
|
|
312
|
+
* var csr = pki.schema.csr.parse(csrDer);
|
|
313
|
+
* pki.schema.csr.decodeExtension(csr.attributes[0].extensions[0]).state; // -> "decoded"
|
|
314
|
+
*/
|
|
315
|
+
var decodeExtension = DOOR.decodeExtension;
|
|
316
|
+
|
|
208
317
|
module.exports = {
|
|
209
318
|
parse: parse,
|
|
210
319
|
pemDecode: pemDecode,
|
|
211
320
|
pemEncode: pemEncode,
|
|
212
321
|
matches: matches,
|
|
322
|
+
decodeExtensions: decodeExtensions,
|
|
323
|
+
decodeExtension: decodeExtension,
|
|
213
324
|
certificationRequestSchema: CERTIFICATION_REQUEST,
|
|
214
325
|
};
|