@blamejs/pki 0.8.52 → 0.8.53
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 +20 -0
- package/README.md +1 -1
- package/lib/cms-decrypt.js +40 -3
- package/lib/cms-encrypt.js +64 -2
- package/lib/cms-sign.js +67 -2
- package/lib/cms-verify.js +148 -24
- package/lib/ct.js +3 -2
- package/lib/guard-der.js +26 -0
- package/lib/guard-identifier.js +27 -0
- package/lib/lint.js +69 -13
- package/lib/oid.js +11 -1
- package/lib/path-validate.js +82 -12
- package/lib/revocation-fetch.js +1 -3
- package/lib/scep.js +8 -3
- package/lib/schema-all.js +2 -1
- package/lib/schema-cms.js +377 -6
- package/package.json +1 -1
- package/sbom.cdx.json +6 -6
package/lib/cms-verify.js
CHANGED
|
@@ -232,23 +232,22 @@ function _hashAlgOid(seq) {
|
|
|
232
232
|
return o;
|
|
233
233
|
}
|
|
234
234
|
|
|
235
|
+
/** @internal The parameter fields come from `cms.taggedParameterFields`, which holds the DER rule
|
|
236
|
+
* for a SEQUENCE of tagged OPTIONAL and DEFAULT fields: one element per tag, ascending, no repeat,
|
|
237
|
+
* exactly one value each. The comparison that reads these same fields for their defaults walks the
|
|
238
|
+
* same reader, so the verifier and the comparison cannot disagree about what a field is. */
|
|
235
239
|
function _resolvePss(paramsBytes) {
|
|
236
240
|
if (!paramsBytes) return null;
|
|
237
|
-
var
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
var hash = null, saltLen = null, mgfNode = null, trailer = 1n, last = -1;
|
|
243
|
-
for (var i = 0; i < n.children.length; i++) {
|
|
244
|
-
var f = n.children[i];
|
|
245
|
-
if (f.tagClass !== "context" || f.tagNumber > 3 || f.tagNumber <= last || !f.children || f.children.length !== 1) return null;
|
|
246
|
-
last = f.tagNumber;
|
|
241
|
+
var fields = cms.taggedParameterFields(paramsBytes, 3);
|
|
242
|
+
if (fields === null) return null;
|
|
243
|
+
var hash = null, saltLen = null, mgfNode = null, trailer = 1n;
|
|
244
|
+
for (var i = 0; i < fields.length; i++) {
|
|
245
|
+
var f = fields[i];
|
|
247
246
|
try {
|
|
248
|
-
if (f.
|
|
249
|
-
else if (f.
|
|
250
|
-
else if (f.
|
|
251
|
-
else { trailer = asn1.read.integer(f.
|
|
247
|
+
if (f.tag === 0) { hash = PSS_HASH[_hashAlgOid(f.node)]; if (!hash) return null; }
|
|
248
|
+
else if (f.tag === 1) { mgfNode = f.node; }
|
|
249
|
+
else if (f.tag === 2) { saltLen = asn1.read.integer(f.node); }
|
|
250
|
+
else { trailer = asn1.read.integer(f.node); }
|
|
252
251
|
} catch (_e3) { return null; }
|
|
253
252
|
}
|
|
254
253
|
if (hash === null || mgfNode === null) return null;
|
|
@@ -464,6 +463,54 @@ function _surfaceSignedAttrs(si) {
|
|
|
464
463
|
});
|
|
465
464
|
}
|
|
466
465
|
|
|
466
|
+
/** @internal What the RFC 6211 algorithm protection attribute says about the structure carrying it,
|
|
467
|
+
* read ONCE per signer and used for both the verdict and the comparison, so the row an operator
|
|
468
|
+
* reads and the decision the verdict rests on cannot disagree.
|
|
469
|
+
*
|
|
470
|
+
* The attribute is a copy of the algorithm identifiers the structure already names, placed where
|
|
471
|
+
* the signature covers them. Sec. 3.1 makes both comparisons mandatory for a validator that
|
|
472
|
+
* supports the attribute, and support is claimed the moment this runs: "If the fields are not the
|
|
473
|
+
* same (modulo encoding), then signature validation MUST fail."
|
|
474
|
+
*
|
|
475
|
+
* Both arms compare modulo encoding, in the two ways sec. 3 names. A field with a default compares
|
|
476
|
+
* the same written out as omitted, which the clause makes a MUST and says a binary compare cannot
|
|
477
|
+
* satisfy; RSASSA-PSS is where that arises, since RFC 4055 clause 3.1 defaults all four of its
|
|
478
|
+
* parameter fields. Absent and DER NULL parameters read as one algorithm on the digest arm, because
|
|
479
|
+
* RFC 3370 clause 2.1 and RFC 5754 clause 2 require both encodings to be ACCEPTED; sec. 3 leaves
|
|
480
|
+
* that case to the implementer. Anything else in a parameter field is read as written: that is
|
|
481
|
+
* where RSASSA-PSS puts its hash, mask generator, salt length and trailer field, which the
|
|
482
|
+
* signature itself does not cover, so collapsing two values there would admit the substitution the
|
|
483
|
+
* attribute exists to refuse.
|
|
484
|
+
*
|
|
485
|
+
* A `[2]` MAC arm inside `signedAttrs` is a mismatch rather than an absent signature arm: sec. 2
|
|
486
|
+
* populates that arm "only if the attribute is placed in an AuthenticatedData.authAttrs sequence",
|
|
487
|
+
* so the attribute is making a claim about a structure this is not. */
|
|
488
|
+
function _algorithmProtectionReport(si, armName, armIdentifier) {
|
|
489
|
+
return cms.algorithmProtectionReport(si.signedAttrs || si.authAttrs || [],
|
|
490
|
+
si.digestAlgorithm, armName, armIdentifier);
|
|
491
|
+
}
|
|
492
|
+
|
|
493
|
+
/** @internal The report applied to a verdict. A mismatch fails the signer, and the option that
|
|
494
|
+
* requires the attribute fails an absent one; a verdict that already failed keeps its own code,
|
|
495
|
+
* since the signature's verdict is the stronger statement and masking it would tell an operator the
|
|
496
|
+
* wrong thing about why the message was refused. */
|
|
497
|
+
function _applyAlgorithmProtection(verdict, report, required) {
|
|
498
|
+
var fields = { algorithmProtection: guard.verdict.of(report) };
|
|
499
|
+
if (verdict.ok !== true) return guard.verdict.of(verdict, fields);
|
|
500
|
+
if (report.mismatch !== null) {
|
|
501
|
+
return guard.verdict.of(verdict, intrinsic.assign(fields,
|
|
502
|
+
{ ok: false, code: report.mismatch.code, message: report.mismatch.message }));
|
|
503
|
+
}
|
|
504
|
+
if (required && !report.present) {
|
|
505
|
+
return guard.verdict.of(verdict, intrinsic.assign(fields, { ok: false,
|
|
506
|
+
code: "cms/missing-algorithm-protection",
|
|
507
|
+
message: "this signature carries no RFC 6211 algorithm protection attribute, which " +
|
|
508
|
+
"opts.requireAlgorithmProtection asks for; RFC 8933 sec. 4.1 makes including it a SHOULD, " +
|
|
509
|
+
"so a message without one is not malformed" }));
|
|
510
|
+
}
|
|
511
|
+
return guard.verdict.of(verdict, fields);
|
|
512
|
+
}
|
|
513
|
+
|
|
467
514
|
/** @internal The CAdES baseline requirements this verb decides from one SignerInfo's own signed
|
|
468
515
|
* attributes, reported beside the signature verdict rather than folded into it: a signature can be
|
|
469
516
|
* cryptographically sound and still not be a CAdES signature, and the two answers are separate.
|
|
@@ -784,7 +831,7 @@ function _signerNotAfter(verdict) {
|
|
|
784
831
|
catch (_e) { return null; }
|
|
785
832
|
}
|
|
786
833
|
|
|
787
|
-
async function _verifyCountersignatures(si, parsedCerts, allowWeakDigests) {
|
|
834
|
+
async function _verifyCountersignatures(si, parsedCerts, allowWeakDigests, requireProtection) {
|
|
788
835
|
var targetSig = _toBuf(si.signature, "the countersigned signature");
|
|
789
836
|
var values = [];
|
|
790
837
|
intrinsic.forEach(si.unsignedAttrs || [], function (a) {
|
|
@@ -792,17 +839,24 @@ async function _verifyCountersignatures(si, parsedCerts, allowWeakDigests) {
|
|
|
792
839
|
intrinsic.forEach(a.values, function (v) { guard.list.append(values, v && v.bytes ? v.bytes : v); });
|
|
793
840
|
});
|
|
794
841
|
var out = [];
|
|
795
|
-
for (var _cp = 0; _cp < values.length; _cp++) guard.list.append(out, await _verifyOneCountersig(values[_cp], targetSig, parsedCerts, allowWeakDigests));
|
|
842
|
+
for (var _cp = 0; _cp < values.length; _cp++) guard.list.append(out, await _verifyOneCountersig(values[_cp], targetSig, parsedCerts, allowWeakDigests, requireProtection));
|
|
796
843
|
return out;
|
|
797
844
|
}
|
|
798
845
|
|
|
799
|
-
async function _verifyOneCountersig(vDer, targetSig, parsedCerts, allowWeakDigests) {
|
|
846
|
+
async function _verifyOneCountersig(vDer, targetSig, parsedCerts, allowWeakDigests, requireProtection) {
|
|
800
847
|
var csSi;
|
|
801
848
|
try { csSi = cms.walkCountersignature(asn1.decode(intrinsic.isBuffer(vDer) ? vDer : intrinsic.bufferFrom(vDer))); }
|
|
802
849
|
catch (e) { return _row({ ok: false, code: (e instanceof CmsError ? e.code : "cms/bad-countersignature"), message: e && e.message }); }
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
|
|
850
|
+
/** @internal A countersignature is its own SignerInfo with its own algorithm identifiers, so the
|
|
851
|
+
* attribute inside it protects those and is compared against them rather than against the
|
|
852
|
+
* signature it countersigns. */
|
|
853
|
+
var apReport = _algorithmProtectionReport(csSi, "signatureAlgorithm", csSi.signatureAlgorithm);
|
|
854
|
+
var verdict = _applyAlgorithmProtection(
|
|
855
|
+
await _verifyOne(csSi, targetSig, null, parsedCerts, targetSig, undefined, allowWeakDigests),
|
|
856
|
+
apReport, requireProtection === true);
|
|
857
|
+
var nested = await _verifyCountersignatures(csSi, parsedCerts, allowWeakDigests, requireProtection);
|
|
858
|
+
var head = { ok: verdict.ok, sid: verdict.sid, cert: verdict.cert, digestAlgorithm: csSi.digestAlgorithm.name,
|
|
859
|
+
algorithmProtection: verdict.algorithmProtection };
|
|
806
860
|
return guard.verdict.of(
|
|
807
861
|
verdict.code ? guard.verdict.of(head, { code: verdict.code }) : head,
|
|
808
862
|
{ unsignedAttrs: _surfaceUnsignedAttrs(csSi), countersignatures: nested });
|
|
@@ -822,7 +876,7 @@ async function _verifyOneCountersig(vDer, targetSig, parsedCerts, allowWeakDiges
|
|
|
822
876
|
* Verify a CMS SignedData signature (RFC 5652 sec. 5). `input` is a PEM string, a DER
|
|
823
877
|
* `Buffer`, or a parsed `pki.schema.cms` object. Returns `{ valid, trusted, eContentType, eContent, signers }`
|
|
824
878
|
* (`eContent` the encapsulated content when present, else null) where each `signers[i]` is
|
|
825
|
-
* `{ ok, sid, cert, trusted, signedAttributesPresent, signedAttributes, unsignedAttrs, digestAlgorithm, countersignatures, signatureTimeStamps, cadesBaseline }`
|
|
879
|
+
* `{ ok, sid, cert, trusted, signedAttributesPresent, signedAttributes, unsignedAttrs, digestAlgorithm, countersignatures, signatureTimeStamps, cadesBaseline, algorithmProtection }`
|
|
826
880
|
* (`cert` the matched signer certificate DER) or carries a `code` on a structural failure; `valid` is true when
|
|
827
881
|
* there is at least one signer and every signer verified.
|
|
828
882
|
*
|
|
@@ -873,6 +927,15 @@ async function _verifyOneCountersig(vDer, targetSig, parsedCerts, allowWeakDiges
|
|
|
873
927
|
* content, and a token over it answers for either. `pki.cms.attachTimestamp` adds such an attribute to an
|
|
874
928
|
* existing message and `pki.cms.timestampImprint` produces the imprint a TSA signs.
|
|
875
929
|
*
|
|
930
|
+
* `algorithmProtection` reports the RFC 6211 attribute, `{ present, oid, compared, mismatch }`. The
|
|
931
|
+
* attribute is a copy of the digest and signature algorithm identifiers this signer names, placed
|
|
932
|
+
* where the signature covers them, and `compared` lists the identifiers checked against it. A
|
|
933
|
+
* disagreement fails the signer with `cms/algorithm-protection-mismatch`, which sec. 3.1 requires of
|
|
934
|
+
* any validator that supports the attribute: the identifiers the attribute protects are otherwise
|
|
935
|
+
* covered by nothing, and RFC 8933 sec. 6 names RSASSA-PSS as the case where the signature itself
|
|
936
|
+
* pins no digest. An absent attribute is not a failure unless `requireAlgorithmProtection` asks for
|
|
937
|
+
* one, and `present: false` says so rather than looking like a comparison that passed.
|
|
938
|
+
*
|
|
876
939
|
* `cadesBaseline` is a third claim, `{ conformant, signingCertificateAttribute, findings,
|
|
877
940
|
* requirementsChecked }`: a signature can be sound and still not be a CAdES signature.
|
|
878
941
|
* `requirementsChecked` lists the EN 319 122-1 requirements decided here, each unmet one carrying a
|
|
@@ -908,6 +971,14 @@ async function _verifyOneCountersig(vDer, targetSig, parsedCerts, allowWeakDiges
|
|
|
908
971
|
* trust decision from the signer: an operator may anchor one and not the other.
|
|
909
972
|
* @opts timestampRevocationChecker A revocation checker for the TSA chain, as `pki.path.validate`
|
|
910
973
|
* takes one. Each row reports what it established in `revocationChecked`.
|
|
974
|
+
* @opts requireAlgorithmProtection Fail a signer that carries no RFC 6211
|
|
975
|
+
* `CMSAlgorithmProtection` attribute, with `cms/missing-algorithm-protection`. Off
|
|
976
|
+
* by default: RFC 6211 says nothing about absence and RFC 8933 sec. 4.1 makes
|
|
977
|
+
* including it a SHOULD, so a message without one is not malformed and most CMS in
|
|
978
|
+
* the field carries none. A signer that DOES carry one is always held to it, with no
|
|
979
|
+
* option, because sec. 3.1's comparisons are MUSTs once a validator supports the
|
|
980
|
+
* attribute. The option is the boolean it documents: `false`, `null` and an absent
|
|
981
|
+
* option are the off forms, and any other value is refused `cms/bad-input`.
|
|
911
982
|
* @example
|
|
912
983
|
* var pair = await pki.key.generate("Ed25519");
|
|
913
984
|
* var key = await pki.key.export(pair.privateKey);
|
|
@@ -950,7 +1021,8 @@ var _VERIFY_OPTS = intrinsic.assign(intrinsic.create(null), { certs: 1, content:
|
|
|
950
1021
|
* authority is a different trust decision from the signer's: an operator may anchor the TSA while
|
|
951
1022
|
* leaving the signer unanchored, or the reverse, and one `trustAnchors` for both would make those
|
|
952
1023
|
* two answers one. */
|
|
953
|
-
timestampCerts: 1, timestampTrustAnchors: 1, timestampRevocationChecker: 1
|
|
1024
|
+
timestampCerts: 1, timestampTrustAnchors: 1, timestampRevocationChecker: 1,
|
|
1025
|
+
requireAlgorithmProtection: 1 });
|
|
954
1026
|
|
|
955
1027
|
function verify(input, opts) {
|
|
956
1028
|
return guard.async.deferred(function () { return _verify(input, opts); });
|
|
@@ -959,12 +1031,19 @@ function verify(input, opts) {
|
|
|
959
1031
|
async function _verify(input, opts) {
|
|
960
1032
|
opts = guard.identifier.optionsObject(opts, _err, "cms/bad-input", "pki.cms.verify options");
|
|
961
1033
|
guard.identifier.assertKnownKeys(opts, _VERIFY_OPTS, _err, "cms/bad-input", "pki.cms.verify has an unknown option ");
|
|
1034
|
+
/** @internal The two policy switches are taken FIRST, ahead of everything below that can run the
|
|
1035
|
+
* caller's own code. Deriving the message runs accessors on a parsed input, copying the trust
|
|
1036
|
+
* material runs them on each anchor, and a streamed detached content runs the generator while
|
|
1037
|
+
* this verb is pending; any of those can write to an options object the caller still holds, so a
|
|
1038
|
+
* switch read after them is read from whatever they left behind rather than from the call. */
|
|
1039
|
+
var allowWeakDigests = opts.allowWeakDigests === true;
|
|
1040
|
+
var requireProtection = guard.identifier.booleanOption(opts.requireAlgorithmProtection, _err,
|
|
1041
|
+
"cms/bad-input", "pki.cms.verify opts.requireAlgorithmProtection");
|
|
962
1042
|
var parsed = guard.parsed.acceptDerived(input, "cms", function (bytes) {
|
|
963
1043
|
return cms.parse(_snapshotIfBytes(bytes, "pki.cms.verify"));
|
|
964
1044
|
}, _err, "cms/bad-input", "the SignedData");
|
|
965
1045
|
if (!Array.isArray(parsed.signerInfos)) throw _err("cms/bad-input", "input is not a CMS SignedData");
|
|
966
1046
|
var trustCfg = _snapshotTrust(opts);
|
|
967
|
-
var allowWeakDigests = opts.allowWeakDigests === true;
|
|
968
1047
|
var tsTrustCfg = _snapshotTimestampTrust(opts, allowWeakDigests);
|
|
969
1048
|
var parsedCerts = [];
|
|
970
1049
|
(parsed.certificates || []).forEach(function (c) { _addCert(parsedCerts, c && c.bytes ? c.bytes : c); });
|
|
@@ -991,8 +1070,13 @@ async function _verify(input, opts) {
|
|
|
991
1070
|
var signers = [];
|
|
992
1071
|
for (var _sp = 0; _sp < parsed.signerInfos.length; _sp++) {
|
|
993
1072
|
var siOne = parsed.signerInfos[_sp];
|
|
1073
|
+
/** @internal Read once, before the signature is checked, and used for both the verdict row and
|
|
1074
|
+
* the decision: the attribute is an independent requirement on the same signer, the way trust
|
|
1075
|
+
* is, so it composes onto the verdict rather than being folded into the signature check. */
|
|
1076
|
+
var apReport = _algorithmProtectionReport(siOne, "signatureAlgorithm", siOne.signatureAlgorithm);
|
|
994
1077
|
var verdict = await _verifyOne(siOne, content, eContentType, parsedCerts, null, digestByName, allowWeakDigests);
|
|
995
|
-
|
|
1078
|
+
verdict = _applyAlgorithmProtection(verdict, apReport, requireProtection);
|
|
1079
|
+
var counters = await _verifyCountersignatures(siOne, parsedCerts, allowWeakDigests, requireProtection);
|
|
996
1080
|
/** @internal The timestamp rows are built from the same `signature` octets the signature verdict
|
|
997
1081
|
* above was computed over, which is why they are here and not in a verb of their own. The
|
|
998
1082
|
* signer's own certificate supplies the expiry half of EN 319 122-1 clause 6.3 requirement m. */
|
|
@@ -1204,6 +1288,17 @@ function _addCert(out, der) {
|
|
|
1204
1288
|
* (`timeStampToken`); content-type / message-digest / signing-time are
|
|
1205
1289
|
* rejected here (RFC 5652 sec. 11). Use `pki.cms.countersign` for a
|
|
1206
1290
|
* countersignature specifically.
|
|
1291
|
+
* @opts algorithmProtection Emit the RFC 6211 `CMSAlgorithmProtection` signed attribute, a copy of
|
|
1292
|
+
* this signer's digest and signature algorithm identifiers placed where the
|
|
1293
|
+
* signature covers them. RFC 8933 sec. 4.1 makes including it a SHOULD, and
|
|
1294
|
+
* RFC 9882 sec. 4 and RFC 9814 sec. 5 repeat that for ML-DSA and SLH-DSA,
|
|
1295
|
+
* which carry no digest identifier inside the signature. Off by default,
|
|
1296
|
+
* because turning it on changes the bytes of every signature. `true` emits
|
|
1297
|
+
* the identifier RFC 6211 sec. 2 states, which OpenSSL and Bouncy Castle
|
|
1298
|
+
* use; `"registry"` emits the S/MIME arc value the IANA registry and
|
|
1299
|
+
* draft-ietf-lamps-rfc6211-update give it. A verifier here reads both.
|
|
1300
|
+
* `false`, `null` and an absent option emit none, and any other value is
|
|
1301
|
+
* refused `cms/bad-input`.
|
|
1207
1302
|
* @example
|
|
1208
1303
|
* var pair = await pki.key.generate("Ed25519");
|
|
1209
1304
|
* var signerKeyPkcs8 = await pki.key.export(pair.privateKey);
|
|
@@ -1246,6 +1341,10 @@ var sign = cmsSign.sign;
|
|
|
1246
1341
|
* is rejected, RFC 5652 sec. 11.4).
|
|
1247
1342
|
* @opts sid `"issuerAndSerial"` (default) or `"ski"`.
|
|
1248
1343
|
* @opts certificates Embed the countersigner certificate(s) in the SignedData. Default true.
|
|
1344
|
+
* @opts algorithmProtection Emit the RFC 6211 `CMSAlgorithmProtection` attribute on the
|
|
1345
|
+
* countersignature, copying ITS own digest and signature algorithm
|
|
1346
|
+
* identifiers rather than the countersigned signature's. Off by
|
|
1347
|
+
* default; `"registry"` selects the IANA registry identifier.
|
|
1249
1348
|
* @opts pem Return a PEM string instead of a DER Buffer.
|
|
1250
1349
|
* @example
|
|
1251
1350
|
* var pair = await pki.key.generate("Ed25519");
|
|
@@ -1341,6 +1440,18 @@ var encrypt = cmsEncrypt.encrypt;
|
|
|
1341
1440
|
* @opts keyIdentifier `"issuerAndSerial"` (default) or `"subjectKeyIdentifier"`.
|
|
1342
1441
|
* @opts oaepHash The RSAES-OAEP hash for ktri recipients wrapping the MAC key: `"sha256"` (default) / `"sha384"` / `"sha512"`.
|
|
1343
1442
|
* @opts ukm User keying material (a Buffer) for kari / kemri recipients.
|
|
1443
|
+
* @opts algorithmProtection Emit the RFC 6211 `CMSAlgorithmProtection` authenticated attribute, a
|
|
1444
|
+
* copy of the digest and MAC algorithm identifiers this structure names. It matters more here
|
|
1445
|
+
* than on a signature: RFC 5652 sec. 9.2 makes the MAC input the DER encoding of `authAttrs`
|
|
1446
|
+
* alone, so `digestAlgorithm` and `macAlgorithm` are covered by nothing, and rewriting the
|
|
1447
|
+
* digest algorithm leaves the MAC verifying while the recipient recomputes the content digest
|
|
1448
|
+
* under the attacker's choice, no key required. `true` emits the identifier RFC 6211 sec. 2
|
|
1449
|
+
* states, `"registry"` the IANA registry value. Off by default, because it changes the bytes
|
|
1450
|
+
* of every message. `false`, `null` and an absent option emit none, and any other value is
|
|
1451
|
+
* refused `cms/bad-input`. With `authenticatedAttributes: false` the MAC covers the content
|
|
1452
|
+
* octets directly and no attribute set is built, so asking for the attribute there is refused
|
|
1453
|
+
* while the off forms are still accepted. `pki.cms.decrypt` reads both identifiers and
|
|
1454
|
+
* compares the copies it finds.
|
|
1344
1455
|
* @opts pem Return a PEM string (`-----BEGIN CMS-----`) instead of a DER Buffer.
|
|
1345
1456
|
* @example
|
|
1346
1457
|
* // authenticate a message to an RSA recipient, then verify the MAC via decrypt
|
|
@@ -1403,6 +1514,19 @@ var authenticate = cmsEncrypt.authenticate;
|
|
|
1403
1514
|
* @opts maxIterations Lower the PBKDF2 iteration cap (a DoS bound; downward only).
|
|
1404
1515
|
* @opts stream Return `content` as an async iterable of plaintext `Buffer` chunks instead of a single `Buffer`.
|
|
1405
1516
|
* @opts allowUnauthenticatedRsa15 Read a message that uses RSAES-PKCS1-v1_5 key transport over a content with no integrity tag, such as AES-CBC. Refused by default with `cms/unauthenticated-rsa-v15`: the content padding is that combination's only integrity check, so decrypting it either returns the plaintext of a substituted key or tells an attacker submitting chosen ciphertexts that theirs decoded (RFC 8017 sec. 7.2). `openssl cms -encrypt` emits exactly this combination when no algorithm is named, so reading one of its messages needs this option. An AEAD content or an RSAES-OAEP recipient needs none.
|
|
1517
|
+
* @opts requireAlgorithmProtection For an AuthenticatedData, refuse one that carries no RFC 6211
|
|
1518
|
+
* `CMSAlgorithmProtection` authenticated attribute, with `cms/missing-algorithm-protection`.
|
|
1519
|
+
* Off by default, since RFC 8933 sec. 4.1 makes including it a SHOULD. A message that DOES
|
|
1520
|
+
* carry one is always held to it, with no option: its `digestAlgorithm` and `macAlgorithm`
|
|
1521
|
+
* copies are compared against the fields of the `AuthenticatedData` itself, and a disagreement
|
|
1522
|
+
* is `cms/algorithm-protection-mismatch`. That comparison runs before the MAC, because a
|
|
1523
|
+
* mismatch is a statement about the message rather than about the key. The result's
|
|
1524
|
+
* `algorithmProtection` field reports what was compared. The option is the boolean it
|
|
1525
|
+
* documents: `false`, `null` and an absent option are the off forms, and any other value is
|
|
1526
|
+
* refused `cms/bad-input`. Asking for it while decrypting any other content type is refused
|
|
1527
|
+
* the same way: RFC 8933 sec. 6 gives the attribute a place in an `AuthenticatedData` alone,
|
|
1528
|
+
* so an `EnvelopedData`, `AuthEnvelopedData` or `EncryptedData` cannot satisfy the
|
|
1529
|
+
* requirement and returning its plaintext would answer neither question.
|
|
1406
1530
|
* @example
|
|
1407
1531
|
* var rsa = { name: "RSA-OAEP", modulusLength: 2048, publicExponent: new Uint8Array([1, 0, 1]), hash: "SHA-256" };
|
|
1408
1532
|
* var pair = await pki.key.generate(rsa);
|
package/lib/ct.js
CHANGED
|
@@ -2177,7 +2177,7 @@ function _b64Array(obj, name, code, label, exactLen, maxCount) {
|
|
|
2177
2177
|
* @opts headers OPTIONAL extra request headers (the request-framing headers are stripped; the verb owns the GET method).
|
|
2178
2178
|
* @opts timeout OPTIONAL ms budget, default 30s (cap-validated).
|
|
2179
2179
|
* @opts maxResponseBytes OPTIONAL per-GET size cap, default 4 MiB, tightenable DOWNWARD only.
|
|
2180
|
-
* @opts requireJsonContentType OPTIONAL boolean (default false) -- opt in to a strict `ct/bad-content-type` gate on the JSON GET.
|
|
2180
|
+
* @opts requireJsonContentType OPTIONAL boolean (default false) -- opt in to a strict `ct/bad-content-type` gate on the JSON GET. A value other than `true`, `false`, `null` or an absent option is refused `ct/bad-input` before the request goes out.
|
|
2181
2181
|
* @example
|
|
2182
2182
|
* // a live distributor uses the default pki.transport.https; here an injected transport returns the pair
|
|
2183
2183
|
* var r = await pki.ct.fetchLogList({ url: "https://ct.example/log_list.json", signerKey: googleSignerSpki,
|
|
@@ -2241,7 +2241,8 @@ async function fetchLogList(opts) {
|
|
|
2241
2241
|
var req = { headers: headers, tls: fetchTls, timeout: timeout, maxResponseBytes: maxResponseBytes };
|
|
2242
2242
|
/** @internal Read before the request goes out, so the option that decides the check is the one the call
|
|
2243
2243
|
* was made with rather than whatever it says once the response is back. */
|
|
2244
|
-
var requireJsonContentType = opts.requireJsonContentType
|
|
2244
|
+
var requireJsonContentType = guard.identifier.booleanOption(opts.requireJsonContentType, _ctErr,
|
|
2245
|
+
"ct/bad-input", "fetchLogList opts.requireJsonContentType");
|
|
2245
2246
|
var jsonRes = await _fetchBody(transport, jsonUrl, req, "CT log-list JSON");
|
|
2246
2247
|
if (requireJsonContentType && jsonRes.contentType !== "application/json") throw _ctErr("ct/bad-content-type", "the CT log-list JSON GET returned content-type " + JSON.stringify(jsonRes.contentType || null) + " (opts.requireJsonContentType is set)");
|
|
2247
2248
|
var sigRes = await _fetchBody(transport, sigUrl, req, "CT log-list signature");
|
package/lib/guard-der.js
CHANGED
|
@@ -96,6 +96,31 @@ function paramsAbsentOrNull(params) {
|
|
|
96
96
|
return params === null || params === undefined || isNull(params);
|
|
97
97
|
}
|
|
98
98
|
|
|
99
|
+
/** Whether two decoded `AlgorithmIdentifier` records name the same algorithm with the same
|
|
100
|
+
* parameters, comparing the parameters AS THEY ARE: absent equals absent, and an explicit DER NULL
|
|
101
|
+
* is a different identifier from one with no parameters field.
|
|
102
|
+
*
|
|
103
|
+
* This is the comparison for an identifier whose parameters carry meaning. A digest identifier has
|
|
104
|
+
* the opposite rule, because RFC 3370 clause 2.1 and RFC 5754 clause 2 require an implementation to
|
|
105
|
+
* accept both spellings of the same digest, and `schema-cms.sameDigestAlgorithm` grants that. A
|
|
106
|
+
* signature identifier does not: RFC 4055 requires the `*WithRSAEncryption` identifiers to carry a
|
|
107
|
+
* NULL, RFC 5758 and RFC 8410 require the `ecdsaWith*` and the Edwards and post-quantum ones to omit
|
|
108
|
+
* the field, and RSASSA-PSS carries its hash, mask generator, salt length and trailer field there
|
|
109
|
+
* under RFC 4056 clause 2.2. Collapsing the two spellings for one of those is admitting a different
|
|
110
|
+
* algorithm, which is why the grant does not travel.
|
|
111
|
+
*
|
|
112
|
+
* @enforced-by behavioral -- an equality that is too permissive has no rename-proof shape; the RED
|
|
113
|
+
* vector is an identifier pair differing only in an explicit NULL, which must compare unequal here
|
|
114
|
+
* and equal through the digest helper.
|
|
115
|
+
*/
|
|
116
|
+
function sameAlgorithmExactly(a, c) {
|
|
117
|
+
if (!a || !c || a.oid !== c.oid) return false;
|
|
118
|
+
var ap = a.parameters === undefined ? null : a.parameters;
|
|
119
|
+
var cp = c.parameters === undefined ? null : c.parameters;
|
|
120
|
+
if (ap === null || cp === null) return ap === cp;
|
|
121
|
+
return _isBuffer(ap) && _isBuffer(cp) && _sizeOf(ap) === _sizeOf(cp) && _compare(ap, cp) === 0;
|
|
122
|
+
}
|
|
123
|
+
|
|
99
124
|
// @enforced-by behavioral -- see paramsAbsentOrNull.
|
|
100
125
|
function assertParams(params, shape, E, code, label) {
|
|
101
126
|
var ok = shape === "null" ? isNull(params) : (shape === "absent"
|
|
@@ -109,4 +134,5 @@ function assertParams(params, shape, E, code, label) {
|
|
|
109
134
|
module.exports = _freeze({
|
|
110
135
|
element: element, tlv: tlv,
|
|
111
136
|
isNull: isNull, paramsAbsentOrNull: paramsAbsentOrNull, assertParams: assertParams,
|
|
137
|
+
sameAlgorithmExactly: sameAlgorithmExactly,
|
|
112
138
|
});
|
package/lib/guard-identifier.js
CHANGED
|
@@ -600,6 +600,32 @@ function assertCallableOption(opts, key, E, code, label, shape) {
|
|
|
600
600
|
return assertCallable(opts[key], E, code, label, shape);
|
|
601
601
|
}
|
|
602
602
|
|
|
603
|
+
/** @internal How a switch is read: as the boolean it documents, with an absent value standing for
|
|
604
|
+
* off and every other value refused. A switch that turns a requirement on is the case that decides
|
|
605
|
+
* the shape. `requireRevocation: "true"` out of a config file is truthy to JavaScript and unequal
|
|
606
|
+
* to `true`, so a boundary testing `=== true` runs without the requirement and says nothing, while
|
|
607
|
+
* one testing truthiness turns the requirement on for `0` and `""` as well. The value is rendered
|
|
608
|
+
* from its own type rather than coerced, so a caller value with a throwing `toString` reports the
|
|
609
|
+
* typed refusal. A boundary that reads `null` as a value its caller supplied keeps its own check:
|
|
610
|
+
* this one reads it as absence. */
|
|
611
|
+
// @enforced-by behavioral -- reading a caller switch has no rename-proof code shape; `=== true` is
|
|
612
|
+
// @enforced-by behavioral -- the correct read for a switch that turns a control OFF, so a lexical
|
|
613
|
+
// @enforced-by behavioral -- rule would flag the safe direction. The per-verb vectors are the
|
|
614
|
+
// @enforced-by behavioral -- guard: each drives the shipped verb with a non-boolean and asserts the
|
|
615
|
+
// @enforced-by behavioral -- typed refusal, and with false and absence to pin the two off forms.
|
|
616
|
+
function booleanOption(value, E, code, label) {
|
|
617
|
+
if (value === undefined || value === null) return false;
|
|
618
|
+
if (value === true || value === false) return value;
|
|
619
|
+
throw E(code, label + " must be true or false, got " + _showSwitch(value));
|
|
620
|
+
}
|
|
621
|
+
|
|
622
|
+
/** @internal The value's own type, for the message above. Nothing here calls into the value. */
|
|
623
|
+
function _showSwitch(value) {
|
|
624
|
+
if (typeof value === "string") return _stringify(value);
|
|
625
|
+
if (typeof value === "number" || typeof value === "bigint") return _String(value);
|
|
626
|
+
return typeof value;
|
|
627
|
+
}
|
|
628
|
+
|
|
603
629
|
/** @internal Whether a caller's object says something about itself, as opposed to inheriting the
|
|
604
630
|
* answer. A declaration is a promise the author of that object made, so it has to sit on the object
|
|
605
631
|
* they wrote: reading the flag through the object finds a value on `Function.prototype` that every
|
|
@@ -619,6 +645,7 @@ function declaresOwn(obj, name) {
|
|
|
619
645
|
|
|
620
646
|
module.exports = intrinsic.freeze({
|
|
621
647
|
assertCallable: assertCallable,
|
|
648
|
+
booleanOption: booleanOption,
|
|
622
649
|
declaresOwn: declaresOwn,
|
|
623
650
|
assertCallableOption: assertCallableOption,
|
|
624
651
|
assertCanonicalOid: assertCanonicalOid,
|
package/lib/lint.js
CHANGED
|
@@ -4929,12 +4929,21 @@ var ALL_CRL_RULES = RFC5280_CRL_RULES.concat(CNSA2_CRL_RULES);
|
|
|
4929
4929
|
* for YY >= 50 and 20YY otherwise, so every UTCTime names a year inside 1950..2049 and only the
|
|
4930
4930
|
* GeneralizedTime direction of that clause can be broken. */
|
|
4931
4931
|
var OID_SIGNING_TIME = oid.byName("signingTime");
|
|
4932
|
+
/** @internal The two identifiers RFC 6211's attribute is assigned, both read: the body's PKCS 9 leaf
|
|
4933
|
+
* and the S/MIME attribute arc value the IANA registry and the update draft give it. */
|
|
4934
|
+
var OID_ALG_PROTECTION = oid.byName("cmsAlgorithmProtection");
|
|
4935
|
+
var OID_ALG_PROTECTION_AA = oid.byName("cmsAlgorithmProtect");
|
|
4932
4936
|
var OID_SIGNED_DATA = oid.byName("signedData");
|
|
4933
4937
|
|
|
4934
4938
|
/** @internal The content type decides which of these rows apply, and the parser sets `contentType`
|
|
4935
4939
|
* as an own property on every content it builds. A content type that carries no signers has no own
|
|
4936
4940
|
* `signerInfos`, so testing for that field answers from whatever the prototype chain holds. */
|
|
4937
4941
|
function _isSignedData(cms) { return cms.contentType === OID_SIGNED_DATA; }
|
|
4942
|
+
var OID_AUTH_DATA = oid.byName("authData");
|
|
4943
|
+
/** @internal The other content type whose originator carries attributes, and the one whose algorithm
|
|
4944
|
+
* fields nothing else covers: RFC 5652 sec. 9.2 makes the MAC input the DER encoding of `authAttrs`
|
|
4945
|
+
* alone, so `digestAlgorithm` and `macAlgorithm` sit outside it. */
|
|
4946
|
+
function _isAuthData(cms) { return cms.contentType === OID_AUTH_DATA; }
|
|
4938
4947
|
var OID_COUNTERSIGNATURE = oid.byName("countersignature");
|
|
4939
4948
|
/** @internal Every SignerInfo a message carries: its own signers, and the countersignatures their
|
|
4940
4949
|
* unsigned attributes hold, which sec. 11.4 defines as SignerInfo values and which carry their own
|
|
@@ -4969,14 +4978,16 @@ function _cmsAllSignerInfos(cms) {
|
|
|
4969
4978
|
visit(cms.signerInfos, 0, false, 0);
|
|
4970
4979
|
return out;
|
|
4971
4980
|
}
|
|
4972
|
-
function
|
|
4973
|
-
var attrs = signerInfo.signedAttrs;
|
|
4981
|
+
function _cmsAttrInList(attrs, dotted) {
|
|
4974
4982
|
if (!Array.isArray(attrs)) return null;
|
|
4975
4983
|
for (var i = 0; i < attrs.length; i++) {
|
|
4976
4984
|
if (attrs[i].type === dotted) return attrs[i];
|
|
4977
4985
|
}
|
|
4978
4986
|
return null;
|
|
4979
4987
|
}
|
|
4988
|
+
function _cmsSignedAttr(signerInfo, dotted) {
|
|
4989
|
+
return _cmsAttrInList(signerInfo.signedAttrs, dotted);
|
|
4990
|
+
}
|
|
4980
4991
|
/** @internal Whether two DIGEST AlgorithmIdentifiers name one algorithm, under RFC 5754 sec. 2. The
|
|
4981
4992
|
* rule lives in the schema module because the set this row reads is also WRITTEN there, by the verb
|
|
4982
4993
|
* that adds a countersigner's digest to it, and a set written under one rule and read under another
|
|
@@ -5078,8 +5089,50 @@ var RFC5652_CMS_RULES = [
|
|
|
5078
5089
|
},
|
|
5079
5090
|
];
|
|
5080
5091
|
|
|
5081
|
-
|
|
5082
|
-
|
|
5092
|
+
/** @internal RFC 8933 updates RFC 5652 rather than restating it, so its rows are their own profile:
|
|
5093
|
+
* `pki.lint.cms(der, { profile: "rfc8933" })` asks only what that document added. */
|
|
5094
|
+
var RFC8933_CMS_RULES = [
|
|
5095
|
+
{
|
|
5096
|
+
id: "lint/rfc8933/algorithm-protection-absent", severity: "notice", source: "rfc8933", citation: "RFC 8933 4.1",
|
|
5097
|
+
/** @internal "the originator of a signed-data content type that includes signed attributes
|
|
5098
|
+
* SHOULD include the CMSAlgorithmProtection attribute [RFC6211] as one of the signed
|
|
5099
|
+
* attributes. Likewise, the originator of an authenticated-data content type that includes
|
|
5100
|
+
* authenticated attributes SHOULD include the CMSAlgorithmProtection attribute [RFC6211] as one
|
|
5101
|
+
* of the authenticated attributes." RFC 9882 sec. 4 and RFC 9814 sec. 5 repeat the signed-data
|
|
5102
|
+
* half for ML-DSA and SLH-DSA. Both content types are read, and the authenticated-data one is
|
|
5103
|
+
* where the exposure is widest: sec. 9.2 makes the MAC input `authAttrs` alone, so the two outer
|
|
5104
|
+
* algorithm fields are covered by nothing. The SHOULD is conditioned on attributes being
|
|
5105
|
+
* present, so an originator that signs or authenticates the content directly has nowhere to
|
|
5106
|
+
* carry it and draws no finding. Both identifiers count, since the two spell one attribute. A
|
|
5107
|
+
* notice rather than a warning: it is a SHOULD, and most CMS in the field carries no such
|
|
5108
|
+
* attribute. */
|
|
5109
|
+
message: "an originator with signed or authenticated attributes carries no CMSAlgorithmProtection attribute, which this section says it should include",
|
|
5110
|
+
appliesTo: function (cms) { return _isSignedData(cms) || _isAuthData(cms); },
|
|
5111
|
+
check: function (cms) {
|
|
5112
|
+
var out = [];
|
|
5113
|
+
if (_isAuthData(cms)) {
|
|
5114
|
+
var authAttrs = cms.authAttrs;
|
|
5115
|
+
if (!Array.isArray(authAttrs) || !authAttrs.length) return null;
|
|
5116
|
+
if (_cmsAttrInList(authAttrs, OID_ALG_PROTECTION) ||
|
|
5117
|
+
_cmsAttrInList(authAttrs, OID_ALG_PROTECTION_AA)) return null;
|
|
5118
|
+
return { context: { placement: "authAttrs" } };
|
|
5119
|
+
}
|
|
5120
|
+
_cmsAllSignerInfos(cms).forEach(function (entry) {
|
|
5121
|
+
if (!Array.isArray(entry.signerInfo.signedAttrs) || !entry.signerInfo.signedAttrs.length) return;
|
|
5122
|
+
if (_cmsSignedAttr(entry.signerInfo, OID_ALG_PROTECTION) ||
|
|
5123
|
+
_cmsSignedAttr(entry.signerInfo, OID_ALG_PROTECTION_AA)) return;
|
|
5124
|
+
out.push({ context: { signerIndex: entry.signerIndex, countersignature: entry.countersignature } });
|
|
5125
|
+
});
|
|
5126
|
+
return out.length ? out : null;
|
|
5127
|
+
},
|
|
5128
|
+
},
|
|
5129
|
+
];
|
|
5130
|
+
|
|
5131
|
+
var CMS_PROFILES = Object.assign(Object.create(null), {
|
|
5132
|
+
"rfc5652": RFC5652_CMS_RULES,
|
|
5133
|
+
"rfc8933": RFC8933_CMS_RULES,
|
|
5134
|
+
});
|
|
5135
|
+
var ALL_CMS_RULES = RFC5652_CMS_RULES.concat(RFC8933_CMS_RULES);
|
|
5083
5136
|
|
|
5084
5137
|
/** @internal The RFC 3161 profile is the shortest of the three, for the reason the CMS one is
|
|
5085
5138
|
* short: `schema.tsp.parseToken` refuses nearly everything sec. 2.4.2 requires of a token before a
|
|
@@ -6978,9 +7031,10 @@ function _ingestCrl(input) {
|
|
|
6978
7031
|
* `lint/rfc2986/extension-request-ambiguous`; every one of them is read, so nothing a later
|
|
6979
7032
|
* attribute asks for escapes the rows below.
|
|
6980
7033
|
*
|
|
6981
|
-
* @opts profile `"rfc2986"` for the structural rows alone,
|
|
6982
|
-
*
|
|
6983
|
-
*
|
|
7034
|
+
* @opts profile `"rfc2986"` for the structural rows alone, `"cabf-tls"` to add the TLS rows, or
|
|
7035
|
+
* `"rfc9883"` for the rows about a request carrying a possession statement
|
|
7036
|
+
* (default runs the structural rows and the rfc9883 rows). A certificate, CRL
|
|
7037
|
+
* or OCSP profile name is refused rather than run against a request.
|
|
6984
7038
|
* @opts severity Suppress findings below this floor (default `"notice"`). `counts` and `worst`
|
|
6985
7039
|
* always reflect the complete, unfiltered result.
|
|
6986
7040
|
* @example
|
|
@@ -7217,8 +7271,9 @@ function _ingestCms(input) {
|
|
|
7217
7271
|
* `pki.cms.verify`'s, and the certificates a message carries are linted with
|
|
7218
7272
|
* `pki.lint.certificate`.
|
|
7219
7273
|
*
|
|
7220
|
-
* @opts profile `"rfc5652"`,
|
|
7221
|
-
* profile name is refused rather than run against a
|
|
7274
|
+
* @opts profile `"rfc5652"`, or `"rfc8933"` for the rows that update it (default runs both).
|
|
7275
|
+
* A certificate, CRL or OCSP profile name is refused rather than run against a
|
|
7276
|
+
* message.
|
|
7222
7277
|
* @opts severity Suppress findings below this floor (default `"notice"`). `counts` and `worst`
|
|
7223
7278
|
* always reflect the complete, unfiltered result.
|
|
7224
7279
|
* @example
|
|
@@ -7517,10 +7572,11 @@ function rules(profile, artifact) {
|
|
|
7517
7572
|
* and `"default"`, are not among them.
|
|
7518
7573
|
* @example
|
|
7519
7574
|
* pki.lint.profiles();
|
|
7520
|
-
* // -> ["rfc5280", "rfc9881", "rfc9909", "rfc9935", "
|
|
7521
|
-
* // "
|
|
7522
|
-
* // "nist-pqc-transition", "cnsa-2.0", "rfc2986",
|
|
7523
|
-
* // "rfc5652", "
|
|
7575
|
+
* // -> ["rfc5280", "rfc9881", "rfc9909", "rfc9935", "rfc9763", "rfc7633", "x509-altsig",
|
|
7576
|
+
* // "cabf-tls", "cabf-smime", "cabf-cs", "etsi-qc", "mozilla-root", "chrome-root",
|
|
7577
|
+
* // "microsoft-root", "apple-root", "nist-pqc-transition", "cnsa-2.0", "rfc2986",
|
|
7578
|
+
* // "rfc9883", "rfc5280-crl", "rfc6960", "rfc5019", "rfc5652", "rfc8933", "rfc3161",
|
|
7579
|
+
* // "rfc5755"]
|
|
7524
7580
|
*/
|
|
7525
7581
|
/** @internal `cabf-tls` names the certificate profile in this list and is not repeated for the
|
|
7526
7582
|
* request form: `pki.lint.csr` takes the same name and runs those rows against what a request
|
package/lib/oid.js
CHANGED
|
@@ -232,7 +232,14 @@ var FAMILIES = Object.assign(Object.create(null), {
|
|
|
232
232
|
pkcs9: { base: [1, 2, 840, 113549, 1, 9], of: {
|
|
233
233
|
emailAddress: 1, contentType: 3, messageDigest: 4, signingTime: 5,
|
|
234
234
|
countersignature: 6, challengePassword: 7, extensionRequest: 14,
|
|
235
|
-
smimeCapabilities: 15, friendlyName: 20, localKeyId: 21
|
|
235
|
+
smimeCapabilities: 15, friendlyName: 20, localKeyId: 21,
|
|
236
|
+
/** @internal RFC 6211 sec. 2 and Appendix A both state the CMS algorithm protection attribute
|
|
237
|
+
* here, directly under PKCS 9, and that is the value OpenSSL and Bouncy Castle register. The
|
|
238
|
+
* same document's sec. 4 says "All identifiers are assigned out of the S/MIME OID arc", which
|
|
239
|
+
* the IANA registry, errata 9144 and 9145, and draft-ietf-lamps-rfc6211-update-03 read as
|
|
240
|
+
* `1.2.840.113549.1.9.16.2.52`, registered beside it as `cmsAlgorithmProtect`. Both are
|
|
241
|
+
* accepted on parse, because a mismatched identifier is equivalent to no protection. */
|
|
242
|
+
cmsAlgorithmProtection: 52 } },
|
|
236
243
|
|
|
237
244
|
keyPackageContentTypes: { base: [2, 16, 840, 1, 101, 2, 1, 2, 78], of: { aKeyPackage: 5 } },
|
|
238
245
|
|
|
@@ -324,6 +331,9 @@ var FAMILIES = Object.assign(Object.create(null), {
|
|
|
324
331
|
cmcUnsignedData: 34,
|
|
325
332
|
binarySigningTime: 46,
|
|
326
333
|
asymmDecryptKeyID: 54, relatedCertRequest: 60,
|
|
334
|
+
/** @internal The second value the CMS algorithm protection attribute is identified by. See
|
|
335
|
+
* `pkcs9.cmsAlgorithmProtection` for which document states which, and why both are read. */
|
|
336
|
+
cmsAlgorithmProtect: 52,
|
|
327
337
|
certificationRequestInfoTemplate: 61, extensionReqTemplate: 62 } },
|
|
328
338
|
|
|
329
339
|
ansiX962: { base: [1, 2, 840, 10045], of: {
|