@blamejs/pki 0.5.16 → 0.5.18
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 +57 -1
- package/lib/acme.js +7 -4
- package/lib/asn1-der.js +1 -0
- package/lib/attrcert-sign.js +1 -0
- package/lib/cbor-det.js +1 -0
- package/lib/cmc-verify.js +1 -0
- package/lib/cmp-build.js +16 -1
- package/lib/cms-decrypt.js +58 -29
- package/lib/cms-encrypt.js +200 -38
- package/lib/cms-sign.js +18 -4
- package/lib/cms-verify.js +5 -1
- package/lib/constants.js +1 -0
- package/lib/crl-sign.js +62 -2
- package/lib/crmf-sign.js +1 -0
- package/lib/csr-sign.js +1 -0
- package/lib/ct.js +1 -0
- package/lib/est.js +1 -0
- package/lib/framework-error.js +1 -0
- package/lib/guard-secret.js +45 -1
- package/lib/hpke.js +6 -2
- package/lib/http-transport.js +1 -0
- package/lib/inspect.js +1 -0
- package/lib/jose.js +31 -3
- package/lib/key.js +2 -1
- package/lib/lint.js +1 -0
- package/lib/merkle.js +1 -0
- package/lib/ocsp.js +1 -0
- package/lib/oid.js +1 -0
- package/lib/path-validate.js +1 -0
- package/lib/pbes2.js +16 -8
- package/lib/pkcs12-build.js +203 -12
- package/lib/schema-all.js +1 -0
- package/lib/schema-c509.js +1 -0
- package/lib/schema-csrattrs.js +1 -0
- package/lib/schema-engine.js +1 -0
- package/lib/schema-pkcs8.js +1 -0
- package/lib/schema-smime.js +1 -0
- package/lib/schema-tsp.js +1 -0
- package/lib/schema-x509.js +1 -0
- package/lib/shbs.js +1 -0
- package/lib/sigstore.js +1 -0
- package/lib/smime.js +1 -0
- package/lib/tls-cert-compress.js +1 -0
- package/lib/trust.js +1 -0
- package/lib/tsp-sign.js +65 -0
- package/lib/webauthn.js +1 -0
- package/lib/webcrypto.js +11 -8
- package/lib/x509-sign.js +38 -0
- package/package.json +1 -1
- package/sbom.cdx.json +6 -6
package/lib/cms-encrypt.js
CHANGED
|
@@ -351,10 +351,10 @@ function _pwriWrapIv(kek, cek, iv) {
|
|
|
351
351
|
var wk = _pwriFormat(cek);
|
|
352
352
|
try {
|
|
353
353
|
var c1 = nodeCrypto.createCipheriv("aes-256-cbc", kek, iv); c1.setAutoPadding(false);
|
|
354
|
-
var pass1 =
|
|
354
|
+
var pass1 = guard.secret.cipherFinish(c1, wk, CmsError, "cms/bad-input", "the PWRI first-pass ciphertext");
|
|
355
355
|
var iv2 = pass1.subarray(pass1.length - 16);
|
|
356
356
|
var c2 = nodeCrypto.createCipheriv("aes-256-cbc", kek, iv2); c2.setAutoPadding(false);
|
|
357
|
-
return
|
|
357
|
+
return guard.secret.cipherFinish(c2, pass1, CmsError, "cms/bad-input", "the PWRI wrapped key");
|
|
358
358
|
} finally {
|
|
359
359
|
guard.secret.zeroize(wk, CmsError, "cms/bad-input", "the PWRI plaintext block");
|
|
360
360
|
}
|
|
@@ -364,22 +364,91 @@ var PRF_HASH = { hmacWithSHA1: "SHA-1", hmacWithSHA256: "SHA-256", hmacWithSHA38
|
|
|
364
364
|
function _prfHash(prf) { if (!PRF_HASH[prf]) throw _err("cms/bad-input", "unsupported pwri prf " + JSON.stringify(prf)); return PRF_HASH[prf]; }
|
|
365
365
|
|
|
366
366
|
// ---- recipient dispatch ----------------------------------------------------
|
|
367
|
-
|
|
367
|
+
// The fields a recipient descriptor carries, PER ARM. Each arm reads a disjoint set, and every
|
|
368
|
+
// field beyond the selector is optional with a default, so an unrecognized name is missed by
|
|
369
|
+
// nothing and the recipient is built with the shipped parameters instead: `oaepHsh` for `oaepHash`
|
|
370
|
+
// encrypts the content key under the default SHA-256 OAEP, and a misspelled `iterations`, `salt`
|
|
371
|
+
// or `prf` on a password recipient derives the key at the default work factor. A union table would
|
|
372
|
+
// admit another arm's fields, which the chosen arm never reads.
|
|
373
|
+
var _PWRI_KEYS = { password: 1, iterations: 1, prf: 1, salt: 1 };
|
|
374
|
+
var _KEKRI_KEYS = { kek: 1, kekId: 1 };
|
|
375
|
+
// A certificate recipient dispatches on the certificate's KEY ALGORITHM, and the arms read
|
|
376
|
+
// different fields: ktri (RSA) reads oaepHash and never ukm; kari (ECDH / X25519 / X448) and kemri
|
|
377
|
+
// (ML-KEM) read ukm and never oaepHash. One table across all three accepted `oaepHash` on an EC
|
|
378
|
+
// certificate, where nothing reads it, so the content key was wrapped under the arm's own defaults
|
|
379
|
+
// while the caller had named a hash. Checked after the key algorithm resolves, since that is what
|
|
380
|
+
// selects the arm.
|
|
381
|
+
var _KTRI_RECIPIENT_KEYS = { cert: 1, oaepHash: 1, keyIdentifier: 1 };
|
|
382
|
+
var _AGREE_RECIPIENT_KEYS = { cert: 1, keyIdentifier: 1, ukm: 1 };
|
|
383
|
+
|
|
384
|
+
// The three top-level options that are per-recipient DEFAULTS rather than message-wide settings.
|
|
385
|
+
// Which of them any given message actually consumes depends on the recipient arms it selects, so
|
|
386
|
+
// they are checked against the arms after those resolve, not against a fixed list.
|
|
387
|
+
var _RECIPIENT_DEFAULTS = ["oaepHash", "keyIdentifier", "ukm"];
|
|
388
|
+
|
|
389
|
+
function _assertRecipientKeys(desc, known, arm) {
|
|
390
|
+
guard.identifier.assertKnownKeys(desc, known, _err, "cms/bad-input", function (k) {
|
|
391
|
+
return "unknown field " + JSON.stringify(k) + " on a " + arm +
|
|
392
|
+
" recipient; that field is not read for this recipient type";
|
|
393
|
+
});
|
|
394
|
+
}
|
|
395
|
+
|
|
396
|
+
// A default no selected recipient reads is discarded exactly as a misspelling would be: `oaepHash`
|
|
397
|
+
// on a message whose only recipient is a password recipient names a wrapping the message does not
|
|
398
|
+
// perform. Run after every recipient's arm has resolved and BEFORE anything is emitted.
|
|
399
|
+
function _assertDefaultsConsumed(opts, consumed) {
|
|
400
|
+
for (var i = 0; i < _RECIPIENT_DEFAULTS.length; i++) {
|
|
401
|
+
var name = _RECIPIENT_DEFAULTS[i];
|
|
402
|
+
if (opts[name] != null && !consumed[name]) {
|
|
403
|
+
throw _err("cms/bad-input", "opts." + name + " is a per-recipient default and no recipient of this message reads it" +
|
|
404
|
+
(name === "oaepHash" ? " (only an RSA certificate recipient does)"
|
|
405
|
+
: name === "ukm" ? " (only a key-agreement or KEM certificate recipient does)"
|
|
406
|
+
: " (only a certificate recipient does)"));
|
|
407
|
+
}
|
|
408
|
+
}
|
|
409
|
+
}
|
|
410
|
+
|
|
411
|
+
async function _buildRecipient(cek, desc, opts, consumed) {
|
|
368
412
|
if (desc == null || typeof desc !== "object") throw _err("cms/bad-input", "each recipient must be a descriptor object");
|
|
369
|
-
|
|
370
|
-
|
|
413
|
+
// Checked per arm, on the same selector the dispatch below uses, so the table that runs is the
|
|
414
|
+
// one belonging to the arm that will build. `consumed` records which top-level defaults the
|
|
415
|
+
// selected arms read, so a default no recipient consumes can be refused once they all resolve.
|
|
416
|
+
if (desc.password != null) { _assertRecipientKeys(desc, _PWRI_KEYS, "password"); return _buildPwri(cek, desc); }
|
|
417
|
+
if (desc.kek != null) { _assertRecipientKeys(desc, _KEKRI_KEYS, "KEK"); return _buildKekri(cek, desc); }
|
|
371
418
|
if (desc.cert != null) {
|
|
372
419
|
var cert = x509.parse(_normCertDer(desc.cert, "a recipient certificate"));
|
|
373
420
|
var keyOid = cert.subjectPublicKeyInfo.algorithm.oid;
|
|
374
|
-
if (keyOid === O("rsaEncryption") || keyOid === O("rsassaPss"))
|
|
375
|
-
|
|
376
|
-
|
|
421
|
+
if (keyOid === O("rsaEncryption") || keyOid === O("rsassaPss")) {
|
|
422
|
+
_assertRecipientKeys(desc, _KTRI_RECIPIENT_KEYS, "RSA certificate");
|
|
423
|
+
return _buildKtri(cek, cert, mergeOpts(opts, desc, _KTRI_RECIPIENT_KEYS, consumed));
|
|
424
|
+
}
|
|
425
|
+
if (keyOid === O("ecPublicKey") || MONT_KA[keyOid]) {
|
|
426
|
+
_assertRecipientKeys(desc, _AGREE_RECIPIENT_KEYS, "key-agreement certificate");
|
|
427
|
+
return _buildKari(cek, cert, mergeOpts(opts, desc, _AGREE_RECIPIENT_KEYS, consumed));
|
|
428
|
+
}
|
|
429
|
+
if (KEM_WC[keyOid]) {
|
|
430
|
+
_assertRecipientKeys(desc, _AGREE_RECIPIENT_KEYS, "KEM certificate");
|
|
431
|
+
return _buildKemri(cek, cert, mergeOpts(opts, desc, _AGREE_RECIPIENT_KEYS, consumed));
|
|
432
|
+
}
|
|
377
433
|
throw _err("cms/unsupported-algorithm", "unsupported recipient certificate key algorithm " + keyOid);
|
|
378
434
|
}
|
|
379
435
|
throw _err("cms/bad-input", "a recipient needs { cert }, { password }, or { kek, kekId }");
|
|
380
436
|
}
|
|
381
|
-
|
|
382
|
-
|
|
437
|
+
// Each default is taken from the descriptor when it supplies one and from the top-level options
|
|
438
|
+
// otherwise. That one choice is also the record of which top-level defaults the recipient CONSUMED:
|
|
439
|
+
// an arm that does not read the name never consumes it, and neither does one whose descriptor
|
|
440
|
+
// overrode it, so recording it from the arm's table alone would accept a value nothing read.
|
|
441
|
+
function _pickDefault(desc, opts, name, known, consumed, nullish) {
|
|
442
|
+
var supplied = desc[name], fromDesc = nullish ? supplied != null : !!supplied;
|
|
443
|
+
if (!fromDesc && known[name] && consumed && opts[name] != null) consumed[name] = 1;
|
|
444
|
+
return fromDesc ? supplied : opts[name];
|
|
445
|
+
}
|
|
446
|
+
function mergeOpts(opts, desc, known, consumed) {
|
|
447
|
+
return {
|
|
448
|
+
oaepHash: _pickDefault(desc, opts, "oaepHash", known, consumed, false),
|
|
449
|
+
keyIdentifier: _pickDefault(desc, opts, "keyIdentifier", known, consumed, false),
|
|
450
|
+
ukm: _pickDefault(desc, opts, "ukm", known, consumed, true),
|
|
451
|
+
};
|
|
383
452
|
}
|
|
384
453
|
// RecipientInfo CHOICE: ktri untagged; kari [1], kekri [2], pwri [3], ori [4], all IMPLICIT,
|
|
385
454
|
// so the arm's SEQUENCE tag is replaced by the context tag.
|
|
@@ -397,12 +466,46 @@ function _envelopedVersion(recips, hasUnprotected) {
|
|
|
397
466
|
return forcesTwo ? 2 : 0;
|
|
398
467
|
}
|
|
399
468
|
|
|
469
|
+
// The two exported verbs have DISJOINT option sets, so they get separate tables: one shared table
|
|
470
|
+
// would admit every other verb's keys, which is the hole rather than the door. Each is the union of
|
|
471
|
+
// what the verb reads THROUGH ITS DELEGATIONS -- encrypt never names oaepHash, keyIdentifier or ukm
|
|
472
|
+
// itself, but hands opts to the per-recipient builders that do, and to _emit for pem.
|
|
473
|
+
// Authenticated attributes live in an AuthEnvelopedData, which only an AEAD content algorithm
|
|
474
|
+
// produces: a CBC algorithm selects EnvelopedData, which has no field for them and is not handed
|
|
475
|
+
// opts at all. Requested there, they named a protection the message does not carry.
|
|
476
|
+
var _ENCRYPT_OPTS_AEAD = {
|
|
477
|
+
contentEncryptionAlgorithm: 1, contentType: 1, authAttrs: 1, oaepHash: 1, keyIdentifier: 1,
|
|
478
|
+
ukm: 1, pem: 1,
|
|
479
|
+
};
|
|
480
|
+
var _ENCRYPT_OPTS_CBC = {
|
|
481
|
+
contentEncryptionAlgorithm: 1, contentType: 1, oaepHash: 1, keyIdentifier: 1, ukm: 1, pem: 1,
|
|
482
|
+
};
|
|
483
|
+
// authenticatedAttributes: false MACs the content octets directly, and the attribute set is what
|
|
484
|
+
// carries the digest: neither the digest algorithm nor any extra attribute is read on that branch.
|
|
485
|
+
var _AUTHENTICATE_OPTS_ATTRS = {
|
|
486
|
+
macAlgorithm: 1, digestAlgorithm: 1, authenticatedAttributes: 1, authAttrs: 1, contentType: 1,
|
|
487
|
+
keyIdentifier: 1, oaepHash: 1, ukm: 1, pem: 1,
|
|
488
|
+
};
|
|
489
|
+
var _AUTHENTICATE_OPTS_BARE = {
|
|
490
|
+
macAlgorithm: 1, authenticatedAttributes: 1, contentType: 1,
|
|
491
|
+
keyIdentifier: 1, oaepHash: 1, ukm: 1, pem: 1,
|
|
492
|
+
};
|
|
493
|
+
|
|
400
494
|
async function encrypt(content, recipients, opts) {
|
|
401
495
|
opts = opts || {};
|
|
402
|
-
|
|
496
|
+
// The content algorithm selects the structure, so it is resolved before the door and the table
|
|
497
|
+
// that runs is the one belonging to the structure this call will emit.
|
|
403
498
|
var algName = opts.contentEncryptionAlgorithm || "aes-256-gcm";
|
|
404
499
|
var ca = CONTENT_ALGS[algName];
|
|
405
500
|
if (!ca) throw _err("cms/bad-input", "unsupported contentEncryptionAlgorithm " + JSON.stringify(algName));
|
|
501
|
+
guard.identifier.assertKnownKeys(opts, ca.aead ? _ENCRYPT_OPTS_AEAD : _ENCRYPT_OPTS_CBC, _err, "cms/bad-input",
|
|
502
|
+
function (k) {
|
|
503
|
+
return "pki.cms.encrypt has an unknown option " + JSON.stringify(k) +
|
|
504
|
+
(!ca.aead && k === "authAttrs"
|
|
505
|
+
? " for a CBC content algorithm; authenticated attributes are carried only by an AuthEnvelopedData, which an AEAD algorithm produces"
|
|
506
|
+
: "");
|
|
507
|
+
});
|
|
508
|
+
var contentBytes = guard.bytes.view(content, CmsError, "cms/bad-input", "content");
|
|
406
509
|
var contentType = opts.contentType || "data";
|
|
407
510
|
var cek = nodeCrypto.randomBytes(ca.keyBits / 8);
|
|
408
511
|
|
|
@@ -415,7 +518,9 @@ async function encrypt(content, recipients, opts) {
|
|
|
415
518
|
|
|
416
519
|
if (!recipients.length) throw _err("cms/bad-input", "at least one recipient is required (RFC 5652 sec. 6.1)");
|
|
417
520
|
var recips = [];
|
|
418
|
-
|
|
521
|
+
var consumed = {};
|
|
522
|
+
for (var i = 0; i < recipients.length; i++) recips.push(await _buildRecipient(cek, recipients[i], opts, consumed));
|
|
523
|
+
_assertDefaultsConsumed(opts, consumed);
|
|
419
524
|
var riNodes = recips.map(_taggedRecipient);
|
|
420
525
|
|
|
421
526
|
if (ca.aead) return _emit(_authEnvelopedData(contentBytes, cek, ca, contentType, opts, riNodes, recips), "authEnvelopedData", opts);
|
|
@@ -432,11 +537,48 @@ function _emit(inner, ctName, opts) {
|
|
|
432
537
|
|
|
433
538
|
function _envelopedData(contentBytes, cek, ca, contentType, riNodes, recips) {
|
|
434
539
|
var iv = nodeCrypto.randomBytes(16);
|
|
435
|
-
var enc = pbes2.cbcEncrypt(cek, iv, contentBytes, ca.keyBits);
|
|
540
|
+
var enc = pbes2.cbcEncrypt(cek, iv, contentBytes, ca.keyBits, CmsError, "cms/bad-input");
|
|
436
541
|
var eci = b.sequence([b.oid(O(contentType)), b.sequence([b.oid(O(ca.oid)), b.octetString(iv)]), b.contextPrimitive(0, enc)]);
|
|
437
542
|
return b.sequence([b.integer(BigInt(_envelopedVersion(recips, false))), b.setOf(riNodes), eci]);
|
|
438
543
|
}
|
|
439
544
|
|
|
545
|
+
// Every authenticated attribute this module MACs -- auto-built or caller-supplied, in either content
|
|
546
|
+
// type that carries them -- MUST be a well-formed Attribute SEQUENCE { type OBJECT IDENTIFIER,
|
|
547
|
+
// non-empty SET OF value } with no repeated type (RFC 5652 sec. 5.3). Rejecting here means a
|
|
548
|
+
// malformed attribute never reaches the wire; leaving it to the wire means the operator learns from
|
|
549
|
+
// a peer's parser that the message they built is unreadable, with the MAC already computed over it.
|
|
550
|
+
//
|
|
551
|
+
// `forbidden` names an attribute type this particular content type must not emit, with the clause
|
|
552
|
+
// that says so. It is a parameter rather than a constant because the same attribute is required in
|
|
553
|
+
// one content type and discouraged in the other: AuthenticatedData MACs its own message-digest by
|
|
554
|
+
// design (RFC 5652 sec. 9.2), while for AuthEnvelopedData that value is the unencrypted hash of a
|
|
555
|
+
// plaintext the message exists to conceal (RFC 5083 sec. 2.1 / sec. 5).
|
|
556
|
+
function _assertAuthAttrs(pairs, forbidden) {
|
|
557
|
+
var seenTypes = {};
|
|
558
|
+
pairs.forEach(function (p) {
|
|
559
|
+
var node;
|
|
560
|
+
try { node = asn1.decode(p); } catch (e) { throw _err("cms/bad-input", "an authenticated attribute is not well-formed DER", e); }
|
|
561
|
+
if (node.tagClass !== "universal" || node.tagNumber !== asn1.TAGS.SEQUENCE || !node.children || node.children.length !== 2 ||
|
|
562
|
+
node.children[1].tagClass !== "universal" || node.children[1].tagNumber !== asn1.TAGS.SET ||
|
|
563
|
+
!node.children[1].children || node.children[1].children.length < 1) {
|
|
564
|
+
throw _err("cms/bad-input", "an authenticated attribute must be an Attribute SEQUENCE { type, non-empty SET OF value } (RFC 5652)");
|
|
565
|
+
}
|
|
566
|
+
var t;
|
|
567
|
+
try { t = asn1.read.oid(node.children[0]); } catch (e) { throw _err("cms/bad-input", "an authenticated attribute type is not an OBJECT IDENTIFIER", e); }
|
|
568
|
+
if (guard.intrinsic.hasOwn(seenTypes, t)) throw _err("cms/bad-input", "authenticated attributes must not repeat an attribute type (RFC 5652): " + t);
|
|
569
|
+
seenTypes[t] = 1;
|
|
570
|
+
if (forbidden && t === forbidden.oid) throw _err("cms/bad-input", forbidden.why);
|
|
571
|
+
});
|
|
572
|
+
}
|
|
573
|
+
|
|
574
|
+
// The one attribute AuthEnvelopedData declines to emit. Its value is the unencrypted one-way hash of
|
|
575
|
+
// the plaintext, so disclosing it enables content tracking and confirms a guessed plaintext against
|
|
576
|
+
// a message that was encrypted to prevent exactly that.
|
|
577
|
+
var AUTH_ENVELOPED_FORBIDDEN = {
|
|
578
|
+
oid: O("messageDigest"),
|
|
579
|
+
why: "authAttrs must not carry the message-digest attribute in an AuthEnvelopedData: its value is the unencrypted hash of the plaintext, which enables content tracking and confirmation of a guessed plaintext (RFC 5083 sec. 2.1, sec. 5)",
|
|
580
|
+
};
|
|
581
|
+
|
|
440
582
|
function _authEnvelopedData(contentBytes, cek, ca, contentType, opts, riNodes, recips) {
|
|
441
583
|
var nonce = nodeCrypto.randomBytes(12);
|
|
442
584
|
var authAttrsDer = null, aad = Buffer.alloc(0);
|
|
@@ -446,6 +588,7 @@ function _authEnvelopedData(contentBytes, cek, ca, contentType, opts, riNodes, r
|
|
|
446
588
|
throw _err("cms/bad-input", "AuthEnvelopedData with a non-data contentType requires authAttrs (RFC 5083 sec. 2.1)");
|
|
447
589
|
}
|
|
448
590
|
if (opts.authAttrs && opts.authAttrs.length) {
|
|
591
|
+
_assertAuthAttrs(opts.authAttrs, AUTH_ENVELOPED_FORBIDDEN);
|
|
449
592
|
// authAttrs are transmitted [1] IMPLICIT but MACed under the EXPLICIT SET OF tag (RFC 5083 sec. 2.2).
|
|
450
593
|
var setOf = b.setOf(opts.authAttrs);
|
|
451
594
|
aad = setOf; authAttrsDer = b.contextConstructed(1, setOf.subarray(_tlvHeaderLen(setOf)));
|
|
@@ -462,8 +605,31 @@ function _authEnvelopedData(contentBytes, cek, ca, contentType, opts, riNodes, r
|
|
|
462
605
|
return b.sequence(kids);
|
|
463
606
|
}
|
|
464
607
|
|
|
608
|
+
// The single descriptor an EncryptedData takes, PER ARM. `cek` and `password` select the arm and
|
|
609
|
+
// the arms read disjoint sets, so a union would admit the other arm's fields: `{ cek, iterations }`
|
|
610
|
+
// supplies a work factor to a branch that derives nothing, and the caller's parameter is discarded
|
|
611
|
+
// exactly as a misspelling would be. `pem` is read after both arms, so it belongs to each.
|
|
612
|
+
// pem selects the output encoding of the whole call and is an OPTION, the third argument, on every
|
|
613
|
+
// other verb here. A second spelling on the descriptor was read by the password arm alone, so the
|
|
614
|
+
// cek arm accepted it and returned DER; there is now one place to ask for PEM.
|
|
615
|
+
var _ENCRYPTED_DATA_CEK_KEYS = { cek: 1 };
|
|
616
|
+
var _ENCRYPTED_DATA_PW_KEYS = { password: 1, iterations: 1, salt: 1, prf: 1 };
|
|
617
|
+
|
|
465
618
|
function _encryptedData(contentBytes, desc, ca, contentType, opts, cek) {
|
|
466
619
|
if (ca.aead) throw _err("cms/bad-input", "EncryptedData supports only CBC content encryption");
|
|
620
|
+
// An EncryptedData carries no RecipientInfos at all, so nothing here reads a per-recipient
|
|
621
|
+
// default. This branch returns before the recipient loop, so it answers for them itself.
|
|
622
|
+
_assertDefaultsConsumed(opts, {});
|
|
623
|
+
// Checked per arm, on the same selector the dispatch below uses, so the table that runs belongs
|
|
624
|
+
// to the branch that will encrypt.
|
|
625
|
+
if (desc != null && typeof desc === "object") {
|
|
626
|
+
var edKeys = desc.cek != null ? _ENCRYPTED_DATA_CEK_KEYS : _ENCRYPTED_DATA_PW_KEYS;
|
|
627
|
+
var edArm = desc.cek != null ? "cek" : "password";
|
|
628
|
+
guard.identifier.assertKnownKeys(desc, edKeys, _err, "cms/bad-input", function (k) {
|
|
629
|
+
return "unknown field " + JSON.stringify(k) + " on a " + edArm +
|
|
630
|
+
" EncryptedData descriptor; that field is not read for this arm";
|
|
631
|
+
});
|
|
632
|
+
}
|
|
467
633
|
var iv = nodeCrypto.randomBytes(16);
|
|
468
634
|
var contentAlgNode, encKey;
|
|
469
635
|
if (desc && desc.cek != null) {
|
|
@@ -475,7 +641,7 @@ function _encryptedData(contentBytes, desc, ca, contentType, opts, cek) {
|
|
|
475
641
|
} else {
|
|
476
642
|
throw _err("cms/bad-input", "EncryptedData needs a single { cek } or { password } descriptor");
|
|
477
643
|
}
|
|
478
|
-
var enc = pbes2.cbcEncrypt(encKey, iv, contentBytes, ca.keyBits);
|
|
644
|
+
var enc = pbes2.cbcEncrypt(encKey, iv, contentBytes, ca.keyBits, CmsError, "cms/bad-input");
|
|
479
645
|
var eci = b.sequence([b.oid(O(contentType)), contentAlgNode, b.contextPrimitive(0, enc)]);
|
|
480
646
|
var inner = b.sequence([b.integer(0n), eci]);
|
|
481
647
|
return _emit(inner, "encryptedData", opts);
|
|
@@ -495,11 +661,11 @@ function _encryptedDataPbes2(contentBytes, desc, ca, contentType, iv, opts) {
|
|
|
495
661
|
var key = nodeCrypto.pbkdf2Sync(password, salt, iterations, ca.keyBits / 8, pbes2.prfNodeByName(prf, _err, "cms"));
|
|
496
662
|
if (pwOwn2.owned) guard.secret.zeroize(password, CmsError, "cms/bad-input", "the password encoding");
|
|
497
663
|
try {
|
|
498
|
-
var enc = pbes2.cbcEncrypt(key, iv, contentBytes, ca.keyBits);
|
|
664
|
+
var enc = pbes2.cbcEncrypt(key, iv, contentBytes, ca.keyBits, CmsError, "cms/bad-input");
|
|
499
665
|
var contentAlg = pbes2.pbes2AlgId(salt, iterations, prf, ca.oid, iv);
|
|
500
666
|
var eci = b.sequence([b.oid(O(contentType)), contentAlg, b.contextPrimitive(0, enc)]);
|
|
501
667
|
var inner = b.sequence([b.integer(0n), eci]);
|
|
502
|
-
return _emit(inner, "encryptedData",
|
|
668
|
+
return _emit(inner, "encryptedData", opts);
|
|
503
669
|
} finally {
|
|
504
670
|
guard.secret.zeroize(key, CmsError, "cms/bad-input", "the password-derived content-encryption key");
|
|
505
671
|
}
|
|
@@ -509,7 +675,7 @@ function _encryptedDataPbes2(contentBytes, desc, ca, contentType, iv, opts) {
|
|
|
509
675
|
function _gcmEncrypt(key, nonce, plaintext, aad, keyBits, tagLen) {
|
|
510
676
|
var c = nodeCrypto.createCipheriv("aes-" + keyBits + "-gcm", key, nonce, { authTagLength: tagLen });
|
|
511
677
|
if (aad && aad.length) c.setAAD(aad);
|
|
512
|
-
var ct =
|
|
678
|
+
var ct = guard.secret.cipherFinish(c, plaintext, CmsError, "cms/bad-input", "a content-encryption intermediate");
|
|
513
679
|
return { ct: ct, tag: c.getAuthTag() };
|
|
514
680
|
}
|
|
515
681
|
|
|
@@ -543,13 +709,22 @@ var SUPPORTED_DIGEST = { sha256: 1, sha384: 1, sha512: 1 };
|
|
|
543
709
|
|
|
544
710
|
async function authenticate(content, recipients, opts) {
|
|
545
711
|
opts = opts || {};
|
|
712
|
+
// The attribute branch is selected once, and the door and the MAC below both act on that one
|
|
713
|
+
// decision, so the table that runs belongs to the branch that will compute the MAC.
|
|
714
|
+
var withAttrs = opts.authenticatedAttributes !== false;
|
|
715
|
+
guard.identifier.assertKnownKeys(opts, withAttrs ? _AUTHENTICATE_OPTS_ATTRS : _AUTHENTICATE_OPTS_BARE,
|
|
716
|
+
_err, "cms/bad-input", function (k) {
|
|
717
|
+
return "pki.cms.authenticate has an unknown option " + JSON.stringify(k) +
|
|
718
|
+
(!withAttrs && (k === "digestAlgorithm" || k === "authAttrs")
|
|
719
|
+
? " with authenticatedAttributes: false; the MAC then covers the content octets directly and no attribute set is built"
|
|
720
|
+
: "");
|
|
721
|
+
});
|
|
546
722
|
var contentBytes = guard.bytes.view(content, CmsError, "cms/bad-input", "content");
|
|
547
723
|
var macName = opts.macAlgorithm || "hmac-sha256";
|
|
548
724
|
var mac = MAC_ALGS[macName];
|
|
549
725
|
if (!mac) throw _err("cms/bad-input", "unsupported macAlgorithm " + JSON.stringify(macName) + " (hmac-sha256/384/512)");
|
|
550
726
|
if (!Array.isArray(recipients) || !recipients.length) throw _err("cms/bad-input", "at least one recipient is required (RFC 5652 sec. 9.1)");
|
|
551
727
|
var contentType = opts.contentType || "data";
|
|
552
|
-
var withAttrs = opts.authenticatedAttributes !== false;
|
|
553
728
|
// RFC 5652 sec. 9.1: authAttrs MUST be present when the eContentType is not id-data.
|
|
554
729
|
if (contentType !== "data" && !withAttrs) throw _err("cms/bad-input", "AuthenticatedData with a non-data contentType requires authenticated attributes (RFC 5652 sec. 9.1)");
|
|
555
730
|
|
|
@@ -558,12 +733,16 @@ async function authenticate(content, recipients, opts) {
|
|
|
558
733
|
var macKey = nodeCrypto.randomBytes(MAC_KEY_OCTETS);
|
|
559
734
|
try {
|
|
560
735
|
var recips = [];
|
|
561
|
-
|
|
736
|
+
var consumedAuth = {};
|
|
737
|
+
for (var i = 0; i < recipients.length; i++) recips.push(await _buildRecipient(macKey, recipients[i], opts, consumedAuth));
|
|
738
|
+
_assertDefaultsConsumed(opts, consumedAuth);
|
|
562
739
|
var riNodes = recips.map(_taggedRecipient);
|
|
563
740
|
|
|
564
|
-
var digestName = opts.digestAlgorithm || mac.node;
|
|
565
741
|
var digestAlgTagged = null, authAttrsDer = null, preimage;
|
|
566
742
|
if (withAttrs) {
|
|
743
|
+
// Inside the branch that uses it: the attribute set is what carries the digest, so on the
|
|
744
|
+
// other branch there is no digest to name and opts.digestAlgorithm is refused at the door.
|
|
745
|
+
var digestName = opts.digestAlgorithm || mac.node;
|
|
567
746
|
if (!SUPPORTED_DIGEST[digestName]) throw _err("cms/bad-input", "unsupported digestAlgorithm " + JSON.stringify(digestName) + " (sha256/384/512)");
|
|
568
747
|
// sec. 9.2: content-type (== eContentType) + message-digest (== digest(content)) attributes,
|
|
569
748
|
// SET-OF-sorted, MACed under the EXPLICIT SET OF tag (0x31) but transmitted [2] IMPLICIT (0xA2).
|
|
@@ -573,24 +752,7 @@ async function authenticate(content, recipients, opts) {
|
|
|
573
752
|
b.sequence([b.oid(O("messageDigest")), b.setOf([b.octetString(mdDigest)])]),
|
|
574
753
|
];
|
|
575
754
|
if (opts.authAttrs && opts.authAttrs.length) pairs = pairs.concat(opts.authAttrs);
|
|
576
|
-
|
|
577
|
-
// { type OBJECT IDENTIFIER, values SET OF } and each type appears at most once (RFC 5652), so a
|
|
578
|
-
// malformed or duplicate caller attribute is rejected before it is MACed and emitted, never left to
|
|
579
|
-
// fail an operator's parser downstream.
|
|
580
|
-
var seenTypes = {};
|
|
581
|
-
pairs.forEach(function (p) {
|
|
582
|
-
var node;
|
|
583
|
-
try { node = asn1.decode(p); } catch (e) { throw _err("cms/bad-input", "an authenticated attribute is not well-formed DER", e); }
|
|
584
|
-
if (node.tagClass !== "universal" || node.tagNumber !== asn1.TAGS.SEQUENCE || !node.children || node.children.length !== 2 ||
|
|
585
|
-
node.children[1].tagClass !== "universal" || node.children[1].tagNumber !== asn1.TAGS.SET ||
|
|
586
|
-
!node.children[1].children || node.children[1].children.length < 1) {
|
|
587
|
-
throw _err("cms/bad-input", "an authenticated attribute must be an Attribute SEQUENCE { type, non-empty SET OF value } (RFC 5652)");
|
|
588
|
-
}
|
|
589
|
-
var t;
|
|
590
|
-
try { t = asn1.read.oid(node.children[0]); } catch (e) { throw _err("cms/bad-input", "an authenticated attribute type is not an OBJECT IDENTIFIER", e); }
|
|
591
|
-
if (seenTypes[t]) throw _err("cms/bad-input", "authenticated attributes must not repeat an attribute type (RFC 5652): " + t);
|
|
592
|
-
seenTypes[t] = 1;
|
|
593
|
-
});
|
|
755
|
+
_assertAuthAttrs(pairs, null);
|
|
594
756
|
var setOf = b.setOf(pairs);
|
|
595
757
|
preimage = setOf; // MAC over the 0x31 SET OF
|
|
596
758
|
authAttrsDer = b.contextConstructed(2, setOf.subarray(_tlvHeaderLen(setOf))); // [2] IMPLICIT on the wire
|
package/lib/cms-sign.js
CHANGED
|
@@ -43,6 +43,14 @@ var KNOWN_SIGN_OPTS = {
|
|
|
43
43
|
signedAttributes: 1, signingTime: 1, additionalSignedAttributes: 1, unsignedAttributes: 1,
|
|
44
44
|
sid: 1, eContentType: 1, detached: 1, certificates: 1, pem: 1,
|
|
45
45
|
};
|
|
46
|
+
// The SIGNER descriptor has two forms, and every field it reads is named here so a caller that
|
|
47
|
+
// authors one can be held to the form it selects: a certificate signer, and the key-only signer
|
|
48
|
+
// RFC 5272 sec. 3.2 requires for a Full PKI Request, whose identity comes from `spki` +
|
|
49
|
+
// `keyIdentifier` instead. The signature parameters are read by both. Exported for the verbs that
|
|
50
|
+
// take signer descriptors as AUTHORING input and gate them at their own door -- pki.cms.sign takes
|
|
51
|
+
// them straight from its caller, where a bag carrying extra handles is an ordinary shape.
|
|
52
|
+
var KNOWN_SIGNER_CERT_KEYS = { cert: 1, key: 1, pss: 1, digestAlgorithm: 1, combinedRsaSig: 1 };
|
|
53
|
+
var KNOWN_SIGNER_KEY_ONLY_KEYS = { spki: 1, keyIdentifier: 1, key: 1, pss: 1, digestAlgorithm: 1, combinedRsaSig: 1 };
|
|
46
54
|
// Countersign has its own set: it takes signerIndex and countersignatureOf to SELECT which
|
|
47
55
|
// signature it attaches to, and has no content of its own, so eContentType, detached and
|
|
48
56
|
// unsignedAttributes are not among the options it reads.
|
|
@@ -109,7 +117,7 @@ function _buildSid(cert, useSki) {
|
|
|
109
117
|
function _buildSignedAttrs(pairs) {
|
|
110
118
|
var seenTypes = {};
|
|
111
119
|
var attrs = pairs.map(function (p) {
|
|
112
|
-
if (seenTypes
|
|
120
|
+
if (guard.intrinsic.hasOwn(seenTypes, p.type)) throw _err("cms/bad-input", "signedAttrs must not repeat an attribute type (RFC 5652 sec. 5.3): " + p.type);
|
|
113
121
|
seenTypes[p.type] = 1;
|
|
114
122
|
return b.sequence([b.oid(p.type), b.set(p.values)]);
|
|
115
123
|
});
|
|
@@ -147,8 +155,8 @@ function _buildUnsignedAttrs(list) {
|
|
|
147
155
|
var pairs = _resolveAttrPairs(list, "an unsigned attribute value");
|
|
148
156
|
var seen = {};
|
|
149
157
|
pairs.forEach(function (p) {
|
|
150
|
-
if (UNSIGNED_FORBIDDEN
|
|
151
|
-
if (seen
|
|
158
|
+
if (guard.intrinsic.hasOwn(UNSIGNED_FORBIDDEN, p.type)) throw _err("cms/bad-input", "the " + UNSIGNED_FORBIDDEN[p.type] + " attribute must not appear as an unsigned attribute (RFC 5652 sec. 11)");
|
|
159
|
+
if (guard.intrinsic.hasOwn(seen, p.type)) throw _err("cms/bad-input", "unsignedAttrs must not repeat an attribute type (RFC 5652 sec. 5.3): " + p.type);
|
|
152
160
|
seen[p.type] = 1;
|
|
153
161
|
});
|
|
154
162
|
var setOf = b.set(pairs.map(function (p) { return b.sequence([b.oid(p.type), b.set(p.values)]); }));
|
|
@@ -658,4 +666,10 @@ function _countersign(cmsInput, signers, opts) {
|
|
|
658
666
|
// belt-and-suspenders against a future caller reordering the two.
|
|
659
667
|
// * the `crls [1]` preservation branches in `countersign`. pki.cms.sign never emits a crls field,
|
|
660
668
|
// so a store this producer countersigns never carries one to preserve.
|
|
661
|
-
module.exports = {
|
|
669
|
+
module.exports = {
|
|
670
|
+
sign: sign, countersign: countersign,
|
|
671
|
+
// @internal -- the signer-descriptor field tables, for a verb that AUTHORS signer descriptors
|
|
672
|
+
// from its own caller's options and gates them at its own door.
|
|
673
|
+
KNOWN_SIGNER_CERT_KEYS: KNOWN_SIGNER_CERT_KEYS,
|
|
674
|
+
KNOWN_SIGNER_KEY_ONLY_KEYS: KNOWN_SIGNER_KEY_ONLY_KEYS,
|
|
675
|
+
};
|
package/lib/cms-verify.js
CHANGED
|
@@ -5,6 +5,7 @@
|
|
|
5
5
|
* @module pki.cms
|
|
6
6
|
* @nav Verification
|
|
7
7
|
* @title CMS
|
|
8
|
+
* @fullname CMS (Cryptographic Message Syntax) signing and verification
|
|
8
9
|
* @intro Verify a CMS SignedData signature (RFC 5652 sec. 5) -- the structure S/MIME signed
|
|
9
10
|
* mail, RFC 3161 timestamp tokens, and code-signing all rest on. `verify(cms, opts)` parses
|
|
10
11
|
* the SignedData (over the strict `pki.schema.cms` codec), locates each SignerInfo's signer
|
|
@@ -1118,7 +1119,10 @@ var countersign = cmsSign.countersign;
|
|
|
1118
1119
|
* @opts oaepHash The RSAES-OAEP hash for ktri recipients: `"sha256"` (default) / `"sha384"` / `"sha512"`.
|
|
1119
1120
|
* @opts keyIdentifier The recipient identifier form: `"issuerAndSerial"` (default) or `"subjectKeyIdentifier"`.
|
|
1120
1121
|
* @opts ukm User keying material (a Buffer) for kari / kemri recipients.
|
|
1121
|
-
* @opts authAttrs Authenticated attributes (SET OF Attribute) for AuthEnvelopedData.
|
|
1122
|
+
* @opts authAttrs Authenticated attributes (SET OF Attribute) for AuthEnvelopedData. Each must be a
|
|
1123
|
+
* well-formed `Attribute SEQUENCE { type, non-empty SET OF value }` and no type may repeat (RFC 5652
|
|
1124
|
+
* sec. 5.3). `message-digest` is refused: its value is the unencrypted hash of the plaintext, which
|
|
1125
|
+
* enables content tracking and confirms a guessed plaintext (RFC 5083 sec. 2.1, sec. 5).
|
|
1122
1126
|
* @opts pem Return a PEM string instead of a DER Buffer.
|
|
1123
1127
|
* @example
|
|
1124
1128
|
* var rsa = { name: "RSA-OAEP", modulusLength: 2048, publicExponent: new Uint8Array([1, 0, 1]), hash: "SHA-256" };
|
package/lib/constants.js
CHANGED
package/lib/crl-sign.js
CHANGED
|
@@ -6,6 +6,7 @@
|
|
|
6
6
|
* @module pki.crl
|
|
7
7
|
* @nav Signing
|
|
8
8
|
* @title CRLs
|
|
9
|
+
* @fullname CRLs (certificate revocation lists): read, sign and shard
|
|
9
10
|
* @intro The X.509 CRL producing side. `pki.crl.sign` builds a `TBSCertList`, signs it, and emits a
|
|
10
11
|
* `CertificateList` (RFC 5280 sec. 5) that `pki.schema.crl.parse`, `pki.path.crlChecker`, and OpenSSL
|
|
11
12
|
* all accept -- over any signature algorithm the toolkit registry resolves: RSA (PKCS#1 v1.5 / PSS),
|
|
@@ -71,8 +72,38 @@ var REASON_BY_NAME = {};
|
|
|
71
72
|
Object.keys(CRL_REASON).forEach(function (v) { REASON_BY_NAME[CRL_REASON[v]] = Number(v); });
|
|
72
73
|
|
|
73
74
|
var KNOWN_CRL_EXT_KEYS = { authorityKeyIdentifier: 1, issuingDistributionPoint: 1, deltaCRLIndicator: 1, freshestCRL: 1, authorityInfoAccess: 1 };
|
|
75
|
+
// The three caller-owned objects pki.crl.sign reads. `crlNumber` and `extensions` are read by
|
|
76
|
+
// _buildCrlExtensions, which _sign hands the whole spec to, so they belong here even though _sign
|
|
77
|
+
// never names them -- the table is derived from what the call ACTUALLY reads, including through
|
|
78
|
+
// its delegations, not from the doc block.
|
|
79
|
+
var KNOWN_SPEC_KEYS = {
|
|
80
|
+
issuer: 1, thisUpdate: 1, nextUpdate: 1, revoked: 1, crlNumber: 1, extensions: 1,
|
|
81
|
+
};
|
|
82
|
+
// spec.issuer is the third place an issuer distinguished name can come from, and it is read only
|
|
83
|
+
// when neither issuer.cert nor issuer.name supplies one: both of those win outright. A spec naming
|
|
84
|
+
// one issuer beside a certificate for another was signed under the certificate's.
|
|
85
|
+
var KNOWN_SPEC_KEYS_ISSUER_NAMED = {
|
|
86
|
+
thisUpdate: 1, nextUpdate: 1, revoked: 1, crlNumber: 1, extensions: 1,
|
|
87
|
+
};
|
|
88
|
+
// Two FORMS, exactly as x509-sign has: when `issuer.cert` is present the builder takes the DN and
|
|
89
|
+
// the public key from it and never reads `name` or `publicKey`, so a union signed under the
|
|
90
|
+
// certificate's identity while discarding the one the caller wrote out.
|
|
91
|
+
var KNOWN_ISSUER_CERT_KEYS = { key: 1, cert: 1 };
|
|
92
|
+
var KNOWN_ISSUER_EXPLICIT_KEYS = { key: 1, name: 1, publicKey: 1 };
|
|
93
|
+
var KNOWN_SIGN_OPTS = { digestAlgorithm: 1, pem: 1, pss: 1 };
|
|
94
|
+
|
|
74
95
|
var KNOWN_IDP_KEYS = { fullName: 1, onlyContainsUserCerts: 1, onlyContainsCACerts: 1, onlyContainsAttributeCerts: 1, indirectCRL: 1 };
|
|
75
96
|
|
|
97
|
+
// The fields ONE revoked-certificate entry carries. Every optional one is encoded only when
|
|
98
|
+
// present, so a misspelling is missed by nothing and the entry is signed without it: `reson` for
|
|
99
|
+
// `reason` lists the certificate as revoked with no reasonCode at all, and a relying party reading
|
|
100
|
+
// the CRL cannot tell a caller who meant keyCompromise from one who gave no reason. The entry is
|
|
101
|
+
// where this bites hardest, because the CRL as a whole still verifies. `certificateIssuer` is
|
|
102
|
+
// listed because the builder DOES read it -- to refuse it with its own message.
|
|
103
|
+
var KNOWN_REVOKED_KEYS = {
|
|
104
|
+
serialNumber: 1, revocationDate: 1, reason: 1, invalidityDate: 1, certificateIssuer: 1, extensions: 1,
|
|
105
|
+
};
|
|
106
|
+
|
|
76
107
|
// RFC 5280 sec. 5.2 -- the profile-fixed criticality of each recognized CRL extension. cRLNumber (5.2.3),
|
|
77
108
|
// authorityKeyIdentifier (5.2.1), freshestCRL (5.2.6), and authorityInfoAccess (5.2.7) MUST be non-critical;
|
|
78
109
|
// issuingDistributionPoint (5.2.5) and deltaCRLIndicator (5.2.4) MUST be critical. Enforced on the object
|
|
@@ -311,6 +342,12 @@ function _buildRevoked(entryList, isDelta) {
|
|
|
311
342
|
var anyExt = false, seenSerials = {};
|
|
312
343
|
var entries = entryList.map(function (e, idx) {
|
|
313
344
|
if (!e || typeof e !== "object" || Buffer.isBuffer(e)) throw _err("crl/bad-input", "each revoked entry must be an object");
|
|
345
|
+
// Before any field of the entry is encoded, so an unrecognized name is a refusal rather than an
|
|
346
|
+
// entry signed without what it asked for.
|
|
347
|
+
guard.identifier.assertKnownKeys(e, KNOWN_REVOKED_KEYS, _err, "crl/bad-input", function (k) {
|
|
348
|
+
return "unknown field " + JSON.stringify(k) + " in revoked entry [" + idx +
|
|
349
|
+
"]; an entry is { serialNumber, revocationDate, reason?, invalidityDate?, extensions? }";
|
|
350
|
+
});
|
|
314
351
|
if (e.serialNumber == null) throw _err("crl/bad-input", "revoked entry [" + idx + "] requires a serialNumber");
|
|
315
352
|
var serialTlv = _serialInteger(e.serialNumber);
|
|
316
353
|
// RFC 5280 sec. 5.1.2.6 -- a serial number identifies a certificate uniquely, so a CRL MUST NOT list the
|
|
@@ -398,11 +435,34 @@ function _sign(spec, issuer, opts) {
|
|
|
398
435
|
opts = opts || {};
|
|
399
436
|
if (!spec || typeof spec !== "object" || Buffer.isBuffer(spec)) throw _err("crl/bad-input", "the CRL spec must be an object");
|
|
400
437
|
issuer = issuer || {};
|
|
438
|
+
// Before any field is read. A dropped key here is worse than elsewhere in the toolkit: a CRL
|
|
439
|
+
// asserts what is revoked, so the failure is a correctly signed, structurally valid CRL saying
|
|
440
|
+
// NOTHING is revoked. `revokedCertificates` earns its own hint because it is the name the parser
|
|
441
|
+
// uses for the same list -- a caller round-tripping a parsed CRL back into sign reaches for it
|
|
442
|
+
// first, and got a signed empty list.
|
|
443
|
+
// The issuer form is settled once, before either door, so the spec table matches the source the
|
|
444
|
+
// distinguished name will actually come from.
|
|
445
|
+
var byCert = issuer.cert != null, byName = issuer.name != null;
|
|
446
|
+
guard.identifier.assertKnownKeys(spec, byCert || byName ? KNOWN_SPEC_KEYS_ISSUER_NAMED : KNOWN_SPEC_KEYS,
|
|
447
|
+
_err, "crl/bad-input", function (k) {
|
|
448
|
+
return "unknown CRL spec field " + JSON.stringify(k) +
|
|
449
|
+
(k === "revokedCertificates" ? "; the producer reads `revoked` (the parser reports the same list as `revokedCertificates`)"
|
|
450
|
+
: k === "issuer" ? "; the issuer argument already names one, through " + (byCert ? "issuer.cert" : "issuer.name") +
|
|
451
|
+
", and that is the name the CRL is signed under"
|
|
452
|
+
: "");
|
|
453
|
+
});
|
|
454
|
+
guard.identifier.assertKnownKeys(issuer, byCert ? KNOWN_ISSUER_CERT_KEYS : KNOWN_ISSUER_EXPLICIT_KEYS,
|
|
455
|
+
_err, "crl/bad-input", function (k) {
|
|
456
|
+
return "unknown issuer field " + JSON.stringify(k) + " for the " + (byCert
|
|
457
|
+
? "certificate form; issuer.cert supplies the name and the public key, so they are not read here"
|
|
458
|
+
: "explicit form; the issuer takes { key, name, publicKey }, or { key, cert } to take both from a certificate");
|
|
459
|
+
});
|
|
460
|
+
guard.identifier.assertKnownKeys(opts, KNOWN_SIGN_OPTS, _err, "crl/bad-input", "pki.crl.sign has an unknown option ");
|
|
401
461
|
if (issuer.key == null) throw _err("crl/bad-input", "a signing key (issuer.key, a PKCS#8 private key) is required");
|
|
402
462
|
|
|
403
463
|
// Resolve the issuer name + signing-key SPKI. `{ cert }` supplies both; else `{ name|spec.issuer, publicKey }`.
|
|
404
464
|
var issuerDer, issuerSpki, issuerCert = null;
|
|
405
|
-
if (
|
|
465
|
+
if (byCert) {
|
|
406
466
|
issuerCert = _parseIssuerCert(issuer.cert);
|
|
407
467
|
_assertIssuerCanSignCrl(issuerCert);
|
|
408
468
|
issuerDer = pkiBuild.tbsNameField(issuerCert, "subject");
|
|
@@ -410,7 +470,7 @@ function _sign(spec, issuer, opts) {
|
|
|
410
470
|
} else {
|
|
411
471
|
issuerSpki = _reqDer(issuer.publicKey, "issuer.publicKey (the issuer SPKI DER)");
|
|
412
472
|
_assertValidSpki(issuerSpki, "issuer.publicKey");
|
|
413
|
-
var dnSource =
|
|
473
|
+
var dnSource = byName ? issuer.name : spec.issuer;
|
|
414
474
|
if (dnSource == null) throw _err("crl/bad-issuer", "an issuer distinguished name is required (issuer.name or spec.issuer) when no issuer.cert is given");
|
|
415
475
|
issuerDer = _encodeName(dnSource);
|
|
416
476
|
}
|
package/lib/crmf-sign.js
CHANGED
|
@@ -6,6 +6,7 @@
|
|
|
6
6
|
* @module pki.crmf
|
|
7
7
|
* @nav Signing
|
|
8
8
|
* @title Certificate request messages
|
|
9
|
+
* @fullname CRMF (Certificate Request Message Format, RFC 4211)
|
|
9
10
|
* @intro The RFC 4211 certificate-request-message producing side. `pki.crmf.build` assembles a
|
|
10
11
|
* `CertReqMessages` -- one or more `CertReqMsg`, each a `CertRequest` (a `CertTemplate` of the requested
|
|
11
12
|
* certificate fields plus optional controls) paired with a proof of possession. The common proof is a
|
package/lib/csr-sign.js
CHANGED
|
@@ -6,6 +6,7 @@
|
|
|
6
6
|
* @module pki.csr
|
|
7
7
|
* @nav Signing
|
|
8
8
|
* @title Certification requests
|
|
9
|
+
* @fullname CSRs (PKCS#10 certification requests): build, sign and verify
|
|
9
10
|
* @intro The PKCS#10 certification-request producing side. `pki.csr.sign` builds a
|
|
10
11
|
* `CertificationRequestInfo`, signs it with the subject's own private key (proof of possession, since
|
|
11
12
|
* a CSR has no issuer), and emits a `CertificationRequest` (RFC 2986) that `pki.schema.csr.parse`,
|
package/lib/ct.js
CHANGED
package/lib/est.js
CHANGED