@blamejs/pki 0.8.51 → 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 +42 -0
- package/README.md +1 -1
- package/index.js +2 -1
- package/lib/cms-decrypt.js +40 -3
- package/lib/cms-encrypt.js +64 -2
- package/lib/cms-sign.js +321 -2
- package/lib/cms-verify.js +602 -30
- package/lib/constants.js +15 -0
- package/lib/ct.js +3 -2
- package/lib/guard-der.js +26 -0
- package/lib/guard-identifier.js +27 -0
- package/lib/guard-time.js +98 -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 +3 -2
- package/lib/schema-cms.js +377 -6
- 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-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;
|
|
@@ -35,13 +40,14 @@ function O(name) { return oid.byName(name); }
|
|
|
35
40
|
|
|
36
41
|
var KNOWN_SIGN_OPTS = intrinsic.assign(intrinsic.create(null), {
|
|
37
42
|
signedAttributes: 1, signingTime: 1, additionalSignedAttributes: 1, unsignedAttributes: 1,
|
|
43
|
+
algorithmProtection: 1,
|
|
38
44
|
sid: 1, eContentType: 1, detached: 1, certificates: 1, pem: 1,
|
|
39
45
|
});
|
|
40
46
|
var KNOWN_SIGNER_CERT_KEYS = intrinsic.assign(intrinsic.create(null), { cert: 1, key: 1, pss: 1, digestAlgorithm: 1, combinedRsaSig: 1 });
|
|
41
47
|
var KNOWN_SIGNER_KEY_ONLY_KEYS = intrinsic.assign(intrinsic.create(null), { spki: 1, keyIdentifier: 1, key: 1, pss: 1, digestAlgorithm: 1, combinedRsaSig: 1 });
|
|
42
48
|
var KNOWN_COUNTERSIGN_OPTS = intrinsic.assign(intrinsic.create(null), {
|
|
43
49
|
signerIndex: 1, countersignatureOf: 1, signingTime: 1, certificates: 1, pem: 1,
|
|
44
|
-
signedAttributes: 1, additionalSignedAttributes: 1, sid: 1,
|
|
50
|
+
signedAttributes: 1, additionalSignedAttributes: 1, sid: 1, algorithmProtection: 1,
|
|
45
51
|
});
|
|
46
52
|
var NS = pkix.makeNS("cms", CmsError, oid);
|
|
47
53
|
var _b = pkiBuild.makeBuilder({
|
|
@@ -113,6 +119,12 @@ var UNSIGNED_FORBIDDEN = intrinsic.create(null);
|
|
|
113
119
|
UNSIGNED_FORBIDDEN[O("contentType")] = "content-type";
|
|
114
120
|
UNSIGNED_FORBIDDEN[O("messageDigest")] = "message-digest";
|
|
115
121
|
UNSIGNED_FORBIDDEN[O("signingTime")] = "signing-time";
|
|
122
|
+
/** @internal RFC 6211 sec. 2: the algorithm protection attribute "MUST NOT be an unsigned
|
|
123
|
+
* attribute", and sec. 5 adds that one placed there "does not provide any additional security".
|
|
124
|
+
* Both identifiers, since the two spell one attribute. The parser refuses the placement, so without
|
|
125
|
+
* the door here this verb would emit a message its own verifier rejects. */
|
|
126
|
+
UNSIGNED_FORBIDDEN[O("cmsAlgorithmProtection")] = "algorithm protection";
|
|
127
|
+
UNSIGNED_FORBIDDEN[O("cmsAlgorithmProtect")] = "algorithm protection";
|
|
116
128
|
|
|
117
129
|
function _buildUnsignedAttrs(list) {
|
|
118
130
|
if (list == null) return null;
|
|
@@ -136,6 +148,49 @@ function _buildUnsignedAttrs(list) {
|
|
|
136
148
|
return wire;
|
|
137
149
|
}
|
|
138
150
|
|
|
151
|
+
/** @internal The RFC 6211 attribute built from the very TLVs the structure's own fields are built
|
|
152
|
+
* from. Sec. 2 says the attribute "contains a copy of the SignerInfo.digestAlgorithm field ...
|
|
153
|
+
* including any parameters associated with it", so the copy is made from the value in hand rather
|
|
154
|
+
* than re-derived from a registry row: a re-derivation can differ from the field it claims to copy,
|
|
155
|
+
* and the parameters are the substitution target the attribute exists to pin.
|
|
156
|
+
*
|
|
157
|
+
* `armTag` is 1 for a `SignerInfo` signature algorithm and 2 for an `AuthenticatedData` MAC
|
|
158
|
+
* algorithm, which sec. 2 makes exclusive to their own placements. `attrOid` is the identifier the
|
|
159
|
+
* caller's option resolved to, taken at the door so the pair carries the one that was asked for. */
|
|
160
|
+
function _algorithmProtectionPair(attrOid, digestAlgId, armAlgId, armTag) {
|
|
161
|
+
return { type: attrOid, values: [b.sequence([digestAlgId, b.implicit(armTag, armAlgId)])] };
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
/** @internal An asked-for protection that cannot be placed is a refusal rather than a message that
|
|
165
|
+
* silently lacks it. The attribute rides in the signed attributes, so with none there is nowhere to
|
|
166
|
+
* put it, which is why RFC 8933 sec. 4.1 conditions its SHOULD on their presence. A caller who
|
|
167
|
+
* names both options is asking for two things that cannot both hold.
|
|
168
|
+
*
|
|
169
|
+
* The option is resolved FIRST, on every route, and the placement question asked second. Resolving
|
|
170
|
+
* it inside the placement test would only read it on the route that cannot place it: a streamed
|
|
171
|
+
* content is consumed and hashed before the attributes are built, so a misspelled value would be
|
|
172
|
+
* refused after the stream had been read, which for a stream that cannot be replayed is a refusal
|
|
173
|
+
* the caller cannot act on, and for one that never ends is no refusal at all.
|
|
174
|
+
*
|
|
175
|
+
* The emitter below reads the same option again, which is sound because the options door refuses
|
|
176
|
+
* one supplied through an accessor, own or inherited, before any value is read: two reads of a
|
|
177
|
+
* plain field are one answer. */
|
|
178
|
+
function _assertProtectionPlaceable(opts) {
|
|
179
|
+
var wanted = cms.algorithmProtectionOid(opts.algorithmProtection, _err);
|
|
180
|
+
if (opts.signedAttributes === false && wanted) {
|
|
181
|
+
throw _err("cms/bad-input", "algorithmProtection needs signed attributes to ride in, and " +
|
|
182
|
+
"signedAttributes: false signs the content directly (RFC 6211 sec. 2 places the attribute in " +
|
|
183
|
+
"SignerInfo.signedAttrs); drop one of the two");
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
/** @internal The producer door, applied to this verb's attribute records. The policy itself lives in
|
|
188
|
+
* `schema-cms`, so the signing verbs and the authenticate verb enforce one rule rather than three
|
|
189
|
+
* copies of it. */
|
|
190
|
+
function _assertAlgorithmProtectionPairs(pairs, digestAlgId, armAlgId, armTag) {
|
|
191
|
+
cms.assertAlgorithmProtectionEmission(pairs, b.sequence([digestAlgId, b.implicit(armTag, armAlgId)]), _err);
|
|
192
|
+
}
|
|
193
|
+
|
|
139
194
|
function _resolveSignerContext(signer, opts) {
|
|
140
195
|
var so = signer || {};
|
|
141
196
|
var keyOnly = so.cert == null && so.spki != null;
|
|
@@ -161,7 +216,13 @@ async function _finishSignerInfo(rc, md, content, eContentType, opts) {
|
|
|
161
216
|
{ type: O("messageDigest"), values: [b.octetString(md)] },
|
|
162
217
|
];
|
|
163
218
|
if (opts.signingTime !== false) intrinsic.push(pairs, { type: O("signingTime"), values: [_timeValue(opts.signingTime)] });
|
|
219
|
+
var apOid = cms.algorithmProtectionOid(opts.algorithmProtection, _err);
|
|
220
|
+
if (apOid) {
|
|
221
|
+
intrinsic.push(pairs, _algorithmProtectionPair(apOid,
|
|
222
|
+
rc.scheme.digestAlgId, rc.scheme.sigAlgId, 1));
|
|
223
|
+
}
|
|
164
224
|
pairs = intrinsic.concat(pairs, _resolveAttrPairs(opts.additionalSignedAttributes, "a signed attribute value"));
|
|
225
|
+
_assertAlgorithmProtectionPairs(pairs, rc.scheme.digestAlgId, rc.scheme.sigAlgId, 1);
|
|
165
226
|
toSign = _buildSignedAttrs(pairs);
|
|
166
227
|
}
|
|
167
228
|
var signedBytes = toSign.setOf ? toSign.setOf : toSign;
|
|
@@ -285,6 +346,7 @@ function _signDispatch(content, signers, opts) {
|
|
|
285
346
|
async function _signStream(stream, signers, opts) {
|
|
286
347
|
opts = guard.identifier.optionsObject(opts, _err, "cms/bad-input", "pki.cms.sign options");
|
|
287
348
|
guard.identifier.assertKnownKeys(opts, KNOWN_SIGN_OPTS, _err, "cms/bad-input", "unknown opts field ");
|
|
349
|
+
_assertProtectionPlaceable(opts);
|
|
288
350
|
if (opts.detached !== true) {
|
|
289
351
|
throw _err("cms/bad-input", "a streaming (async-iterable) content requires opts.detached: true; an attached " +
|
|
290
352
|
"SignedData embeds the content as a definite-length OCTET STRING, which cannot be produced without buffering it");
|
|
@@ -324,6 +386,7 @@ async function _signStream(stream, signers, opts) {
|
|
|
324
386
|
function _sign(content, signers, opts) {
|
|
325
387
|
opts = guard.identifier.optionsObject(opts, _err, "cms/bad-input", "pki.cms.sign options");
|
|
326
388
|
guard.identifier.assertKnownKeys(opts, KNOWN_SIGN_OPTS, _err, "cms/bad-input", "unknown opts field ");
|
|
389
|
+
_assertProtectionPlaceable(opts);
|
|
327
390
|
var contentBuf = _toBuf(content, "content");
|
|
328
391
|
var list = Array.isArray(signers) ? _b.reqDenseArray(signers, "the signer list") : [signers];
|
|
329
392
|
if (!list.length) throw _err("cms/bad-input", "pki.cms.sign requires at least one signer");
|
|
@@ -460,9 +523,15 @@ function _buildCountersignature(targetSigOctets, countersigner, opts) {
|
|
|
460
523
|
return _digest(scheme.digest, targetSigOctets).then(function (md) {
|
|
461
524
|
var pairs = [{ type: O("messageDigest"), values: [b.octetString(md)] }];
|
|
462
525
|
if (opts.signingTime !== false) pairs.push({ type: O("signingTime"), values: [_timeValue(opts.signingTime)] });
|
|
526
|
+
var csApOid = cms.algorithmProtectionOid(opts.algorithmProtection, _err);
|
|
527
|
+
if (csApOid) {
|
|
528
|
+
intrinsic.push(pairs, _algorithmProtectionPair(csApOid, scheme.digestAlgId, scheme.sigAlgId, 1));
|
|
529
|
+
}
|
|
463
530
|
var extra = _resolveAttrPairs(opts.additionalSignedAttributes, "a countersignature signed attribute value");
|
|
464
531
|
extra.forEach(function (p) { if (p.type === O("contentType")) throw _err("cms/bad-input", "a countersignature must not carry a content-type attribute (RFC 5652 sec. 11.4)"); });
|
|
465
|
-
|
|
532
|
+
var all = intrinsic.concat(pairs, extra);
|
|
533
|
+
_assertAlgorithmProtectionPairs(all, scheme.digestAlgId, scheme.sigAlgId, 1);
|
|
534
|
+
return _buildSignedAttrs(all);
|
|
466
535
|
});
|
|
467
536
|
}).then(function (attrs) {
|
|
468
537
|
var preimage = attrs ? attrs.setOf : targetSigOctets;
|
|
@@ -554,6 +623,7 @@ function countersign(cmsInput, signers, opts) {
|
|
|
554
623
|
function _countersign(cmsInput, signers, opts) {
|
|
555
624
|
opts = guard.identifier.optionsObject(opts, _err, "cms/bad-input", "pki.cms.countersign options");
|
|
556
625
|
guard.identifier.assertKnownKeys(opts, KNOWN_COUNTERSIGN_OPTS, _err, "cms/bad-input", "unknown opts field ");
|
|
626
|
+
_assertProtectionPlaceable(opts);
|
|
557
627
|
var list = Array.isArray(signers) ? signers : [signers];
|
|
558
628
|
if (!list.length) throw _err("cms/bad-input", "pki.cms.countersign requires at least one countersigner");
|
|
559
629
|
if (opts.signingTime != null && opts.signingTime !== false) guard.time.assertEncodable(opts.signingTime, _err, "cms/bad-input", "signingTime");
|
|
@@ -628,8 +698,257 @@ function _countersign(cmsInput, signers, opts) {
|
|
|
628
698
|
});
|
|
629
699
|
}
|
|
630
700
|
|
|
701
|
+
/** @internal One Attribute appended to a SignerInfo's `[1] unsignedAttrs`, with every attribute
|
|
702
|
+
* already there kept byte for byte and the SET OF re-sorted. A NEW attribute member rather than a
|
|
703
|
+
* value merged into an existing one of the same type: ETSI EN 319 122-1 clause 5.3 reads "The
|
|
704
|
+
* signature-time-stamp attribute shall contain exactly one component of AttributeValue type", while
|
|
705
|
+
* RFC 5126 clause 6.1.1 has "Several instances of this attribute may occur with an electronic
|
|
706
|
+
* signature, from different TSAs", so n timestamps are n attribute instances each carrying a
|
|
707
|
+
* one-element SET. That is the opposite of the countersignature above, where RFC 5652 clause 11.4
|
|
708
|
+
* puts n signatures in one attribute's values, which is why the two do not share this helper.
|
|
709
|
+
*
|
|
710
|
+
* `build.set` sorts with the same comparison the parser's `derSetOrder` check applies, so the
|
|
711
|
+
* rebuilt SET OF cannot be in an order the reader refuses. The `0x31` to `0xA1` re-tag is sound
|
|
712
|
+
* because both identifiers are a single octet, leaving the length encoding untouched. */
|
|
713
|
+
function _appendUnsignedAttr(siNode, attrDer) {
|
|
714
|
+
var kids = siNode.children;
|
|
715
|
+
var last = kids[kids.length - 1];
|
|
716
|
+
var hasUa = last.tagClass === "context" && last.tagNumber === 1;
|
|
717
|
+
var base = intrinsic.map((hasUa ? intrinsic.arraySlice(kids, 0, kids.length - 1) : kids), function (k) { return k.bytes; });
|
|
718
|
+
var existing = hasUa ? intrinsic.map(last.children, function (a) { return a.bytes; }) : [];
|
|
719
|
+
var setOf = b.set(intrinsic.concat(existing, [attrDer]));
|
|
720
|
+
var wire = intrinsic.bufferFrom(setOf); wire[0] = 0xA1;
|
|
721
|
+
intrinsic.push(base, wire);
|
|
722
|
+
return b.sequence(base);
|
|
723
|
+
}
|
|
724
|
+
|
|
725
|
+
/** @internal The SignerInfo node a verb is targeting, with the index checked against what the message
|
|
726
|
+
* carries rather than against a caller's assertion. */
|
|
727
|
+
function _timestampTarget(der, signerIndex, who) {
|
|
728
|
+
var parsed = cms.parse(der);
|
|
729
|
+
if (!intrinsic.isArray(parsed.signerInfos)) throw _err("cms/bad-input", who + " input is not a CMS SignedData");
|
|
730
|
+
var idx = signerIndex == null ? 0 : signerIndex;
|
|
731
|
+
if (typeof idx !== "number" || !intrinsic.isInteger(idx) || idx < 0 || idx >= parsed.signerInfos.length) {
|
|
732
|
+
throw _err("cms/bad-input", who + " signerIndex out of range: " + guard.text.showValue(signerIndex) +
|
|
733
|
+
" (the message carries " + parsed.signerInfos.length + " signer" + (parsed.signerInfos.length === 1 ? "" : "s") + ")");
|
|
734
|
+
}
|
|
735
|
+
var root = asn1.decode(der);
|
|
736
|
+
var sd = root.children[1].children[0];
|
|
737
|
+
var siSet = sd.children[sd.children.length - 1];
|
|
738
|
+
return { parsed: parsed, root: root, sd: sd, siSet: siSet, idx: idx, siNode: siSet.children[idx] };
|
|
739
|
+
}
|
|
740
|
+
|
|
741
|
+
var KNOWN_IMPRINT_OPTS = intrinsic.assign(intrinsic.create(null), { signerIndex: 1, digestAlgorithm: 1 });
|
|
742
|
+
|
|
743
|
+
/**
|
|
744
|
+
* @primitive pki.cms.timestampImprint
|
|
745
|
+
* @signature pki.cms.timestampImprint(input, opts?) -> Promise<{ imprint, signerIndex }>
|
|
746
|
+
* @since 0.8.52
|
|
747
|
+
* @status stable
|
|
748
|
+
* @spec ETSI EN 319 122-1 clause 5.3, RFC 5126 clause 6.1.1, RFC 3161 clause 2.4.1
|
|
749
|
+
* @related pki.cms.attachTimestamp, pki.tsp.request, pki.tsp.verify, pki.cms.verify
|
|
750
|
+
*
|
|
751
|
+
* The message imprint a time-stamping authority is asked to sign over, for the CAdES
|
|
752
|
+
* signature-time-stamp attribute. EN 319 122-1 clause 5.3 fixes what it covers: "the hash value of
|
|
753
|
+
* the signature field (without the ASN.1 tag and length) within SignerInfo for which the
|
|
754
|
+
* signature-time-stamp attribute is created". So this is a digest of the contents octets of
|
|
755
|
+
* `SignerInfo.signature`, with the tag and length excluded, which is the same octet rule RFC 5652
|
|
756
|
+
* clause 11.4 states for a countersignature.
|
|
757
|
+
*
|
|
758
|
+
* `input` is the SignedData as a DER `Buffer` or a PEM `CMS` string, the same input
|
|
759
|
+
* `pki.cms.countersign` takes and for the same reason: the imprint covers exact octets of the
|
|
760
|
+
* message as it stands, so this reads the wire bytes rather than a representation of them. The
|
|
761
|
+
* result is `{ imprint, signerIndex }`: `imprint` is `{ hashAlgorithm, hashedMessage }`, the
|
|
762
|
+
* argument `pki.tsp.request` and `pki.tsp.sign` take, so a third-party authority can be driven
|
|
763
|
+
* without this package signing anything, and `signerIndex` is the signer it was taken for, which is
|
|
764
|
+
* what `pki.cms.attachTimestamp` is given when the token comes back.
|
|
765
|
+
*
|
|
766
|
+
* @opts signerIndex which signer to take the imprint for (default 0).
|
|
767
|
+
* @opts digestAlgorithm the digest to take (default `"sha256"`). MD5 is refused: EN 319 122-1
|
|
768
|
+
* clause 6.2.1 reads "In addition, MD5 algorithm shall not be used as digest
|
|
769
|
+
* algorithm".
|
|
770
|
+
* @example
|
|
771
|
+
* var pair = await pki.key.generate("Ed25519");
|
|
772
|
+
* var key = await pki.key.export(pair.privateKey);
|
|
773
|
+
* var cert = await pki.x509.sign({ subject: "Signer", subjectPublicKey: await pki.key.export(pair.publicKey),
|
|
774
|
+
* notBefore: new Date("2026-01-01T00:00:00Z"), notAfter: new Date("2036-01-01T00:00:00Z") }, { key: key });
|
|
775
|
+
* var signedDer = await pki.cms.sign(Buffer.from("hello"), { cert: cert, key: key });
|
|
776
|
+
* var taken = await pki.cms.timestampImprint(signedDer);
|
|
777
|
+
* var req = pki.tsp.request(taken.imprint, { certReq: true });
|
|
778
|
+
* taken.imprint.hashAlgorithm; // "sha256"
|
|
779
|
+
*/
|
|
780
|
+
function timestampImprint(input, opts) {
|
|
781
|
+
return guard.bytes.fixedCall(CmsError, "cms/bad-input", [
|
|
782
|
+
[input, "the CMS message"], [opts, "pki.cms.timestampImprint options"],
|
|
783
|
+
], _timestampImprint);
|
|
784
|
+
}
|
|
785
|
+
|
|
786
|
+
function _timestampImprint(input, opts) {
|
|
787
|
+
opts = guard.identifier.optionsObject(opts, _err, "cms/bad-input", "pki.cms.timestampImprint options");
|
|
788
|
+
guard.identifier.assertKnownKeys(opts, KNOWN_IMPRINT_OPTS, _err, "cms/bad-input", "unknown opts field ");
|
|
789
|
+
var name = opts.digestAlgorithm == null ? "sha256" : opts.digestAlgorithm;
|
|
790
|
+
/** @internal MD5 is answered before the build's own digest table, which does not carry it either:
|
|
791
|
+
* asked for by name, it gets the clause that forbids it rather than the generic "this build does
|
|
792
|
+
* not have that digest", which is a different thing to tell an operator. */
|
|
793
|
+
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)");
|
|
794
|
+
/** @internal Admitted against the digests a timestamp VERIFIER reads an imprint under, not the
|
|
795
|
+
* build's general digest table: a digest outside that set produces an imprint whose token every
|
|
796
|
+
* verifier here refuses, so taking one would hand an operator a request for an unusable token. */
|
|
797
|
+
if (typeof name !== "string" || !intrinsic.hasOwn(constants.NAMES.TIMESTAMP_IMPRINT_DIGESTS, name)) {
|
|
798
|
+
throw _err("cms/bad-input", "digestAlgorithm must name a digest a signature timestamp is read " +
|
|
799
|
+
"under (sha256, sha384 or sha512), got " + guard.text.showValue(opts.digestAlgorithm));
|
|
800
|
+
}
|
|
801
|
+
/** @internal A weak digest is refused outright here, with no opt-in beside it. `attachTimestamp`
|
|
802
|
+
* has one because a token that already exists may be archived under a digest that was current
|
|
803
|
+
* when it was made; a token this imprint would be sent for does not exist yet, so the only thing
|
|
804
|
+
* the option could buy is a request for a token this verb's own sibling then refuses. */
|
|
805
|
+
if (intrinsic.hasOwn(constants.NAMES.WEAK_DIGESTS, name)) {
|
|
806
|
+
throw _err("cms/bad-input", "the imprint digest " + name + " is one a signature timestamp is " +
|
|
807
|
+
"refused for, so a token taken over it could not be attached or verified");
|
|
808
|
+
}
|
|
809
|
+
var der = pkix.coerceToDer(input, { pemLabel: null, PemError: frameworkError.PemError, ErrorClass: CmsError, prefix: "cms" });
|
|
810
|
+
var t = _timestampTarget(der, opts.signerIndex, "pki.cms.timestampImprint");
|
|
811
|
+
/** @internal `imprint` is nested rather than spread across the result, so the object this returns
|
|
812
|
+
* is the one `pki.tsp.request` and `pki.tsp.sign` take: their message-imprint door admits
|
|
813
|
+
* `hashAlgorithm` and `hashedMessage` and refuses any other field, which a flat result carrying
|
|
814
|
+
* the signer index would trip. The index is what `pki.cms.attachTimestamp` is then given, so it
|
|
815
|
+
* is reported beside the imprint instead of inside it. */
|
|
816
|
+
return guard.verdict.of({
|
|
817
|
+
imprint: guard.verdict.of({
|
|
818
|
+
hashAlgorithm: name,
|
|
819
|
+
hashedMessage: guard.crypto.digest(name, _signatureOctets(t.siNode)),
|
|
820
|
+
}),
|
|
821
|
+
signerIndex: t.idx,
|
|
822
|
+
});
|
|
823
|
+
}
|
|
824
|
+
|
|
825
|
+
var KNOWN_ATTACH_OPTS = intrinsic.assign(intrinsic.create(null), { signerIndex: 1, pem: 1, allowWeakDigests: 1 });
|
|
826
|
+
|
|
827
|
+
/**
|
|
828
|
+
* @primitive pki.cms.attachTimestamp
|
|
829
|
+
* @signature pki.cms.attachTimestamp(input, token, opts?) -> Promise<Buffer>
|
|
830
|
+
* @since 0.8.52
|
|
831
|
+
* @status stable
|
|
832
|
+
* @spec ETSI EN 319 122-1 clause 5.3, RFC 5126 clause 6.1.1, RFC 3161
|
|
833
|
+
* @related pki.cms.timestampImprint, pki.cms.verify, pki.tsp.sign, pki.cms.countersign
|
|
834
|
+
*
|
|
835
|
+
* Attach an RFC 3161 `TimeStampToken` to a signature as the CAdES signature-time-stamp unsigned
|
|
836
|
+
* attribute `{ 1 2 840 113549 1 9 16 2 14 }`, taking a `SignedData` to the EN 319 122-1 B-T level.
|
|
837
|
+
* `input` is that SignedData as a DER `Buffer` or a PEM `CMS` string and `token` the token a
|
|
838
|
+
* time-stamping authority returned for the imprint `pki.cms.timestampImprint` produced, as DER or
|
|
839
|
+
* PEM. Both are read as wire bytes rather than as a parsed representation, because this verb
|
|
840
|
+
* rewrites the message's own octets.
|
|
841
|
+
*
|
|
842
|
+
* It SPLICES rather than re-signs. Re-signing produces a different signature value, so a token taken
|
|
843
|
+
* over the first one would no longer match it, which is measured and not reasoned: with ECDSA the
|
|
844
|
+
* second signature differs every time. What is preserved byte for byte is therefore everything the
|
|
845
|
+
* four signature inputs cover: the encapsulated content and its type, and per signer the `version`,
|
|
846
|
+
* `sid`, `digestAlgorithm`, `[0] signedAttrs`, `signatureAlgorithm` and `signature` TLVs, plus every
|
|
847
|
+
* embedded certificate, every revocation entry, and every attribute already in `unsignedAttrs`. What
|
|
848
|
+
* is re-encoded is the targeted signer's `[1] unsignedAttrs` SET OF and the five enclosing lengths,
|
|
849
|
+
* none of which appears in any signature input.
|
|
850
|
+
*
|
|
851
|
+
* Several instances may be attached, one per authority, which is what RFC 5126 clause 6.1.1 permits
|
|
852
|
+
* and EN 319 122-1 clause 5.3 shapes: each is its own attribute carrying exactly one value.
|
|
853
|
+
*
|
|
854
|
+
* `signerIndex` names a signer of the INPUT. `signerInfos` is a SET OF, whose members are emitted in
|
|
855
|
+
* DER order, so giving one member an attribute changes its encoding and can change its position:
|
|
856
|
+
* identify a signer of the OUTPUT by its signature value or its certificate, not by the index it
|
|
857
|
+
* held on the way in.
|
|
858
|
+
*
|
|
859
|
+
* The imprint is checked before anything is written, so this verb cannot produce a message its own
|
|
860
|
+
* verifier would reject: the token's imprint must be the hash of the targeted signer's signature
|
|
861
|
+
* value, and its digest must be one the verifier admits. Nothing else about the token is judged
|
|
862
|
+
* here; `pki.cms.verify` does that.
|
|
863
|
+
*
|
|
864
|
+
* @opts signerIndex which signer to attach to (default 0).
|
|
865
|
+
* @opts pem return a PEM `CMS` block rather than DER.
|
|
866
|
+
* @opts allowWeakDigests attach a token whose imprint is a weak digest, which both this verb and
|
|
867
|
+
* `pki.cms.verify` refuse by default. This is the archived-token path, and the
|
|
868
|
+
* same option is needed again at verification.
|
|
869
|
+
* @example
|
|
870
|
+
* var pair = await pki.key.generate("Ed25519");
|
|
871
|
+
* var key = await pki.key.export(pair.privateKey);
|
|
872
|
+
* var spki = await pki.key.export(pair.publicKey);
|
|
873
|
+
* var nb = new Date("2026-01-01T00:00:00Z"), na = new Date("2036-01-01T00:00:00Z");
|
|
874
|
+
* var cert = await pki.x509.sign({ subject: "Signer", subjectPublicKey: spki, notBefore: nb, notAfter: na }, { key: key });
|
|
875
|
+
* // RFC 3161 sec. 2.3: a TSA certificate's extendedKeyUsage MUST be critical and name timeStamping alone
|
|
876
|
+
* var tsaCert = await pki.x509.sign({ subject: "Example TSA", subjectPublicKey: spki, notBefore: nb, notAfter: na,
|
|
877
|
+
* extensions: { keyUsage: ["digitalSignature"], extendedKeyUsage: ["timeStamping"], extendedKeyUsageCritical: true } },
|
|
878
|
+
* { key: key });
|
|
879
|
+
* var signedDer = await pki.cms.sign(Buffer.from("hello"), { cert: cert, key: key });
|
|
880
|
+
* var taken = await pki.cms.timestampImprint(signedDer);
|
|
881
|
+
* var token = await pki.tsp.sign(taken.imprint, { cert: tsaCert, key: key },
|
|
882
|
+
* { policy: "1.3.6.1.4.1.1", serialNumber: 1 });
|
|
883
|
+
* var bT = await pki.cms.attachTimestamp(signedDer, token, { signerIndex: taken.signerIndex });
|
|
884
|
+
* (await pki.cms.verify(bT, { certs: [cert] })).signers[0].signatureTimeStamps[0].valid; // true
|
|
885
|
+
*/
|
|
886
|
+
function attachTimestamp(input, token, opts) {
|
|
887
|
+
return guard.async.deferred(function () {
|
|
888
|
+
return guard.bytes.fixedCall(CmsError, "cms/bad-input", [
|
|
889
|
+
[input, "the CMS message"], [token, "the time-stamp token"], [opts, "pki.cms.attachTimestamp options"],
|
|
890
|
+
], _attachTimestamp);
|
|
891
|
+
});
|
|
892
|
+
}
|
|
893
|
+
|
|
894
|
+
function _attachTimestamp(input, token, opts) {
|
|
895
|
+
opts = guard.identifier.optionsObject(opts, _err, "cms/bad-input", "pki.cms.attachTimestamp options");
|
|
896
|
+
guard.identifier.assertKnownKeys(opts, KNOWN_ATTACH_OPTS, _err, "cms/bad-input", "unknown opts field ");
|
|
897
|
+
var der = pkix.coerceToDer(input, { pemLabel: null, PemError: frameworkError.PemError, ErrorClass: CmsError, prefix: "cms" });
|
|
898
|
+
var tokenDer = pkix.coerceToDer(token, { pemLabel: null, PemError: frameworkError.PemError, ErrorClass: CmsError, prefix: "cms" });
|
|
899
|
+
var t = _timestampTarget(der, opts.signerIndex, "pki.cms.attachTimestamp");
|
|
900
|
+
|
|
901
|
+
/** @internal The imprint is checked HERE, before a byte is written, against the signature this
|
|
902
|
+
* attribute will hang on. A verb that wrote first would produce a message `pki.cms.verify` refuses
|
|
903
|
+
* on the row it just created, and the caller would have no way to tell a mis-addressed token from
|
|
904
|
+
* a corrupt one. The token's own structure, its authority's certificate and its chain are not
|
|
905
|
+
* judged here: those are the verifier's, and judging them twice in two places is two answers. */
|
|
906
|
+
var tst = tsp.parseToken(tokenDer);
|
|
907
|
+
var want = tst.tstInfo.messageImprint;
|
|
908
|
+
var name = want.hashAlgorithm && want.hashAlgorithm.name;
|
|
909
|
+
if (!name || !intrinsic.hasOwn(constants.NAMES.TIMESTAMP_IMPRINT_DIGESTS, name)) {
|
|
910
|
+
throw _err("cms/bad-input", "the token's message imprint names a digest a signature timestamp " +
|
|
911
|
+
"is not read under: " + guard.text.showValue(name || (want.hashAlgorithm && want.hashAlgorithm.oid)));
|
|
912
|
+
}
|
|
913
|
+
/** @internal The identifier is held to its own encoding as well as to its name: a digest
|
|
914
|
+
* AlgorithmIdentifier carries absent or DER NULL parameters (RFC 5754 clause 2), and the ANY
|
|
915
|
+
* field is carried through the parse unvalidated, so one naming sha256 beside arbitrary bytes
|
|
916
|
+
* would otherwise be hashed as a well-formed imprint. */
|
|
917
|
+
if (!guard.der.paramsAbsentOrNull(want.hashAlgorithm.parameters)) {
|
|
918
|
+
throw _err("cms/bad-input", "the token's message imprint algorithm identifier carries " +
|
|
919
|
+
"parameters other than DER NULL, which a digest identifier does not (RFC 5754 clause 2)");
|
|
920
|
+
}
|
|
921
|
+
/** @internal The same digest posture the verifier holds this attribute to, read from the one
|
|
922
|
+
* membership list: a token whose imprint is a weak digest is refused here too, under the option
|
|
923
|
+
* that admits it there, so the verb does not write an attribute whose row `pki.cms.verify` then
|
|
924
|
+
* refuses by default. The imprint carries the whole binding between the token and the signature,
|
|
925
|
+
* and a collision in it substitutes one signature under a genuine token. */
|
|
926
|
+
if (opts.allowWeakDigests !== true && intrinsic.hasOwn(constants.NAMES.WEAK_DIGESTS, name)) {
|
|
927
|
+
throw _err("cms/weak-timestamp-imprint", "the token's message imprint is a " + name + " digest, " +
|
|
928
|
+
"which a signature timestamp is refused for by default; pass allowWeakDigests: true to attach " +
|
|
929
|
+
"an archived token under it");
|
|
930
|
+
}
|
|
931
|
+
var have = guard.crypto.digest(name, _signatureOctets(t.siNode));
|
|
932
|
+
if (!guard.crypto.constantTimeEqual(have, want.hashedMessage)) {
|
|
933
|
+
throw _err("cms/timestamp-imprint-mismatch", "the token's message imprint is not the hash of this " +
|
|
934
|
+
"signer's signature value, so attaching it would produce a signature timestamp that does not " +
|
|
935
|
+
"verify (ETSI EN 319 122-1 clause 5.3)");
|
|
936
|
+
}
|
|
937
|
+
|
|
938
|
+
var attr = b.sequence([b.oid(O("timeStampToken")), b.set([tokenDer])]);
|
|
939
|
+
var newSiSet = b.set(intrinsic.map(t.siSet.children, function (siNode, i) {
|
|
940
|
+
return i === t.idx ? _appendUnsignedAttr(siNode, attr) : siNode.bytes;
|
|
941
|
+
}));
|
|
942
|
+
var sdKids = t.sd.children;
|
|
943
|
+
var newSdFields = intrinsic.map(intrinsic.arraySlice(sdKids, 0, sdKids.length - 1), function (k) { return k.bytes; });
|
|
944
|
+
intrinsic.push(newSdFields, newSiSet);
|
|
945
|
+
var out = b.sequence([t.root.children[0].bytes, b.explicit(0, b.sequence(newSdFields))]);
|
|
946
|
+
return opts.pem ? pkix.pemEncode(out, "CMS", frameworkError.PemError) : out;
|
|
947
|
+
}
|
|
948
|
+
|
|
631
949
|
module.exports = {
|
|
632
950
|
sign: sign, countersign: countersign, certsOnly: certsOnly,
|
|
951
|
+
timestampImprint: timestampImprint, attachTimestamp: attachTimestamp,
|
|
633
952
|
// @internal
|
|
634
953
|
KNOWN_SIGNER_CERT_KEYS: KNOWN_SIGNER_CERT_KEYS,
|
|
635
954
|
KNOWN_SIGNER_KEY_ONLY_KEYS: KNOWN_SIGNER_KEY_ONLY_KEYS,
|