@blamejs/pki 0.8.50 → 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 +37 -0
- package/README.md +4 -4
- package/index.js +2 -1
- package/lib/acme.js +33 -13
- package/lib/cmp-build.js +9 -3
- package/lib/cms-sign.js +254 -0
- package/lib/cms-verify.js +456 -8
- package/lib/constants.js +18 -2
- package/lib/est.js +31 -14
- package/lib/guard-intrinsic.js +4 -0
- package/lib/guard-time.js +98 -0
- package/lib/http-digest.js +7 -2
- package/lib/inspect.js +3 -2
- package/lib/lint.js +105 -2
- package/lib/path-validate.js +89 -8
- package/lib/pki-build.js +40 -0
- package/lib/revocation-fetch.js +11 -5
- package/lib/scep.js +26 -12
- package/lib/schema-all.js +1 -1
- package/lib/schema-smime.js +215 -5
- package/lib/tsp-sign.js +58 -95
- package/lib/x509-sign.js +22 -2
- package/package.json +1 -1
- package/sbom.cdx.json +6 -6
package/lib/cms-sign.js
CHANGED
|
@@ -25,6 +25,11 @@ var _promiseResolve = intrinsic.uncurry(intrinsic.promiseResolve);
|
|
|
25
25
|
var _promiseAll = intrinsic.uncurry(intrinsic.promiseAll);
|
|
26
26
|
var pkiBuild = require("./pki-build");
|
|
27
27
|
var cms = require("./schema-cms");
|
|
28
|
+
var constants = require("./constants");
|
|
29
|
+
/** @internal The RFC 3161 structural layer, for the one thing this module asks of a token: which
|
|
30
|
+
* octets its message imprint claims to cover. `schema-tsp` reaches only the schema modules, so this
|
|
31
|
+
* is not the signing module's own cycle back through `tsp-sign`. */
|
|
32
|
+
var tsp = require("./schema-tsp");
|
|
28
33
|
|
|
29
34
|
var subtle = webcrypto.webcrypto.subtle;
|
|
30
35
|
var CmsError = frameworkError.CmsError;
|
|
@@ -628,8 +633,257 @@ function _countersign(cmsInput, signers, opts) {
|
|
|
628
633
|
});
|
|
629
634
|
}
|
|
630
635
|
|
|
636
|
+
/** @internal One Attribute appended to a SignerInfo's `[1] unsignedAttrs`, with every attribute
|
|
637
|
+
* already there kept byte for byte and the SET OF re-sorted. A NEW attribute member rather than a
|
|
638
|
+
* value merged into an existing one of the same type: ETSI EN 319 122-1 clause 5.3 reads "The
|
|
639
|
+
* signature-time-stamp attribute shall contain exactly one component of AttributeValue type", while
|
|
640
|
+
* RFC 5126 clause 6.1.1 has "Several instances of this attribute may occur with an electronic
|
|
641
|
+
* signature, from different TSAs", so n timestamps are n attribute instances each carrying a
|
|
642
|
+
* one-element SET. That is the opposite of the countersignature above, where RFC 5652 clause 11.4
|
|
643
|
+
* puts n signatures in one attribute's values, which is why the two do not share this helper.
|
|
644
|
+
*
|
|
645
|
+
* `build.set` sorts with the same comparison the parser's `derSetOrder` check applies, so the
|
|
646
|
+
* rebuilt SET OF cannot be in an order the reader refuses. The `0x31` to `0xA1` re-tag is sound
|
|
647
|
+
* because both identifiers are a single octet, leaving the length encoding untouched. */
|
|
648
|
+
function _appendUnsignedAttr(siNode, attrDer) {
|
|
649
|
+
var kids = siNode.children;
|
|
650
|
+
var last = kids[kids.length - 1];
|
|
651
|
+
var hasUa = last.tagClass === "context" && last.tagNumber === 1;
|
|
652
|
+
var base = intrinsic.map((hasUa ? intrinsic.arraySlice(kids, 0, kids.length - 1) : kids), function (k) { return k.bytes; });
|
|
653
|
+
var existing = hasUa ? intrinsic.map(last.children, function (a) { return a.bytes; }) : [];
|
|
654
|
+
var setOf = b.set(intrinsic.concat(existing, [attrDer]));
|
|
655
|
+
var wire = intrinsic.bufferFrom(setOf); wire[0] = 0xA1;
|
|
656
|
+
intrinsic.push(base, wire);
|
|
657
|
+
return b.sequence(base);
|
|
658
|
+
}
|
|
659
|
+
|
|
660
|
+
/** @internal The SignerInfo node a verb is targeting, with the index checked against what the message
|
|
661
|
+
* carries rather than against a caller's assertion. */
|
|
662
|
+
function _timestampTarget(der, signerIndex, who) {
|
|
663
|
+
var parsed = cms.parse(der);
|
|
664
|
+
if (!intrinsic.isArray(parsed.signerInfos)) throw _err("cms/bad-input", who + " input is not a CMS SignedData");
|
|
665
|
+
var idx = signerIndex == null ? 0 : signerIndex;
|
|
666
|
+
if (typeof idx !== "number" || !intrinsic.isInteger(idx) || idx < 0 || idx >= parsed.signerInfos.length) {
|
|
667
|
+
throw _err("cms/bad-input", who + " signerIndex out of range: " + guard.text.showValue(signerIndex) +
|
|
668
|
+
" (the message carries " + parsed.signerInfos.length + " signer" + (parsed.signerInfos.length === 1 ? "" : "s") + ")");
|
|
669
|
+
}
|
|
670
|
+
var root = asn1.decode(der);
|
|
671
|
+
var sd = root.children[1].children[0];
|
|
672
|
+
var siSet = sd.children[sd.children.length - 1];
|
|
673
|
+
return { parsed: parsed, root: root, sd: sd, siSet: siSet, idx: idx, siNode: siSet.children[idx] };
|
|
674
|
+
}
|
|
675
|
+
|
|
676
|
+
var KNOWN_IMPRINT_OPTS = intrinsic.assign(intrinsic.create(null), { signerIndex: 1, digestAlgorithm: 1 });
|
|
677
|
+
|
|
678
|
+
/**
|
|
679
|
+
* @primitive pki.cms.timestampImprint
|
|
680
|
+
* @signature pki.cms.timestampImprint(input, opts?) -> Promise<{ imprint, signerIndex }>
|
|
681
|
+
* @since 0.8.52
|
|
682
|
+
* @status stable
|
|
683
|
+
* @spec ETSI EN 319 122-1 clause 5.3, RFC 5126 clause 6.1.1, RFC 3161 clause 2.4.1
|
|
684
|
+
* @related pki.cms.attachTimestamp, pki.tsp.request, pki.tsp.verify, pki.cms.verify
|
|
685
|
+
*
|
|
686
|
+
* The message imprint a time-stamping authority is asked to sign over, for the CAdES
|
|
687
|
+
* signature-time-stamp attribute. EN 319 122-1 clause 5.3 fixes what it covers: "the hash value of
|
|
688
|
+
* the signature field (without the ASN.1 tag and length) within SignerInfo for which the
|
|
689
|
+
* signature-time-stamp attribute is created". So this is a digest of the contents octets of
|
|
690
|
+
* `SignerInfo.signature`, with the tag and length excluded, which is the same octet rule RFC 5652
|
|
691
|
+
* clause 11.4 states for a countersignature.
|
|
692
|
+
*
|
|
693
|
+
* `input` is the SignedData as a DER `Buffer` or a PEM `CMS` string, the same input
|
|
694
|
+
* `pki.cms.countersign` takes and for the same reason: the imprint covers exact octets of the
|
|
695
|
+
* message as it stands, so this reads the wire bytes rather than a representation of them. The
|
|
696
|
+
* result is `{ imprint, signerIndex }`: `imprint` is `{ hashAlgorithm, hashedMessage }`, the
|
|
697
|
+
* argument `pki.tsp.request` and `pki.tsp.sign` take, so a third-party authority can be driven
|
|
698
|
+
* without this package signing anything, and `signerIndex` is the signer it was taken for, which is
|
|
699
|
+
* what `pki.cms.attachTimestamp` is given when the token comes back.
|
|
700
|
+
*
|
|
701
|
+
* @opts signerIndex which signer to take the imprint for (default 0).
|
|
702
|
+
* @opts digestAlgorithm the digest to take (default `"sha256"`). MD5 is refused: EN 319 122-1
|
|
703
|
+
* clause 6.2.1 reads "In addition, MD5 algorithm shall not be used as digest
|
|
704
|
+
* algorithm".
|
|
705
|
+
* @example
|
|
706
|
+
* var pair = await pki.key.generate("Ed25519");
|
|
707
|
+
* var key = await pki.key.export(pair.privateKey);
|
|
708
|
+
* var cert = await pki.x509.sign({ subject: "Signer", subjectPublicKey: await pki.key.export(pair.publicKey),
|
|
709
|
+
* notBefore: new Date("2026-01-01T00:00:00Z"), notAfter: new Date("2036-01-01T00:00:00Z") }, { key: key });
|
|
710
|
+
* var signedDer = await pki.cms.sign(Buffer.from("hello"), { cert: cert, key: key });
|
|
711
|
+
* var taken = await pki.cms.timestampImprint(signedDer);
|
|
712
|
+
* var req = pki.tsp.request(taken.imprint, { certReq: true });
|
|
713
|
+
* taken.imprint.hashAlgorithm; // "sha256"
|
|
714
|
+
*/
|
|
715
|
+
function timestampImprint(input, opts) {
|
|
716
|
+
return guard.bytes.fixedCall(CmsError, "cms/bad-input", [
|
|
717
|
+
[input, "the CMS message"], [opts, "pki.cms.timestampImprint options"],
|
|
718
|
+
], _timestampImprint);
|
|
719
|
+
}
|
|
720
|
+
|
|
721
|
+
function _timestampImprint(input, opts) {
|
|
722
|
+
opts = guard.identifier.optionsObject(opts, _err, "cms/bad-input", "pki.cms.timestampImprint options");
|
|
723
|
+
guard.identifier.assertKnownKeys(opts, KNOWN_IMPRINT_OPTS, _err, "cms/bad-input", "unknown opts field ");
|
|
724
|
+
var name = opts.digestAlgorithm == null ? "sha256" : opts.digestAlgorithm;
|
|
725
|
+
/** @internal MD5 is answered before the build's own digest table, which does not carry it either:
|
|
726
|
+
* asked for by name, it gets the clause that forbids it rather than the generic "this build does
|
|
727
|
+
* not have that digest", which is a different thing to tell an operator. */
|
|
728
|
+
if (name === "md5") throw _err("cms/bad-input", "MD5 shall not be used as a digest algorithm (ETSI EN 319 122-1 clause 6.2.1)");
|
|
729
|
+
/** @internal Admitted against the digests a timestamp VERIFIER reads an imprint under, not the
|
|
730
|
+
* build's general digest table: a digest outside that set produces an imprint whose token every
|
|
731
|
+
* verifier here refuses, so taking one would hand an operator a request for an unusable token. */
|
|
732
|
+
if (typeof name !== "string" || !intrinsic.hasOwn(constants.NAMES.TIMESTAMP_IMPRINT_DIGESTS, name)) {
|
|
733
|
+
throw _err("cms/bad-input", "digestAlgorithm must name a digest a signature timestamp is read " +
|
|
734
|
+
"under (sha256, sha384 or sha512), got " + guard.text.showValue(opts.digestAlgorithm));
|
|
735
|
+
}
|
|
736
|
+
/** @internal A weak digest is refused outright here, with no opt-in beside it. `attachTimestamp`
|
|
737
|
+
* has one because a token that already exists may be archived under a digest that was current
|
|
738
|
+
* when it was made; a token this imprint would be sent for does not exist yet, so the only thing
|
|
739
|
+
* the option could buy is a request for a token this verb's own sibling then refuses. */
|
|
740
|
+
if (intrinsic.hasOwn(constants.NAMES.WEAK_DIGESTS, name)) {
|
|
741
|
+
throw _err("cms/bad-input", "the imprint digest " + name + " is one a signature timestamp is " +
|
|
742
|
+
"refused for, so a token taken over it could not be attached or verified");
|
|
743
|
+
}
|
|
744
|
+
var der = pkix.coerceToDer(input, { pemLabel: null, PemError: frameworkError.PemError, ErrorClass: CmsError, prefix: "cms" });
|
|
745
|
+
var t = _timestampTarget(der, opts.signerIndex, "pki.cms.timestampImprint");
|
|
746
|
+
/** @internal `imprint` is nested rather than spread across the result, so the object this returns
|
|
747
|
+
* is the one `pki.tsp.request` and `pki.tsp.sign` take: their message-imprint door admits
|
|
748
|
+
* `hashAlgorithm` and `hashedMessage` and refuses any other field, which a flat result carrying
|
|
749
|
+
* the signer index would trip. The index is what `pki.cms.attachTimestamp` is then given, so it
|
|
750
|
+
* is reported beside the imprint instead of inside it. */
|
|
751
|
+
return guard.verdict.of({
|
|
752
|
+
imprint: guard.verdict.of({
|
|
753
|
+
hashAlgorithm: name,
|
|
754
|
+
hashedMessage: guard.crypto.digest(name, _signatureOctets(t.siNode)),
|
|
755
|
+
}),
|
|
756
|
+
signerIndex: t.idx,
|
|
757
|
+
});
|
|
758
|
+
}
|
|
759
|
+
|
|
760
|
+
var KNOWN_ATTACH_OPTS = intrinsic.assign(intrinsic.create(null), { signerIndex: 1, pem: 1, allowWeakDigests: 1 });
|
|
761
|
+
|
|
762
|
+
/**
|
|
763
|
+
* @primitive pki.cms.attachTimestamp
|
|
764
|
+
* @signature pki.cms.attachTimestamp(input, token, opts?) -> Promise<Buffer>
|
|
765
|
+
* @since 0.8.52
|
|
766
|
+
* @status stable
|
|
767
|
+
* @spec ETSI EN 319 122-1 clause 5.3, RFC 5126 clause 6.1.1, RFC 3161
|
|
768
|
+
* @related pki.cms.timestampImprint, pki.cms.verify, pki.tsp.sign, pki.cms.countersign
|
|
769
|
+
*
|
|
770
|
+
* Attach an RFC 3161 `TimeStampToken` to a signature as the CAdES signature-time-stamp unsigned
|
|
771
|
+
* attribute `{ 1 2 840 113549 1 9 16 2 14 }`, taking a `SignedData` to the EN 319 122-1 B-T level.
|
|
772
|
+
* `input` is that SignedData as a DER `Buffer` or a PEM `CMS` string and `token` the token a
|
|
773
|
+
* time-stamping authority returned for the imprint `pki.cms.timestampImprint` produced, as DER or
|
|
774
|
+
* PEM. Both are read as wire bytes rather than as a parsed representation, because this verb
|
|
775
|
+
* rewrites the message's own octets.
|
|
776
|
+
*
|
|
777
|
+
* It SPLICES rather than re-signs. Re-signing produces a different signature value, so a token taken
|
|
778
|
+
* over the first one would no longer match it, which is measured and not reasoned: with ECDSA the
|
|
779
|
+
* second signature differs every time. What is preserved byte for byte is therefore everything the
|
|
780
|
+
* four signature inputs cover: the encapsulated content and its type, and per signer the `version`,
|
|
781
|
+
* `sid`, `digestAlgorithm`, `[0] signedAttrs`, `signatureAlgorithm` and `signature` TLVs, plus every
|
|
782
|
+
* embedded certificate, every revocation entry, and every attribute already in `unsignedAttrs`. What
|
|
783
|
+
* is re-encoded is the targeted signer's `[1] unsignedAttrs` SET OF and the five enclosing lengths,
|
|
784
|
+
* none of which appears in any signature input.
|
|
785
|
+
*
|
|
786
|
+
* Several instances may be attached, one per authority, which is what RFC 5126 clause 6.1.1 permits
|
|
787
|
+
* and EN 319 122-1 clause 5.3 shapes: each is its own attribute carrying exactly one value.
|
|
788
|
+
*
|
|
789
|
+
* `signerIndex` names a signer of the INPUT. `signerInfos` is a SET OF, whose members are emitted in
|
|
790
|
+
* DER order, so giving one member an attribute changes its encoding and can change its position:
|
|
791
|
+
* identify a signer of the OUTPUT by its signature value or its certificate, not by the index it
|
|
792
|
+
* held on the way in.
|
|
793
|
+
*
|
|
794
|
+
* The imprint is checked before anything is written, so this verb cannot produce a message its own
|
|
795
|
+
* verifier would reject: the token's imprint must be the hash of the targeted signer's signature
|
|
796
|
+
* value, and its digest must be one the verifier admits. Nothing else about the token is judged
|
|
797
|
+
* here; `pki.cms.verify` does that.
|
|
798
|
+
*
|
|
799
|
+
* @opts signerIndex which signer to attach to (default 0).
|
|
800
|
+
* @opts pem return a PEM `CMS` block rather than DER.
|
|
801
|
+
* @opts allowWeakDigests attach a token whose imprint is a weak digest, which both this verb and
|
|
802
|
+
* `pki.cms.verify` refuse by default. This is the archived-token path, and the
|
|
803
|
+
* same option is needed again at verification.
|
|
804
|
+
* @example
|
|
805
|
+
* var pair = await pki.key.generate("Ed25519");
|
|
806
|
+
* var key = await pki.key.export(pair.privateKey);
|
|
807
|
+
* var spki = await pki.key.export(pair.publicKey);
|
|
808
|
+
* var nb = new Date("2026-01-01T00:00:00Z"), na = new Date("2036-01-01T00:00:00Z");
|
|
809
|
+
* var cert = await pki.x509.sign({ subject: "Signer", subjectPublicKey: spki, notBefore: nb, notAfter: na }, { key: key });
|
|
810
|
+
* // RFC 3161 sec. 2.3: a TSA certificate's extendedKeyUsage MUST be critical and name timeStamping alone
|
|
811
|
+
* var tsaCert = await pki.x509.sign({ subject: "Example TSA", subjectPublicKey: spki, notBefore: nb, notAfter: na,
|
|
812
|
+
* extensions: { keyUsage: ["digitalSignature"], extendedKeyUsage: ["timeStamping"], extendedKeyUsageCritical: true } },
|
|
813
|
+
* { key: key });
|
|
814
|
+
* var signedDer = await pki.cms.sign(Buffer.from("hello"), { cert: cert, key: key });
|
|
815
|
+
* var taken = await pki.cms.timestampImprint(signedDer);
|
|
816
|
+
* var token = await pki.tsp.sign(taken.imprint, { cert: tsaCert, key: key },
|
|
817
|
+
* { policy: "1.3.6.1.4.1.1", serialNumber: 1 });
|
|
818
|
+
* var bT = await pki.cms.attachTimestamp(signedDer, token, { signerIndex: taken.signerIndex });
|
|
819
|
+
* (await pki.cms.verify(bT, { certs: [cert] })).signers[0].signatureTimeStamps[0].valid; // true
|
|
820
|
+
*/
|
|
821
|
+
function attachTimestamp(input, token, opts) {
|
|
822
|
+
return guard.async.deferred(function () {
|
|
823
|
+
return guard.bytes.fixedCall(CmsError, "cms/bad-input", [
|
|
824
|
+
[input, "the CMS message"], [token, "the time-stamp token"], [opts, "pki.cms.attachTimestamp options"],
|
|
825
|
+
], _attachTimestamp);
|
|
826
|
+
});
|
|
827
|
+
}
|
|
828
|
+
|
|
829
|
+
function _attachTimestamp(input, token, opts) {
|
|
830
|
+
opts = guard.identifier.optionsObject(opts, _err, "cms/bad-input", "pki.cms.attachTimestamp options");
|
|
831
|
+
guard.identifier.assertKnownKeys(opts, KNOWN_ATTACH_OPTS, _err, "cms/bad-input", "unknown opts field ");
|
|
832
|
+
var der = pkix.coerceToDer(input, { pemLabel: null, PemError: frameworkError.PemError, ErrorClass: CmsError, prefix: "cms" });
|
|
833
|
+
var tokenDer = pkix.coerceToDer(token, { pemLabel: null, PemError: frameworkError.PemError, ErrorClass: CmsError, prefix: "cms" });
|
|
834
|
+
var t = _timestampTarget(der, opts.signerIndex, "pki.cms.attachTimestamp");
|
|
835
|
+
|
|
836
|
+
/** @internal The imprint is checked HERE, before a byte is written, against the signature this
|
|
837
|
+
* attribute will hang on. A verb that wrote first would produce a message `pki.cms.verify` refuses
|
|
838
|
+
* on the row it just created, and the caller would have no way to tell a mis-addressed token from
|
|
839
|
+
* a corrupt one. The token's own structure, its authority's certificate and its chain are not
|
|
840
|
+
* judged here: those are the verifier's, and judging them twice in two places is two answers. */
|
|
841
|
+
var tst = tsp.parseToken(tokenDer);
|
|
842
|
+
var want = tst.tstInfo.messageImprint;
|
|
843
|
+
var name = want.hashAlgorithm && want.hashAlgorithm.name;
|
|
844
|
+
if (!name || !intrinsic.hasOwn(constants.NAMES.TIMESTAMP_IMPRINT_DIGESTS, name)) {
|
|
845
|
+
throw _err("cms/bad-input", "the token's message imprint names a digest a signature timestamp " +
|
|
846
|
+
"is not read under: " + guard.text.showValue(name || (want.hashAlgorithm && want.hashAlgorithm.oid)));
|
|
847
|
+
}
|
|
848
|
+
/** @internal The identifier is held to its own encoding as well as to its name: a digest
|
|
849
|
+
* AlgorithmIdentifier carries absent or DER NULL parameters (RFC 5754 clause 2), and the ANY
|
|
850
|
+
* field is carried through the parse unvalidated, so one naming sha256 beside arbitrary bytes
|
|
851
|
+
* would otherwise be hashed as a well-formed imprint. */
|
|
852
|
+
if (!guard.der.paramsAbsentOrNull(want.hashAlgorithm.parameters)) {
|
|
853
|
+
throw _err("cms/bad-input", "the token's message imprint algorithm identifier carries " +
|
|
854
|
+
"parameters other than DER NULL, which a digest identifier does not (RFC 5754 clause 2)");
|
|
855
|
+
}
|
|
856
|
+
/** @internal The same digest posture the verifier holds this attribute to, read from the one
|
|
857
|
+
* membership list: a token whose imprint is a weak digest is refused here too, under the option
|
|
858
|
+
* that admits it there, so the verb does not write an attribute whose row `pki.cms.verify` then
|
|
859
|
+
* refuses by default. The imprint carries the whole binding between the token and the signature,
|
|
860
|
+
* and a collision in it substitutes one signature under a genuine token. */
|
|
861
|
+
if (opts.allowWeakDigests !== true && intrinsic.hasOwn(constants.NAMES.WEAK_DIGESTS, name)) {
|
|
862
|
+
throw _err("cms/weak-timestamp-imprint", "the token's message imprint is a " + name + " digest, " +
|
|
863
|
+
"which a signature timestamp is refused for by default; pass allowWeakDigests: true to attach " +
|
|
864
|
+
"an archived token under it");
|
|
865
|
+
}
|
|
866
|
+
var have = guard.crypto.digest(name, _signatureOctets(t.siNode));
|
|
867
|
+
if (!guard.crypto.constantTimeEqual(have, want.hashedMessage)) {
|
|
868
|
+
throw _err("cms/timestamp-imprint-mismatch", "the token's message imprint is not the hash of this " +
|
|
869
|
+
"signer's signature value, so attaching it would produce a signature timestamp that does not " +
|
|
870
|
+
"verify (ETSI EN 319 122-1 clause 5.3)");
|
|
871
|
+
}
|
|
872
|
+
|
|
873
|
+
var attr = b.sequence([b.oid(O("timeStampToken")), b.set([tokenDer])]);
|
|
874
|
+
var newSiSet = b.set(intrinsic.map(t.siSet.children, function (siNode, i) {
|
|
875
|
+
return i === t.idx ? _appendUnsignedAttr(siNode, attr) : siNode.bytes;
|
|
876
|
+
}));
|
|
877
|
+
var sdKids = t.sd.children;
|
|
878
|
+
var newSdFields = intrinsic.map(intrinsic.arraySlice(sdKids, 0, sdKids.length - 1), function (k) { return k.bytes; });
|
|
879
|
+
intrinsic.push(newSdFields, newSiSet);
|
|
880
|
+
var out = b.sequence([t.root.children[0].bytes, b.explicit(0, b.sequence(newSdFields))]);
|
|
881
|
+
return opts.pem ? pkix.pemEncode(out, "CMS", frameworkError.PemError) : out;
|
|
882
|
+
}
|
|
883
|
+
|
|
631
884
|
module.exports = {
|
|
632
885
|
sign: sign, countersign: countersign, certsOnly: certsOnly,
|
|
886
|
+
timestampImprint: timestampImprint, attachTimestamp: attachTimestamp,
|
|
633
887
|
// @internal
|
|
634
888
|
KNOWN_SIGNER_CERT_KEYS: KNOWN_SIGNER_CERT_KEYS,
|
|
635
889
|
KNOWN_SIGNER_KEY_ONLY_KEYS: KNOWN_SIGNER_KEY_ONLY_KEYS,
|