@blamejs/pki 0.8.51 → 0.8.52
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 +22 -0
- package/README.md +1 -1
- package/index.js +2 -1
- package/lib/cms-sign.js +254 -0
- package/lib/cms-verify.js +456 -8
- package/lib/constants.js +15 -0
- package/lib/guard-time.js +98 -0
- package/lib/schema-all.js +1 -1
- package/lib/schema-smime.js +215 -5
- package/lib/tsp-sign.js +58 -95
- package/package.json +1 -1
- package/sbom.cdx.json +6 -6
package/lib/cms-verify.js
CHANGED
|
@@ -31,6 +31,7 @@ var asn1 = require("./asn1-der");
|
|
|
31
31
|
var oid = require("./oid");
|
|
32
32
|
var x509 = require("./schema-x509");
|
|
33
33
|
var cms = require("./schema-cms");
|
|
34
|
+
var smime = require("./schema-smime");
|
|
34
35
|
var webcrypto = require("./webcrypto");
|
|
35
36
|
var subtle = webcrypto.webcrypto.subtle;
|
|
36
37
|
var edwardsPoint = require("./edwards-point");
|
|
@@ -46,6 +47,7 @@ var validator = require("./validator-all");
|
|
|
46
47
|
var compositeSig = require("./composite-sig");
|
|
47
48
|
var guard = require("./guard-all");
|
|
48
49
|
var intrinsic = require("./guard-intrinsic");
|
|
50
|
+
var constants = require("./constants");
|
|
49
51
|
var frameworkError = require("./framework-error");
|
|
50
52
|
var pkix = require("./schema-pkix");
|
|
51
53
|
|
|
@@ -69,6 +71,15 @@ function _row(o) { return guard.verdict.of(o); }
|
|
|
69
71
|
var OID_MESSAGE_DIGEST = oid.byName("messageDigest");
|
|
70
72
|
var OID_CONTENT_TYPE = oid.byName("contentType");
|
|
71
73
|
var OID_COUNTERSIGNATURE = oid.byName("countersignature");
|
|
74
|
+
/** @internal `id-aa-signatureTimeStampToken`, which the registry already carries under the S/MIME
|
|
75
|
+
* authenticated-attribute arc as `timeStampToken`: `oid.byName("timeStampToken")` is
|
|
76
|
+
* `1.2.840.113549.1.9.16.2.14`, the OID RFC 5126 clause 6.1.1 assigns. A second name for one OID
|
|
77
|
+
* would give `oid.name()` two answers, so the existing name is used rather than a CAdES alias. */
|
|
78
|
+
var OID_TIMESTAMP_TOKEN = oid.byName("timeStampToken");
|
|
79
|
+
var OID_ID_DATA = oid.byName("data");
|
|
80
|
+
var OID_SIGNING_CERTIFICATE = oid.byName("signingCertificate");
|
|
81
|
+
var OID_SIGNING_CERTIFICATE_V2 = oid.byName("signingCertificateV2");
|
|
82
|
+
var OID_SIGNING_TIME = oid.byName("signingTime");
|
|
72
83
|
|
|
73
84
|
var DIGEST_HASH = intrinsic.assign(intrinsic.create(null), {
|
|
74
85
|
sha1: "SHA-1", sha256: "SHA-256", sha384: "SHA-384", sha512: "SHA-512",
|
|
@@ -83,9 +94,16 @@ function _digestOidTable(named) {
|
|
|
83
94
|
});
|
|
84
95
|
return t;
|
|
85
96
|
}
|
|
86
|
-
|
|
97
|
+
/** @internal Both tables read the one membership list in `C.NAMES.WEAK_DIGESTS`, keyed the two ways
|
|
98
|
+
* the two checks ask the question: by the digest OID a SignerInfo declares, and by the WebCrypto
|
|
99
|
+
* hash name a signature algorithm resolves to. A digest added to that list reaches both. */
|
|
100
|
+
var _WEAK_DIGEST_NAMES = intrinsic.map(intrinsic.keys(constants.NAMES.WEAK_DIGESTS), function (n) {
|
|
101
|
+
return [n, constants.NAMES.WEAK_DIGESTS[n]];
|
|
102
|
+
});
|
|
103
|
+
var WEAK_DIGEST_OIDS = _digestOidTable(_WEAK_DIGEST_NAMES);
|
|
87
104
|
var UNVERIFIABLE_DIGEST_OIDS = _digestOidTable([["md5", "MD5"], ["md4", "MD4"], ["md2", "MD2"]]);
|
|
88
|
-
var WEAK_SIGNATURE_HASHES = intrinsic.
|
|
105
|
+
var WEAK_SIGNATURE_HASHES = intrinsic.create(null);
|
|
106
|
+
intrinsic.forEach(_WEAK_DIGEST_NAMES, function (pair) { WEAK_SIGNATURE_HASHES[pair[1]] = 1; });
|
|
89
107
|
function _supportedDigest(name) { return intrinsic.hasOwn(DIGEST_HASH, name) && !!DIGEST_HASH[name]; }
|
|
90
108
|
async function _computeDigest(name, content) {
|
|
91
109
|
return intrinsic.bufferFrom(await subtle.digest(DIGEST_HASH[name], content));
|
|
@@ -155,6 +173,35 @@ async function _verifyAgainstCandidates(scheme, sigHash, sigBytes, signedBytes,
|
|
|
155
173
|
: { ok: false, sid: sid, cert: candidates[0].der });
|
|
156
174
|
}
|
|
157
175
|
|
|
176
|
+
/** @internal Where a signer identifier matches several certificates, the signing-certificate
|
|
177
|
+
* attribute is what says which of them made the signature, so one it names is tried first. A
|
|
178
|
+
* subjectKeyIdentifier names a KEY: two certificates holding that key verify the same signature
|
|
179
|
+
* equally, and without this the one reported as `signers[i].cert` would be whichever candidate came
|
|
180
|
+
* first, taking with it every decision keyed to that certificate, its validity window, its key
|
|
181
|
+
* usage, and the chain built from it. RFC 5035 clause 5.4 makes the first identifier "the
|
|
182
|
+
* certificate used to verify the signature", which is exactly this question.
|
|
183
|
+
*
|
|
184
|
+
* Order is all that changes: a candidate the attribute names still has to verify the signature, and
|
|
185
|
+
* where none is named or none matches, the list is returned as it came. */
|
|
186
|
+
function _preferBoundCandidate(si, candidates) {
|
|
187
|
+
if (candidates.length < 2) return candidates;
|
|
188
|
+
/** @internal The attribute is read ONCE and each candidate compared against what it read. Reading
|
|
189
|
+
* it per candidate would multiply its size by the candidate count, and both are attacker-chosen:
|
|
190
|
+
* a message can embed many certificates carrying one subject key identifier and a long
|
|
191
|
+
* certificate list inside the attribute, so the work would be their product, spent before the
|
|
192
|
+
* signature is checked. */
|
|
193
|
+
var claims = smime.certBindingClaims(si.signedAttrs || []);
|
|
194
|
+
if (!claims.present) return candidates;
|
|
195
|
+
var bound = [];
|
|
196
|
+
var rest = [];
|
|
197
|
+
intrinsic.forEach(candidates, function (c) {
|
|
198
|
+
var entry = smime.certBindingAgainst(claims.chosen, c.der);
|
|
199
|
+
var names = entry.matches === true && entry.issuerSerialMatches !== false;
|
|
200
|
+
guard.list.append(names ? bound : rest, c);
|
|
201
|
+
});
|
|
202
|
+
return bound.length ? intrinsic.concat(bound, rest) : candidates;
|
|
203
|
+
}
|
|
204
|
+
|
|
158
205
|
function _certSki(cert) {
|
|
159
206
|
var ext = (cert.extensions || []).filter(function (e) { return e.oid === oid.byName("subjectKeyIdentifier"); })[0];
|
|
160
207
|
if (!ext) return null;
|
|
@@ -295,7 +342,7 @@ async function _verifyOne(si, content, eContentType, parsedCerts, csTarget, dige
|
|
|
295
342
|
if ((scheme.kind !== "eddsa" && scheme.kind !== "mldsa" && scheme.kind !== "slhdsa" && !sigHash) || (si.signedAttrsBytes && !_supportedDigest(si.digestAlgorithm.name))) return _pResolve({ ok: false, code: "cms/unsupported-algorithm", sid: si.sid, message: "unsupported digest algorithm " + JSON.stringify(si.digestAlgorithm.name) });
|
|
296
343
|
if (scheme.kind === "mldsa" && si.signedAttrsBytes && !_mldsaDigestSuitable(scheme.name, si.digestAlgorithm.name)) return _pResolve({ ok: false, code: "cms/unsupported-algorithm", sid: si.sid, message: "the " + si.digestAlgorithm.name + " message digest is below the security strength of " + scheme.name + " (RFC 9882 sec. 3.3)" });
|
|
297
344
|
if (scheme.kind === "slhdsa" && si.signedAttrsBytes && si.digestAlgorithm.name !== scheme.digest) return _pResolve({ ok: false, code: "cms/unsupported-algorithm", sid: si.sid, message: "SLH-DSA " + scheme.name + " requires the " + scheme.digest + " message digest (RFC 9814 sec. 4)" });
|
|
298
|
-
var signers = _findSignerCerts(si.sid, parsedCerts);
|
|
345
|
+
var signers = _preferBoundCandidate(si, _findSignerCerts(si.sid, parsedCerts));
|
|
299
346
|
if (!signers.length) return _pResolve({ ok: false, code: "cms/signer-cert-not-found", sid: si.sid, message: "no certificate matches this SignerInfo's signer identifier" });
|
|
300
347
|
var sigBytes = _toBuf(si.signature, "the SignerInfo signature");
|
|
301
348
|
|
|
@@ -381,7 +428,7 @@ async function _verifyComposite(si, comp, content, eContentType, parsedCerts, cs
|
|
|
381
428
|
if (si.digestAlgorithm.name !== comp.phCms) {
|
|
382
429
|
return _pResolve({ ok: false, code: "cms/unsupported-algorithm", sid: si.sid, message: "the SignerInfo digestAlgorithm " + JSON.stringify(si.digestAlgorithm.name) + " is not the composite " + comp.name + " pre-hash " + JSON.stringify(comp.phCms) + " (draft-ietf-lamps-cms-composite-sigs sec. 3.4)" });
|
|
383
430
|
}
|
|
384
|
-
var signers = _findSignerCerts(si.sid, parsedCerts);
|
|
431
|
+
var signers = _preferBoundCandidate(si, _findSignerCerts(si.sid, parsedCerts));
|
|
385
432
|
if (!signers.length) return _pResolve({ ok: false, code: "cms/signer-cert-not-found", sid: si.sid, message: "no certificate matches this SignerInfo's signer identifier" });
|
|
386
433
|
var sigBytes = _toBuf(si.signature, "the SignerInfo signature");
|
|
387
434
|
var signedBytes = await (csTarget != null ? _computeCountersigBytes(si, csTarget) : _computeSignedBytes(si, content, eContentType, digestByName));
|
|
@@ -417,6 +464,326 @@ function _surfaceSignedAttrs(si) {
|
|
|
417
464
|
});
|
|
418
465
|
}
|
|
419
466
|
|
|
467
|
+
/** @internal The CAdES baseline requirements this verb decides from one SignerInfo's own signed
|
|
468
|
+
* attributes, reported beside the signature verdict rather than folded into it: a signature can be
|
|
469
|
+
* cryptographically sound and still not be a CAdES signature, and the two answers are separate.
|
|
470
|
+
*
|
|
471
|
+
* `requirementsChecked` names the scope in the data, so a caller reads which requirements were
|
|
472
|
+
* evaluated instead of taking `conformant` for a full ETSI EN 319 122-1 clause 6.3 audit. Each
|
|
473
|
+
* finding carries the `level` of the requirement it is against, and `conformant` answers for the
|
|
474
|
+
* shall-level ones alone, so a SHOULD the document states as guidance is reported without being
|
|
475
|
+
* reported as non-conformance.
|
|
476
|
+
*
|
|
477
|
+
* - requirement f, shall: "The content-type attribute shall have value id-data".
|
|
478
|
+
* - clauses 5.2.2.2 and 5.2.2.3, shall: the signing certificate is protected by a
|
|
479
|
+
* `signing-certificate` or `signing-certificate-v2` attribute, and the identifier it carries
|
|
480
|
+
* names the certificate the signature resolved to. Clause 5.2.2.2 NOTE 2: "the binding
|
|
481
|
+
* information is the digest of the certificate".
|
|
482
|
+
* - requirement h, shall: "The ESS signing-certificate attribute shall be used if the SHA-1 hash
|
|
483
|
+
* algorithm is used". The two attributes are defined BY their hash algorithm, clause 5.2.2.2 as
|
|
484
|
+
* "a signing certificate attribute using the SHA-1 hash algorithm" and clause 5.2.2.3 as one
|
|
485
|
+
* "using a hash algorithm different from SHA-1", so what the requirement reads is the digest the
|
|
486
|
+
* identifier binds with and not the digest the signature was computed under. Its sibling
|
|
487
|
+
* requirement i, the v2 form "when another hash algorithm than SHA-1 is used", has no verifiable
|
|
488
|
+
* counterexample: a v1 ESSCertID carries no algorithm field, so a non-SHA-1 binding cannot be
|
|
489
|
+
* expressed in one, and it is not claimed as checked.
|
|
490
|
+
* - requirement g, should: "The issuerSerial field should not be included in the encoding of the
|
|
491
|
+
* ESSCertID, ESSCertIDv2 or OtherCertID type".
|
|
492
|
+
* - requirement j, should: "The generator should migrate to the use of ESS signing-certificate-v2
|
|
493
|
+
* in preference to ESS signing-certificate in line with the guidance regarding limited lifetime
|
|
494
|
+
* for the use of SHA-1", which is what a SHA-1 binding is reported against.
|
|
495
|
+
*/
|
|
496
|
+
var CADES_BASELINE_CHECKED = intrinsic.freeze([
|
|
497
|
+
"ETSI EN 319 122-1 clause 6.3 requirement f",
|
|
498
|
+
"ETSI EN 319 122-1 clauses 5.2.2.2 and 5.2.2.3",
|
|
499
|
+
"ETSI EN 319 122-1 clause 6.3 requirement g",
|
|
500
|
+
"ETSI EN 319 122-1 clause 6.3 requirement h",
|
|
501
|
+
"ETSI EN 319 122-1 clause 6.3 requirement j",
|
|
502
|
+
]);
|
|
503
|
+
|
|
504
|
+
function _cadesBaseline(si, signerCert) {
|
|
505
|
+
var attrs = si.signedAttrs || [];
|
|
506
|
+
var findings = [];
|
|
507
|
+
var contentType = null;
|
|
508
|
+
intrinsic.forEach(attrs, function (a) {
|
|
509
|
+
if (a.type !== OID_CONTENT_TYPE || contentType !== null || !a.values || a.values.length !== 1) return;
|
|
510
|
+
try { contentType = asn1.read.oid(asn1.decode(a.values[0])); }
|
|
511
|
+
// allow:swallow-unverified a content-type that does not read is reported as the unmet requirement below
|
|
512
|
+
catch (_e) { contentType = null; }
|
|
513
|
+
});
|
|
514
|
+
if (contentType !== OID_ID_DATA) {
|
|
515
|
+
guard.list.append(findings, {
|
|
516
|
+
code: "cms/cades-content-type", level: "shall",
|
|
517
|
+
message: "the content-type attribute shall have value id-data (ETSI EN 319 122-1 clause 6.3 " +
|
|
518
|
+
"requirement f), and this signature carries " +
|
|
519
|
+
(contentType === null ? "no readable content-type attribute" : (oid.name(contentType) || contentType)),
|
|
520
|
+
});
|
|
521
|
+
}
|
|
522
|
+
var v1 = null;
|
|
523
|
+
var v2 = null;
|
|
524
|
+
intrinsic.forEach(attrs, function (a) {
|
|
525
|
+
if (a.type === OID_SIGNING_CERTIFICATE && v1 === null && a.values && a.values.length === 1) v1 = a.values[0];
|
|
526
|
+
if (a.type === OID_SIGNING_CERTIFICATE_V2 && v2 === null && a.values && a.values.length === 1) v2 = a.values[0];
|
|
527
|
+
});
|
|
528
|
+
if (v1 === null && v2 === null) {
|
|
529
|
+
guard.list.append(findings, {
|
|
530
|
+
code: "cms/cades-no-signing-certificate", level: "shall",
|
|
531
|
+
message: "the signing certificate shall be protected by a signing-certificate or " +
|
|
532
|
+
"signing-certificate-v2 signed attribute (ETSI EN 319 122-1 clauses 5.2.2.2 and 5.2.2.3)",
|
|
533
|
+
});
|
|
534
|
+
} else {
|
|
535
|
+
intrinsic.forEach(_bindingFindings(attrs, signerCert), function (f) {
|
|
536
|
+
guard.list.append(findings, f);
|
|
537
|
+
});
|
|
538
|
+
}
|
|
539
|
+
return guard.verdict.of({
|
|
540
|
+
conformant: guard.list.allMatch(findings, function (f) { return f.level !== "shall"; }),
|
|
541
|
+
signingCertificateAttribute: v2 !== null ? "v2" : (v1 !== null ? "v1" : null),
|
|
542
|
+
findings: findings,
|
|
543
|
+
requirementsChecked: CADES_BASELINE_CHECKED,
|
|
544
|
+
});
|
|
545
|
+
}
|
|
546
|
+
|
|
547
|
+
/** @internal What the signing-certificate attribute claims, checked against the certificate this
|
|
548
|
+
* SignerInfo selected. The requirement is that the certificate IS protected, so the attribute's
|
|
549
|
+
* presence is not the answer: an identifier naming other bytes binds the signature to a certificate
|
|
550
|
+
* nobody resolved it to, which is the substitution the attribute exists to prevent (RFC 5035
|
|
551
|
+
* clause 5.4: "The first certificate identified in the sequence of certificate identifiers MUST be
|
|
552
|
+
* the certificate used to verify the signature").
|
|
553
|
+
*
|
|
554
|
+
* The claims are read by `pki.schema.smime.certBindingFacts`, the one implementation of "does this
|
|
555
|
+
* identifier name that certificate", which `pki.tsp.verify` holds a timestamp token to. What differs
|
|
556
|
+
* is the policy over the facts, not the facts: that verb refuses a token on the first failing claim,
|
|
557
|
+
* and this one reports every claim that failed, because a conformance verdict is read to find out
|
|
558
|
+
* what is wrong rather than to decide one thing. A superseded v1 attribute is held to the same claim
|
|
559
|
+
* as the v2 that supersedes it, so a structure naming one certificate in each is reported rather
|
|
560
|
+
* than resolved in favor of whichever a reader prefers. An identifier this build cannot recompute,
|
|
561
|
+
* and a signature whose certificate was never matched, are reported as the undecided claims they
|
|
562
|
+
* are rather than passed over. */
|
|
563
|
+
function _bindingFindings(attrs, signerCert) {
|
|
564
|
+
if (signerCert == null) {
|
|
565
|
+
return [{ code: "cms/cades-signing-certificate-unverifiable", level: "shall",
|
|
566
|
+
message: "no certificate was matched for this signer, so what the signing-certificate " +
|
|
567
|
+
"attribute binds cannot be checked" }];
|
|
568
|
+
}
|
|
569
|
+
var facts = smime.certBindingFacts(attrs, signerCert);
|
|
570
|
+
var out = [];
|
|
571
|
+
var entries = [facts.chosen];
|
|
572
|
+
intrinsic.forEach(facts.superseded, function (s) { guard.list.append(entries, s); });
|
|
573
|
+
intrinsic.forEach(entries, function (e) {
|
|
574
|
+
var which = e.form === "v2" ? "signing-certificate-v2" : "signing-certificate";
|
|
575
|
+
if (!e.readable) {
|
|
576
|
+
guard.list.append(out, { code: "cms/cades-signing-certificate-unreadable", level: "shall",
|
|
577
|
+
message: "the " + which + " attribute value does not decode, so what it binds cannot be checked" });
|
|
578
|
+
return;
|
|
579
|
+
}
|
|
580
|
+
if (!e.supported) {
|
|
581
|
+
guard.list.append(out, { code: "cms/cades-signing-certificate-unreadable", level: "shall",
|
|
582
|
+
message: "the " + which + " identifier names the digest " + e.digestName +
|
|
583
|
+
", which this build does not bind a certificate with, so what it binds cannot be checked" });
|
|
584
|
+
return;
|
|
585
|
+
}
|
|
586
|
+
/** @internal The form is held to the digest it binds with, which is what defines each of the two
|
|
587
|
+
* attributes: a v2 identifier naming SHA-1 is the case requirement h sends to the v1 attribute.
|
|
588
|
+
* A v1 identifier carries no algorithm field, so it is SHA-1 by the type and can never be the
|
|
589
|
+
* wrong form for its own digest; what it draws is requirement j's migration guidance. */
|
|
590
|
+
if (e.weak && e.form === "v2") {
|
|
591
|
+
guard.list.append(out, { code: "cms/cades-signing-certificate-form", level: "shall",
|
|
592
|
+
message: "the signing-certificate attribute shall be used if the SHA-1 hash algorithm is " +
|
|
593
|
+
"used (ETSI EN 319 122-1 clause 6.3 requirement h), and this signature carries a " +
|
|
594
|
+
"signing-certificate-v2 identifier binding with " + e.digestName });
|
|
595
|
+
} else if (e.weak) {
|
|
596
|
+
guard.list.append(out, { code: "cms/cades-weak-certificate-binding", level: "should",
|
|
597
|
+
message: "the " + which + " identifier binds the certificate with " + e.digestName +
|
|
598
|
+
", and a generator should migrate to signing-certificate-v2 in preference to " +
|
|
599
|
+
"signing-certificate given the limited lifetime for the use of SHA-1 (ETSI EN 319 122-1 " +
|
|
600
|
+
"clause 6.3 requirement j)" });
|
|
601
|
+
}
|
|
602
|
+
if (e.issuerSerialPresent) {
|
|
603
|
+
guard.list.append(out, { code: "cms/cades-issuer-serial-included", level: "should",
|
|
604
|
+
message: "the issuerSerial field should not be included in the encoding of the " +
|
|
605
|
+
(e.form === "v2" ? "ESSCertIDv2" : "ESSCertID") + " type (ETSI EN 319 122-1 clause 6.3 " +
|
|
606
|
+
"requirement g); clause 5.2.2.2 NOTE 2 has it as a hint, the binding being the digest of " +
|
|
607
|
+
"the certificate" });
|
|
608
|
+
}
|
|
609
|
+
if (e.certUnreadable) {
|
|
610
|
+
guard.list.append(out, { code: "cms/cades-signing-certificate-unverifiable", level: "shall",
|
|
611
|
+
message: "the " + which + " identifier states an issuer and serial, and the certificate " +
|
|
612
|
+
"this signer resolved to does not parse, so that claim cannot be checked" });
|
|
613
|
+
}
|
|
614
|
+
if (e.matches !== true || e.issuerSerialMatches === false) {
|
|
615
|
+
guard.list.append(out, { code: "cms/cades-signing-certificate-mismatch", level: "shall",
|
|
616
|
+
message: "the first " + which + " identifier does not name the certificate this signature " +
|
|
617
|
+
"was resolved to: " + (e.matches !== true ? "the certificate hash disagrees"
|
|
618
|
+
: "the hash agrees but the issuer and serial name another certificate") +
|
|
619
|
+
" (RFC 5035 clause 5.4), so the signing certificate is not protected (ETSI EN 319 122-1 " +
|
|
620
|
+
"clauses 5.2.2.2 and 5.2.2.3)" });
|
|
621
|
+
}
|
|
622
|
+
});
|
|
623
|
+
return out;
|
|
624
|
+
}
|
|
625
|
+
|
|
626
|
+
/** @internal The CAdES signature timestamps on one SignerInfo, each as its own row.
|
|
627
|
+
*
|
|
628
|
+
* `id-aa-signatureTimeStampToken` is `{ 1 2 840 113549 1 9 16 2 14 }` (RFC 5126 clause 6.1.1; ETSI
|
|
629
|
+
* EN 319 122-1 clause 5.3 and Annex D) and its imprint is "the hash value of the signature field
|
|
630
|
+
* (without the ASN.1 tag and length) within SignerInfo for which the signature-time-stamp attribute
|
|
631
|
+
* is created" (EN 319 122-1 clause 5.3).
|
|
632
|
+
*
|
|
633
|
+
* Read HERE, in the loop that already holds `targetSig`, rather than in a verb of its own. The
|
|
634
|
+
* imprint preimage is the same octets the countersignature row covers, and a separate verb would
|
|
635
|
+
* re-derive it from its own parse: two derivations of one preimage that can disagree is a silent
|
|
636
|
+
* fail-open, since the one that disagrees is the one that accepts.
|
|
637
|
+
*
|
|
638
|
+
* Plurality is the opposite of countersignature: EN 319 122-1 clause 5.3 has "exactly one component
|
|
639
|
+
* of AttributeValue type" per attribute, and RFC 5126 clause 6.1.1 allows "several instances of this
|
|
640
|
+
* attribute ... from different TSAs", so each instance is one row and an instance carrying two values
|
|
641
|
+
* is refused rather than read as two timestamps.
|
|
642
|
+
*
|
|
643
|
+
* Every failure is a ROW verdict and never a throw: a signature whose timestamp cannot be read still
|
|
644
|
+
* has its own signature verdict, and collapsing the two would lose it.
|
|
645
|
+
*
|
|
646
|
+
* `0.4.0.1733.2.4`, the archive timestamp, is NOT read here. EN 319 122-1 clause 5.5.3 contains the
|
|
647
|
+
* sentence "The archive-time-stamp-v3 attribute shall be identified by the
|
|
648
|
+
* id-aa-signatureTimeStampToken OID" and then defines `id-aa-ets-archiveTimestampV3` under the ETSI
|
|
649
|
+
* arc, as does Annex D. The ASN.1 is followed: the two attributes hash different preimages, and
|
|
650
|
+
* clause 5.5.3 step 3 has the archive timestamp cover six SignerInfo fields "including the tag,
|
|
651
|
+
* length and value octets" where this one excludes the tag and length of one of them, so a dispatcher
|
|
652
|
+
* following that sentence would hash the wrong octets for every B-T signature. */
|
|
653
|
+
async function _verifySignatureTimestamps(si, targetSig, opts) {
|
|
654
|
+
var rows = [];
|
|
655
|
+
var attrs = [];
|
|
656
|
+
intrinsic.forEach(si.unsignedAttrs || [], function (a) { if (a.type === OID_TIMESTAMP_TOKEN) guard.list.append(attrs, a); });
|
|
657
|
+
if (!attrs.length) return rows;
|
|
658
|
+
/** @internal The signer's own claimed signing time and certificate window, read once here and
|
|
659
|
+
* passed to every row, so each row answers the coherence questions against the same values. */
|
|
660
|
+
var claimed = _signingTimeOf(si);
|
|
661
|
+
for (var i = 0; i < attrs.length; i++) {
|
|
662
|
+
guard.list.append(rows, await _verifyOneTimestamp(attrs[i], targetSig, claimed, opts));
|
|
663
|
+
}
|
|
664
|
+
return rows;
|
|
665
|
+
}
|
|
666
|
+
|
|
667
|
+
/** @internal The signer's `signing-time` attribute as a Date, or null where it carries none. It is the
|
|
668
|
+
* signer's own assertion and is not evidence of the real time, but it sits inside the signature, so a
|
|
669
|
+
* timestamp that precedes it is a contradiction within one signature. */
|
|
670
|
+
function _signingTimeOf(si) {
|
|
671
|
+
var found = null;
|
|
672
|
+
intrinsic.forEach(si.signedAttrs || [], function (a) {
|
|
673
|
+
if (a.type !== OID_SIGNING_TIME || found !== null || !a.values || !a.values.length) return;
|
|
674
|
+
/** @internal Read without a guard around it: `pki.schema.cms.parse` holds a signing-time
|
|
675
|
+
* attribute's value to a Time through this same reader before any of this runs, so a value
|
|
676
|
+
* that reaches here reads. A parser that stopped checking would surface the failure rather
|
|
677
|
+
* than have this quietly report no claimed time. */
|
|
678
|
+
found = asn1.read.time(asn1.decode(a.values[0]));
|
|
679
|
+
});
|
|
680
|
+
return found;
|
|
681
|
+
}
|
|
682
|
+
|
|
683
|
+
/** @internal The instant a Date names, or null where the value is absent or names no instant. Every
|
|
684
|
+
* ordering rule below reads its operands through this, so "no value" and "a value that cannot be
|
|
685
|
+
* ordered" reach the comparison as the same absence rather than as a comparison that is false both
|
|
686
|
+
* ways. */
|
|
687
|
+
function _instantOrNull(value) {
|
|
688
|
+
if (!guard.time.isDate(value)) return null;
|
|
689
|
+
var at = guard.time.instantOf(value);
|
|
690
|
+
return intrinsic.numberIsNaN(at) ? null : at;
|
|
691
|
+
}
|
|
692
|
+
|
|
693
|
+
async function _verifyOneTimestamp(attr, targetSig, claimedSigningTime, opts) {
|
|
694
|
+
if (!attr.values || attr.values.length !== 1) {
|
|
695
|
+
return guard.verdict.of({ valid: false, trusted: false, genTime: null,
|
|
696
|
+
code: "cms/timestamp-multi-valued",
|
|
697
|
+
message: "a signature-time-stamp attribute shall contain exactly one component of AttributeValue " +
|
|
698
|
+
"type (ETSI EN 319 122-1 clause 5.3), got " + ((attr.values && attr.values.length) || 0) });
|
|
699
|
+
}
|
|
700
|
+
var tokenDer = attr.values[0];
|
|
701
|
+
tokenDer = tokenDer && tokenDer.bytes ? tokenDer.bytes : tokenDer;
|
|
702
|
+
var res;
|
|
703
|
+
try {
|
|
704
|
+
/** @internal Reached inside the function rather than at the top of the file: `tsp-sign` requires
|
|
705
|
+
* THIS module to verify a token's own CMS signature, so a top-level require here is a cycle and
|
|
706
|
+
* one of the two would see a half-built export object. */
|
|
707
|
+
var tspVerify = require("./tsp-sign"); // allow:inline-require -- circular load: tsp-sign requires this module
|
|
708
|
+
res = await tspVerify.verify(tokenDer, targetSig, {
|
|
709
|
+
certs: opts.timestampCerts,
|
|
710
|
+
trustAnchors: opts.timestampTrustAnchors,
|
|
711
|
+
revocationChecker: opts.timestampRevocationChecker,
|
|
712
|
+
allowWeakDigests: opts.allowWeakDigests === true,
|
|
713
|
+
});
|
|
714
|
+
} catch (e) {
|
|
715
|
+
return guard.verdict.of({ valid: false, trusted: false, genTime: null,
|
|
716
|
+
code: (e && e.code) || "cms/bad-timestamp", message: e && e.message });
|
|
717
|
+
}
|
|
718
|
+
if (res.valid !== true) {
|
|
719
|
+
return guard.verdict.of({ valid: false, trusted: res.trusted === true, genTime: res.genTime || null,
|
|
720
|
+
code: res.code === "tsp/imprint-mismatch" ? "cms/timestamp-imprint-mismatch" : res.code,
|
|
721
|
+
message: res.reason || null });
|
|
722
|
+
}
|
|
723
|
+
/** @internal Two coherence rules the token alone cannot answer, each against a value read from the
|
|
724
|
+
* signature this attribute hangs on. The first is the signer's claimed time; the second is
|
|
725
|
+
* EN 319 122-1 clause 6.3 requirement m, "the tokens ... shall be created before the signing
|
|
726
|
+
* certificate has been revoked or has expired", whose expiry half is decidable from the signer
|
|
727
|
+
* certificate alone and whose revocation half needs status this verb does not fetch.
|
|
728
|
+
*
|
|
729
|
+
* Each instant is taken once, through the captured read, and a genTime that is not a usable
|
|
730
|
+
* instant is refused rather than compared: an ordering test against it answers false in both
|
|
731
|
+
* directions, so a token carrying one would pass both rules by being uncomparable. */
|
|
732
|
+
var genAt = _instantOrNull(res.genTime);
|
|
733
|
+
if (res.genTime != null && genAt === null) {
|
|
734
|
+
return guard.verdict.of({ valid: false, trusted: res.trusted === true, genTime: null,
|
|
735
|
+
code: "cms/bad-timestamp-gentime",
|
|
736
|
+
message: "the token's genTime is not a usable instant, so the times this attribute is held " +
|
|
737
|
+
"to cannot be ordered against it" });
|
|
738
|
+
}
|
|
739
|
+
var claimedAt = _instantOrNull(claimedSigningTime);
|
|
740
|
+
if (claimedAt !== null && genAt !== null && genAt < claimedAt) {
|
|
741
|
+
return guard.verdict.of({ valid: false, trusted: res.trusted === true, genTime: res.genTime,
|
|
742
|
+
code: "cms/timestamp-before-signing-time",
|
|
743
|
+
message: "the token's genTime " + guard.time.isoOf(res.genTime) + " precedes the signing-time " +
|
|
744
|
+
"the signature itself asserts, " + guard.time.isoOf(claimedSigningTime) });
|
|
745
|
+
}
|
|
746
|
+
/** @internal The upper bound is the LATEST instant the token could have been created at, not the
|
|
747
|
+
* instant it names. Two things widen it past a `Date`: RFC 3161 clause 2.4.2 lets genTime carry a
|
|
748
|
+
* fraction finer than a Date holds, so a time one microsecond past a notAfter truncates to the
|
|
749
|
+
* notAfter itself, and the accuracy the token states is a window around genTime rather than a
|
|
750
|
+
* point, so a genTime half a second inside the window with two seconds of accuracy establishes
|
|
751
|
+
* nothing about which side of it the token was made on. Requirement m asks that the token WAS
|
|
752
|
+
* created before the certificate expired, so a bound that cannot establish it refuses. */
|
|
753
|
+
var notAfterAt = _instantOrNull(opts.signerNotAfter);
|
|
754
|
+
var genUpper = genAt === null ? null
|
|
755
|
+
: guard.time.latestInstantOf(res.genTime, res.tstInfo && res.tstInfo.genTimeFraction, res.accuracy || null);
|
|
756
|
+
if (notAfterAt !== null && genUpper !== null && genUpper > notAfterAt) {
|
|
757
|
+
return guard.verdict.of({ valid: false, trusted: res.trusted === true, genTime: res.genTime,
|
|
758
|
+
code: "cms/timestamp-after-signer-expiry",
|
|
759
|
+
/** @internal The message names the BOUND, not just the genTime: this verdict is reached
|
|
760
|
+
* whenever the latest instant the token could have been created at is past the notAfter, and
|
|
761
|
+
* with a wide accuracy or a sub-millisecond fraction that is true of tokens whose stated
|
|
762
|
+
* genTime is inside the window. Reporting the genTime alone would give an operator a reason
|
|
763
|
+
* they can check and find false. */
|
|
764
|
+
message: "the token's genTime " + guard.time.isoOf(res.genTime) + ", with the accuracy it " +
|
|
765
|
+
"states, puts the latest it could have been created at " +
|
|
766
|
+
guard.time.isoOf(new intrinsic.Date(genUpper)) + ", past the signing certificate's notAfter " +
|
|
767
|
+
guard.time.isoOf(opts.signerNotAfter) + ", so it does not establish that the token was " +
|
|
768
|
+
"created before that certificate expired (ETSI EN 319 122-1 clause 6.3 requirement m)" });
|
|
769
|
+
}
|
|
770
|
+
return guard.verdict.of({
|
|
771
|
+
valid: true, trusted: res.trusted === true, genTime: res.genTime,
|
|
772
|
+
accuracy: res.accuracy, serialNumber: res.serialNumber, policy: res.policy,
|
|
773
|
+
revocationChecked: res.revocationChecked, tsa: res.tsa, signer: res.signer,
|
|
774
|
+
});
|
|
775
|
+
}
|
|
776
|
+
|
|
777
|
+
/** @internal The signing certificate's `notAfter`, where the signer row matched one. The expiry half
|
|
778
|
+
* of EN 319 122-1 clause 6.3 requirement m is decidable from this certificate alone; the revocation
|
|
779
|
+
* half needs status this verb does not fetch and is not claimed. */
|
|
780
|
+
function _signerNotAfter(verdict) {
|
|
781
|
+
if (!verdict || !verdict.cert) return null;
|
|
782
|
+
try { return x509.parse(verdict.cert).validity.notAfter; }
|
|
783
|
+
// allow:swallow-unverified a signer certificate that does not parse already failed its own verdict
|
|
784
|
+
catch (_e) { return null; }
|
|
785
|
+
}
|
|
786
|
+
|
|
420
787
|
async function _verifyCountersignatures(si, parsedCerts, allowWeakDigests) {
|
|
421
788
|
var targetSig = _toBuf(si.signature, "the countersigned signature");
|
|
422
789
|
var values = [];
|
|
@@ -455,7 +822,7 @@ async function _verifyOneCountersig(vDer, targetSig, parsedCerts, allowWeakDiges
|
|
|
455
822
|
* Verify a CMS SignedData signature (RFC 5652 sec. 5). `input` is a PEM string, a DER
|
|
456
823
|
* `Buffer`, or a parsed `pki.schema.cms` object. Returns `{ valid, trusted, eContentType, eContent, signers }`
|
|
457
824
|
* (`eContent` the encapsulated content when present, else null) where each `signers[i]` is
|
|
458
|
-
* `{ ok, sid, cert, trusted, signedAttributesPresent, signedAttributes, unsignedAttrs, digestAlgorithm, countersignatures }`
|
|
825
|
+
* `{ ok, sid, cert, trusted, signedAttributesPresent, signedAttributes, unsignedAttrs, digestAlgorithm, countersignatures, signatureTimeStamps, cadesBaseline }`
|
|
459
826
|
* (`cert` the matched signer certificate DER) or carries a `code` on a structural failure; `valid` is true when
|
|
460
827
|
* there is at least one signer and every signer verified.
|
|
461
828
|
*
|
|
@@ -480,12 +847,43 @@ async function _verifyOneCountersig(vDer, targetSig, parsedCerts, allowWeakDiges
|
|
|
480
847
|
* `subjectKeyIdentifier` names a key, and several certificates can hold it with different
|
|
481
848
|
* validity windows, key usage and policies; deciding from a sibling would let an expired or
|
|
482
849
|
* wrong-purpose signer certificate be reported trusted because a different certificate chained.
|
|
483
|
-
* Supply the certificate you want used.
|
|
850
|
+
* Supply the certificate you want used. Where several candidates match the identifier and the
|
|
851
|
+
* signature carries an ESS signing-certificate attribute, the candidate that attribute names is the
|
|
852
|
+
* one selected, since RFC 5035 clause 5.4 makes its first identifier "the certificate used to verify
|
|
853
|
+
* the signature"; a named candidate must still verify the signature like any other. RSA (PKCS#1 v1.5 and RSASSA-PSS), ECDSA, EdDSA, and the post-quantum
|
|
484
854
|
* ML-DSA (ML-DSA-44/65/87, RFC 9882) and SLH-DSA (the twelve FIPS 205 sets, RFC 9814) signatures
|
|
485
855
|
* are recognized, the post-quantum families in pure mode with an empty context, as is composite
|
|
486
856
|
* ML-DSA (draft-ietf-lamps-cms-composite-sigs), which pairs ML-DSA with a traditional
|
|
487
857
|
* RSA / ECDSA / EdDSA and verifies only when both components pass (never an AND-to-OR downgrade).
|
|
488
858
|
*
|
|
859
|
+
* `signatureTimeStamps` is one row per `id-aa-signatureTimeStampToken` unsigned attribute (RFC 5126
|
|
860
|
+
* clause 6.1.1, ETSI EN 319 122-1 clause 5.3), each `{ valid, trusted, genTime, accuracy,
|
|
861
|
+
* serialNumber, policy, revocationChecked, tsa, signer }` or `{ valid: false, code, message }`. A
|
|
862
|
+
* token that does not verify is a row verdict and does not take the signer's own `ok` down: the
|
|
863
|
+
* signature and the time it was made at are separate claims. Each row's imprint is checked against
|
|
864
|
+
* the signature octets this verb just verified, and a row is refused when its `genTime` precedes the
|
|
865
|
+
* signer's own `signing-time` attribute or falls after the signing certificate's `notAfter`
|
|
866
|
+
* (EN 319 122-1 clause 6.3 requirement m).
|
|
867
|
+
*
|
|
868
|
+
* What a `valid: true` row establishes is that the token was issued over the signature octets THIS
|
|
869
|
+
* SignerInfo carries. It does not say that signature is sound, which is `signers[i].ok`, and rows are
|
|
870
|
+
* reported beside a failed signature rather than withheld, so a caller acting on a timestamp reads
|
|
871
|
+
* both. The imprint names signature octets rather than a signer, which is what EN 319 122-1
|
|
872
|
+
* clause 5.3 specifies: two certificates holding one key produce one signature value over one
|
|
873
|
+
* content, and a token over it answers for either. `pki.cms.attachTimestamp` adds such an attribute to an
|
|
874
|
+
* existing message and `pki.cms.timestampImprint` produces the imprint a TSA signs.
|
|
875
|
+
*
|
|
876
|
+
* `cadesBaseline` is a third claim, `{ conformant, signingCertificateAttribute, findings,
|
|
877
|
+
* requirementsChecked }`: a signature can be sound and still not be a CAdES signature.
|
|
878
|
+
* `requirementsChecked` lists the EN 319 122-1 requirements decided here, each unmet one carrying a
|
|
879
|
+
* `{ code, message }` finding, so `conformant` is read against that list and not as a full
|
|
880
|
+
* clause 6.3 audit. The signing-certificate requirement is the attribute's CLAIM checked against the
|
|
881
|
+
* certificate this SignerInfo selected, the one reported as `signers[i].cert`, not the attribute's
|
|
882
|
+
* presence. Like the timestamp rows it is reported beside a failed signature rather than withheld,
|
|
883
|
+
* so `conformant: true` on a signer whose `ok` is false says the CAdES attributes are in order and
|
|
884
|
+
* nothing about the signature. `pki.cms.sign` reaches `conformant: true` with a
|
|
885
|
+
* `signingCertificateV2` attribute built by `pki.schema.smime.buildSigningCertificateV2`.
|
|
886
|
+
*
|
|
489
887
|
* @opts content The detached content when the SignedData carries no encapsulated eContent, and
|
|
490
888
|
* required for a detached signature. A `Buffer`, or an async iterable of byte chunks
|
|
491
889
|
* to verify a large payload without holding it in memory. The streamed form is hashed
|
|
@@ -503,6 +901,13 @@ async function _verifyOneCountersig(vDer, targetSig, parsedCerts, allowWeakDiges
|
|
|
503
901
|
* question from `requiredEku`, since a root distributed with NSS trust bits can be
|
|
504
902
|
* marked untrusted for one purpose and good for another. Those bits and
|
|
505
903
|
* `distrustAfter` are consulted only when this names a purpose.
|
|
904
|
+
* @opts timestampCerts Extra TSA certificates for the signature-timestamp rows, in addition to the
|
|
905
|
+
* ones each token embeds.
|
|
906
|
+
* @opts timestampTrustAnchors The roots a time-stamping authority must chain to for a timestamp row
|
|
907
|
+
* to report `trusted`. Named apart from `trustAnchors` because a TSA is a different
|
|
908
|
+
* trust decision from the signer: an operator may anchor one and not the other.
|
|
909
|
+
* @opts timestampRevocationChecker A revocation checker for the TSA chain, as `pki.path.validate`
|
|
910
|
+
* takes one. Each row reports what it established in `revocationChecked`.
|
|
506
911
|
* @example
|
|
507
912
|
* var pair = await pki.key.generate("Ed25519");
|
|
508
913
|
* var key = await pki.key.export(pair.privateKey);
|
|
@@ -540,7 +945,12 @@ async function _resolveContentDigests(parsed, streamSource) {
|
|
|
540
945
|
return byName;
|
|
541
946
|
}
|
|
542
947
|
|
|
543
|
-
var _VERIFY_OPTS = intrinsic.assign(intrinsic.create(null), { certs: 1, content: 1, trustAnchors: 1, time: 1, requiredEku: 1, checkPurpose: 1, allowWeakDigests: 1
|
|
948
|
+
var _VERIFY_OPTS = intrinsic.assign(intrinsic.create(null), { certs: 1, content: 1, trustAnchors: 1, time: 1, requiredEku: 1, checkPurpose: 1, allowWeakDigests: 1,
|
|
949
|
+
/** @internal The timestamp inputs are named apart from the signer's own, because a time-stamping
|
|
950
|
+
* authority is a different trust decision from the signer's: an operator may anchor the TSA while
|
|
951
|
+
* leaving the signer unanchored, or the reverse, and one `trustAnchors` for both would make those
|
|
952
|
+
* two answers one. */
|
|
953
|
+
timestampCerts: 1, timestampTrustAnchors: 1, timestampRevocationChecker: 1 });
|
|
544
954
|
|
|
545
955
|
function verify(input, opts) {
|
|
546
956
|
return guard.async.deferred(function () { return _verify(input, opts); });
|
|
@@ -555,6 +965,7 @@ async function _verify(input, opts) {
|
|
|
555
965
|
if (!Array.isArray(parsed.signerInfos)) throw _err("cms/bad-input", "input is not a CMS SignedData");
|
|
556
966
|
var trustCfg = _snapshotTrust(opts);
|
|
557
967
|
var allowWeakDigests = opts.allowWeakDigests === true;
|
|
968
|
+
var tsTrustCfg = _snapshotTimestampTrust(opts, allowWeakDigests);
|
|
558
969
|
var parsedCerts = [];
|
|
559
970
|
(parsed.certificates || []).forEach(function (c) { _addCert(parsedCerts, c && c.bytes ? c.bytes : c); });
|
|
560
971
|
(opts.certs || []).forEach(function (c) { _addCert(parsedCerts, c); });
|
|
@@ -582,11 +993,18 @@ async function _verify(input, opts) {
|
|
|
582
993
|
var siOne = parsed.signerInfos[_sp];
|
|
583
994
|
var verdict = await _verifyOne(siOne, content, eContentType, parsedCerts, null, digestByName, allowWeakDigests);
|
|
584
995
|
var counters = await _verifyCountersignatures(siOne, parsedCerts, allowWeakDigests);
|
|
996
|
+
/** @internal The timestamp rows are built from the same `signature` octets the signature verdict
|
|
997
|
+
* above was computed over, which is why they are here and not in a verb of their own. The
|
|
998
|
+
* signer's own certificate supplies the expiry half of EN 319 122-1 clause 6.3 requirement m. */
|
|
999
|
+
var stamps = await _verifySignatureTimestamps(siOne, _toBuf(siOne.signature, "the timestamped signature"),
|
|
1000
|
+
intrinsic.assign(intrinsic.create(null), tsTrustCfg, { signerNotAfter: _signerNotAfter(verdict) }));
|
|
585
1001
|
guard.list.append(signers, guard.verdict.of(verdict, {
|
|
586
1002
|
countersignatures: counters,
|
|
1003
|
+
signatureTimeStamps: stamps,
|
|
587
1004
|
unsignedAttrs: _surfaceUnsignedAttrs(siOne),
|
|
588
1005
|
signedAttributesPresent: !!siOne.signedAttrsBytes,
|
|
589
1006
|
signedAttributes: _surfaceSignedAttrs(siOne),
|
|
1007
|
+
cadesBaseline: _cadesBaseline(siOne, verdict && verdict.cert ? verdict.cert : null),
|
|
590
1008
|
digestAlgorithm: siOne.digestAlgorithm.name,
|
|
591
1009
|
trusted: false,
|
|
592
1010
|
}));
|
|
@@ -640,6 +1058,30 @@ function _snapshotTrust(opts) {
|
|
|
640
1058
|
};
|
|
641
1059
|
}
|
|
642
1060
|
|
|
1061
|
+
/** @internal The time-stamping authority's trust inputs, taken at the call like the signer's own and
|
|
1062
|
+
* for the same reason: they are first read inside the per-signer loop, after the signature and
|
|
1063
|
+
* countersignature checks have awaited, so an options object that changes between rows, or one
|
|
1064
|
+
* whose properties are accessors, would have two rows of one verdict judged against two different
|
|
1065
|
+
* authorities. The certificates are copied by value, since a caller holding the array could swap an
|
|
1066
|
+
* entry after the row that read it. The checker is a function and is captured as the one reference
|
|
1067
|
+
* every row calls. */
|
|
1068
|
+
function _snapshotTimestampTrust(opts, allowWeakDigests) {
|
|
1069
|
+
var certs = opts.timestampCerts;
|
|
1070
|
+
var anchors = opts.timestampTrustAnchors;
|
|
1071
|
+
return {
|
|
1072
|
+
timestampCerts: certs == null ? certs
|
|
1073
|
+
: guard.list.copyMap(intrinsic.isArray(certs) ? certs : [certs], function (c) {
|
|
1074
|
+
return _snapshotIfBytes(c, "opts.timestampCerts[]");
|
|
1075
|
+
}),
|
|
1076
|
+
timestampTrustAnchors: anchors == null ? anchors
|
|
1077
|
+
: guard.list.copyMap(intrinsic.isArray(anchors) ? anchors : [anchors], function (a) {
|
|
1078
|
+
return typeof a === "string" ? a : _cloneAnchorValue(a, 0);
|
|
1079
|
+
}),
|
|
1080
|
+
timestampRevocationChecker: opts.timestampRevocationChecker,
|
|
1081
|
+
allowWeakDigests: allowWeakDigests,
|
|
1082
|
+
};
|
|
1083
|
+
}
|
|
1084
|
+
|
|
643
1085
|
/** @internal A signature over content is authorized by digitalSignature, contentCommitment or
|
|
644
1086
|
* nonRepudiation alike; the shared helper carries the rest of the rule. */
|
|
645
1087
|
var _permitsSigning = pkix.keyUsagePermits(pkix.makeNS("cms", CmsError, oid),
|
|
@@ -817,6 +1259,12 @@ var sign = cmsSign.sign;
|
|
|
817
1259
|
*/
|
|
818
1260
|
var countersign = cmsSign.countersign;
|
|
819
1261
|
|
|
1262
|
+
/** @internal The two CAdES signature-timestamp verbs, re-exported here with `sign` and `countersign`
|
|
1263
|
+
* because this module is the `pki.cms` namespace's assembly point. Their documentation blocks live
|
|
1264
|
+
* with their implementations in `cms-sign.js`. */
|
|
1265
|
+
var timestampImprint = cmsSign.timestampImprint;
|
|
1266
|
+
var attachTimestamp = cmsSign.attachTimestamp;
|
|
1267
|
+
|
|
820
1268
|
/**
|
|
821
1269
|
* @primitive pki.cms.encrypt
|
|
822
1270
|
* @signature pki.cms.encrypt(content, recipients, opts?) -> Promise<Buffer | string>
|
|
@@ -1126,4 +1574,4 @@ function isCertsOnly(input) {
|
|
|
1126
1574
|
}
|
|
1127
1575
|
|
|
1128
1576
|
// @internal
|
|
1129
|
-
module.exports = { verify: verify, sign: sign, countersign: countersign, encrypt: encrypt, authenticate: authenticate, decrypt: decrypt, compress: compress, decompress: decompress, digest: cmsDigest.digest, verifyDigest: cmsDigest.verifyDigest, certsOnly: certsOnly, parseCertsOnly: parseCertsOnly, isCertsOnly: isCertsOnly, setEngine: setEngine };
|
|
1577
|
+
module.exports = { verify: verify, sign: sign, countersign: countersign, encrypt: encrypt, authenticate: authenticate, decrypt: decrypt, compress: compress, decompress: decompress, digest: cmsDigest.digest, verifyDigest: cmsDigest.verifyDigest, certsOnly: certsOnly, parseCertsOnly: parseCertsOnly, isCertsOnly: isCertsOnly, setEngine: setEngine, timestampImprint: timestampImprint, attachTimestamp: attachTimestamp };
|
package/lib/constants.js
CHANGED
|
@@ -293,6 +293,21 @@ var NAMES = Object.assign(Object.create(null), {
|
|
|
293
293
|
* this length: an OCSP CertID's issuer hashes, a timestamp's messageImprint. The XOFs (SHAKE) have
|
|
294
294
|
* no single length and are absent. */
|
|
295
295
|
DIGEST_OCTETS: { sha1: 20, sha256: 32, sha384: 48, sha512: 64, "sha3-256": 32, "sha3-512": 64 },
|
|
296
|
+
/** @internal The digests a verify path refuses by default and admits only under an explicit
|
|
297
|
+
* opt-in, by the OID registry's name, with the WebCrypto hash name each maps to. A digest is weak
|
|
298
|
+
* once a collision is reachable, which breaks the binding of every structure that carries "the
|
|
299
|
+
* hash of X": a signature's message digest, a timestamp's messageImprint, an ESS certificate
|
|
300
|
+
* identifier. Membership is defined once here, so a path that refuses one refuses all of them and
|
|
301
|
+
* a path that admits them under an opt-in admits the same set. A digest no path will compute at
|
|
302
|
+
* all is not listed; it is absent from DIGEST_HASH instead. */
|
|
303
|
+
WEAK_DIGESTS: { sha1: "SHA-1" },
|
|
304
|
+
/** @internal The digests an RFC 3161 message imprint may name in this build, by the OID registry's
|
|
305
|
+
* name, with the node name each computes under. One table for the whole timestamp round trip: a
|
|
306
|
+
* verb that TAKES an imprint, a verb that attaches a token over one, and the verifier that reads
|
|
307
|
+
* it all answer from this, so none of them can produce an imprint another would refuse. SHA-1 is
|
|
308
|
+
* listed because archived tokens carry it and the verifier admits it under an explicit opt-in;
|
|
309
|
+
* WEAK_DIGESTS is what decides that, not absence from here. */
|
|
310
|
+
TIMESTAMP_IMPRINT_DIGESTS: { sha1: "sha1", sha256: "sha256", sha384: "sha384", sha512: "sha512" },
|
|
296
311
|
});
|
|
297
312
|
|
|
298
313
|
/** @internal Each inner table is looked up by a name a certificate supplies, so it answers from
|