@blamejs/pki 0.5.31 → 0.5.33
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 +25 -1
- package/README.md +3 -3
- package/lib/acme.js +347 -90
- package/lib/asn1-der.js +93 -33
- package/lib/cmc-build.js +19 -8
- package/lib/cmc-verify.js +4 -4
- package/lib/cmp-build.js +36 -6
- package/lib/cmp-session.js +5 -5
- package/lib/cmp-verify.js +86 -16
- package/lib/cms-compress.js +2 -2
- package/lib/cms-decrypt.js +3 -3
- package/lib/cms-encrypt.js +1 -1
- package/lib/cms-sign.js +214 -93
- package/lib/cms-verify.js +363 -237
- package/lib/crl-sign.js +4 -4
- package/lib/crmf-sign.js +2 -1
- package/lib/csr-sign.js +1 -4
- package/lib/ct.js +8 -4
- package/lib/est.js +234 -34
- package/lib/framework-error.js +20 -6
- package/lib/guard-bytes.js +56 -4
- package/lib/guard-intrinsic.js +13 -0
- package/lib/http-digest.js +69 -16
- package/lib/http-retry-after.js +68 -24
- package/lib/http-transport.js +60 -6
- package/lib/inspect.js +13 -2
- package/lib/key.js +2 -2
- package/lib/lint.js +1 -1
- package/lib/mime.js +27 -4
- package/lib/ocsp.js +7 -7
- package/lib/oid.js +16 -0
- package/lib/path-validate.js +76 -18
- package/lib/pbes2.js +2 -2
- package/lib/pkcs12-build.js +2 -2
- package/lib/pki-build.js +6 -6
- package/lib/rfc3339.js +75 -17
- package/lib/schema-c509.js +36 -9
- package/lib/schema-cmc.js +3 -3
- package/lib/schema-pkix.js +174 -25
- package/lib/schema-tsp.js +10 -1
- package/lib/sign-scheme.js +1 -1
- package/lib/sigstore.js +52 -9
- package/lib/smime.js +86 -6
- package/lib/trust.js +114 -14
- package/lib/tsp-sign.js +39 -27
- package/lib/validator-tpm.js +2 -2
- package/lib/webauthn-mds.js +24 -6
- package/lib/webauthn.js +3 -3
- package/lib/webcrypto.js +80 -1
- package/lib/x509-sign.js +1 -1
- package/package.json +2 -2
- package/sbom.cdx.json +6 -6
package/lib/cms-decrypt.js
CHANGED
|
@@ -129,7 +129,7 @@ function _parse(input) {
|
|
|
129
129
|
return schemaCms.parse(_toDer(input));
|
|
130
130
|
}
|
|
131
131
|
function _toDer(input) {
|
|
132
|
-
if (
|
|
132
|
+
if (guard.bytes.isByteSource(input)) return guard.bytes.snapshotSource(input, CmsError, "cms/bad-input", "input");
|
|
133
133
|
if (typeof input === "string") { try { return schemaCms.pemDecode(input); } catch (e) { throw _err("cms/bad-input", "the CMS PEM could not be decoded", e); } }
|
|
134
134
|
throw _err("cms/bad-input", "input must be a DER Buffer, Uint8Array, or PEM string");
|
|
135
135
|
}
|
|
@@ -166,7 +166,7 @@ function _riKindForKey(km) {
|
|
|
166
166
|
throw _err("cms/bad-input", "key material needs { key, cert }, { password }, { kek }, or { cek }");
|
|
167
167
|
}
|
|
168
168
|
function _assertSupported(ri, km) {
|
|
169
|
-
if (ri.type === "kari" && ri.keyEncryptionAlgorithm &&
|
|
169
|
+
if (ri.type === "kari" && ri.keyEncryptionAlgorithm && intrinsic.stringIndexOf(intrinsic.toLowerCase(ri.keyEncryptionAlgorithm.name || ""), "mqv") !== -1) throw _err("cms/unsupported-algorithm", "ECMQV kari is not supported"); // /mqv/i (ASCII name)
|
|
170
170
|
if (ri.type === "ori") {
|
|
171
171
|
if (ri.oriType !== O("kem")) throw _err("cms/unsupported-recipient-type", "unsupported OtherRecipientInfo type " + ri.oriType);
|
|
172
172
|
if (ri.kemri && ri.kemri.kem && ri.kemri.kem.oid === O("id-kem-rsa")) throw _err("cms/unsupported-algorithm", "RSA-KEM is not supported");
|
|
@@ -801,7 +801,7 @@ function _releaseKeyDer(k) {
|
|
|
801
801
|
if (k && k.owned) guard.secret.zeroize(k.der, CmsError, "cms/bad-input", "the recipient private-key copy");
|
|
802
802
|
}
|
|
803
803
|
function _normCertDer(cert) {
|
|
804
|
-
if (
|
|
804
|
+
if (guard.bytes.isByteSource(cert)) return guard.bytes.snapshotSource(cert, CmsError, "cms/bad-input", "the recipient certificate");
|
|
805
805
|
if (typeof cert === "string") { try { return x509.pemDecode(cert); } catch (e) { throw _err("cms/bad-input", "the recipient certificate PEM could not be decoded", e); } }
|
|
806
806
|
throw _err("cms/bad-input", "the recipient certificate must be a DER Buffer or PEM string");
|
|
807
807
|
}
|
package/lib/cms-encrypt.js
CHANGED
|
@@ -41,7 +41,7 @@ function _algId(name, shape) { return shape === "null" ? b.sequence([b.oid(O(nam
|
|
|
41
41
|
// A certificate descriptor -> raw DER (the recipient cert is parsed for dispatch + rid; the caller
|
|
42
42
|
// supplies bytes, not a re-encoded parse).
|
|
43
43
|
function _normCertDer(cert, what) {
|
|
44
|
-
if (
|
|
44
|
+
if (guard.bytes.isByteSource(cert)) return guard.bytes.snapshotSource(cert, CmsError, "cms/bad-input", what || "a certificate");
|
|
45
45
|
if (typeof cert === "string") { try { return x509.pemDecode(cert); } catch (e) { throw _err("cms/bad-input", (what || "a certificate") + " PEM could not be decoded", e); } }
|
|
46
46
|
throw _err("cms/bad-input", (what || "a certificate") + " must be a DER Buffer, Uint8Array, or PEM string");
|
|
47
47
|
}
|
package/lib/cms-sign.js
CHANGED
|
@@ -117,13 +117,13 @@ function _buildSid(cert, useSki) {
|
|
|
117
117
|
// signature octets + signing-time, content-type omitted per sec. 11.4).
|
|
118
118
|
function _buildSignedAttrs(pairs) {
|
|
119
119
|
var seenTypes = {};
|
|
120
|
-
var attrs =
|
|
120
|
+
var attrs = intrinsic.map(pairs, function (p) {
|
|
121
121
|
if (intrinsic.hasOwn(seenTypes, p.type)) throw _err("cms/bad-input", "signedAttrs must not repeat an attribute type (RFC 5652 sec. 5.3): " + p.type);
|
|
122
122
|
seenTypes[p.type] = 1;
|
|
123
123
|
return b.sequence([b.oid(p.type), b.set(p.values)]);
|
|
124
124
|
});
|
|
125
125
|
var setOf = b.set(attrs); // SET OF (tag 0x31) -- the exact bytes the signature covers (sec. 5.4)
|
|
126
|
-
var wire =
|
|
126
|
+
var wire = intrinsic.bufferFrom(setOf); wire[0] = 0xA0; // the on-wire [0] IMPLICIT tag
|
|
127
127
|
return { setOf: setOf, wire: wire };
|
|
128
128
|
}
|
|
129
129
|
|
|
@@ -131,10 +131,10 @@ function _buildSignedAttrs(pairs) {
|
|
|
131
131
|
// into the pair shape _buildSignedAttrs consumes; each MUST carry >= 1 value (Attribute values is
|
|
132
132
|
// SET SIZE (1..MAX)).
|
|
133
133
|
function _resolveAttrPairs(list, what) {
|
|
134
|
-
return (list || []
|
|
135
|
-
var vals = (a.values || []
|
|
134
|
+
return intrinsic.map(list || [], function (a) {
|
|
135
|
+
var vals = intrinsic.map(a.values || [], function (v) { return _toBuf(v, what); });
|
|
136
136
|
if (!vals.length) throw _err("cms/bad-input", "a signed attribute must carry at least one value (RFC 5652 -- Attribute values is SET SIZE (1..MAX))");
|
|
137
|
-
return { type:
|
|
137
|
+
return { type: oid.isDottedDecimal(a.type) ? a.type : O(a.type), values: vals };
|
|
138
138
|
});
|
|
139
139
|
}
|
|
140
140
|
|
|
@@ -151,24 +151,26 @@ UNSIGNED_FORBIDDEN[O("signingTime")] = "signing-time";
|
|
|
151
151
|
// duplicate type (RFC 5652 sec. 11 / sec. 5.3) is a config-time cms/bad-input.
|
|
152
152
|
function _buildUnsignedAttrs(list) {
|
|
153
153
|
if (list == null) return null;
|
|
154
|
-
if (!
|
|
154
|
+
if (!intrinsic.isArray(list)) throw _err("cms/bad-input", "opts.unsignedAttributes must be an array of { type, values }");
|
|
155
155
|
if (!list.length) return null;
|
|
156
156
|
var pairs = _resolveAttrPairs(list, "an unsigned attribute value");
|
|
157
157
|
var seen = {};
|
|
158
|
-
|
|
158
|
+
intrinsic.forEach(pairs, function (p) {
|
|
159
159
|
if (intrinsic.hasOwn(UNSIGNED_FORBIDDEN, p.type)) throw _err("cms/bad-input", "the " + UNSIGNED_FORBIDDEN[p.type] + " attribute must not appear as an unsigned attribute (RFC 5652 sec. 11)");
|
|
160
160
|
if (intrinsic.hasOwn(seen, p.type)) throw _err("cms/bad-input", "unsignedAttrs must not repeat an attribute type (RFC 5652 sec. 5.3): " + p.type);
|
|
161
161
|
seen[p.type] = 1;
|
|
162
162
|
});
|
|
163
|
-
var setOf = b.set(
|
|
164
|
-
var wire =
|
|
163
|
+
var setOf = b.set(intrinsic.map(pairs, function (p) { return b.sequence([b.oid(p.type), b.set(p.values)]); }));
|
|
164
|
+
var wire = intrinsic.bufferFrom(setOf); wire[0] = 0xA1; // SET OF -> [1] IMPLICIT UnsignedAttributes
|
|
165
165
|
return wire;
|
|
166
166
|
}
|
|
167
167
|
|
|
168
|
-
//
|
|
169
|
-
//
|
|
170
|
-
//
|
|
171
|
-
|
|
168
|
+
// Resolve, at entry and once, every field an emitted SignerInfo depends on (RFC 5652 sec. 5.3):
|
|
169
|
+
// the SignerIdentifier + version, the signature scheme, the private key reference, the declared
|
|
170
|
+
// public bytes, and the signer certificate DER. Nothing here defers, so the values are captured
|
|
171
|
+
// before any promise turn; the digest, the signature, and the self-check happen a turn later in
|
|
172
|
+
// _finishSignerInfo, off exactly these captured values. Buffered and streamed signing share this.
|
|
173
|
+
function _resolveSignerContext(signer, opts) {
|
|
172
174
|
var so = signer || {};
|
|
173
175
|
// A key-only signer: `{ key, spki, keyIdentifier }` with no certificate. RFC
|
|
174
176
|
// 5272 sec. 3.2 requires exactly this when a Full PKI Request is signed with the
|
|
@@ -199,36 +201,54 @@ function _buildSignerInfo(signer, content, eContentType, opts) {
|
|
|
199
201
|
var sidv = keyOnly
|
|
200
202
|
? { sid: b.contextPrimitive(0, _keyOnlyKeyId(so)), version: 3 } // [0] IMPLICIT SubjectKeyIdentifier
|
|
201
203
|
: _buildSid(cert, opts.sid === "ski");
|
|
202
|
-
|
|
204
|
+
return { keyOnly: keyOnly, soKey: soKey, soSpki: soSpki, certDer: certDer, cert: cert,
|
|
205
|
+
scheme: scheme, sid: sidv.sid, version: sidv.version };
|
|
206
|
+
}
|
|
203
207
|
|
|
204
|
-
|
|
205
|
-
|
|
208
|
+
// Finish a SignerInfo (RFC 5652 sec. 5.3) from a resolved context: build the SignedAttributes (or
|
|
209
|
+
// sign the content directly), sign them, self-check the signature against the declared key, and
|
|
210
|
+
// assemble the SignerInfo. `md` is the message digest of the content under `rc.scheme.digest`,
|
|
211
|
+
// already computed -- over a buffered content by _digest, or over a streamed content's chunks by the
|
|
212
|
+
// engine's single-pass digest. `content` is read ONLY on the signedAttributes:false path, where the
|
|
213
|
+
// signature covers the content directly and there is no digest attribute.
|
|
214
|
+
async function _finishSignerInfo(rc, md, content, eContentType, opts) {
|
|
215
|
+
// await throughout, never a live Promise.resolve()/.then lookup: on the streamed path this runs after
|
|
216
|
+
// the content's iterator has driven the digest, so a replaced global Promise.resolve or a replaced
|
|
217
|
+
// Promise.prototype.then would otherwise let it disrupt the signing that completes here. await hooks
|
|
218
|
+
// onto a native promise through the internal job, not the caller-replaceable prototype method.
|
|
219
|
+
var toSign;
|
|
220
|
+
if (opts.signedAttributes === false) {
|
|
221
|
+
toSign = content; // sign the content directly (no signed attributes)
|
|
222
|
+
} else {
|
|
206
223
|
// Signed attributes (RFC 5652 sec. 5.3): content-type == eContentType, message-digest ==
|
|
207
224
|
// digest(content), and (by default) signing-time, plus any caller-supplied attribute.
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
225
|
+
var pairs = [
|
|
226
|
+
{ type: O("contentType"), values: [b.oid(eContentType)] },
|
|
227
|
+
{ type: O("messageDigest"), values: [b.octetString(md)] },
|
|
228
|
+
];
|
|
229
|
+
if (opts.signingTime !== false) intrinsic.push(pairs, { type: O("signingTime"), values: [_timeValue(opts.signingTime)] });
|
|
230
|
+
pairs = intrinsic.concat(pairs, _resolveAttrPairs(opts.additionalSignedAttributes, "a signed attribute value"));
|
|
231
|
+
toSign = _buildSignedAttrs(pairs);
|
|
232
|
+
}
|
|
233
|
+
var signedBytes = toSign.setOf ? toSign.setOf : toSign; // SET-OF form for signing (sec. 5.4)
|
|
234
|
+
var sig = await signScheme.signOverTbs(rc.scheme, rc.soKey, signedBytes, _signE);
|
|
235
|
+
await _assertKeyMatchesSpki(rc.keyOnly, rc.soKey, rc.soSpki, rc.scheme, sig, signedBytes, rc.cert);
|
|
236
|
+
var fields = [b.integer(BigInt(rc.version)), rc.sid, rc.scheme.digestAlgId];
|
|
237
|
+
if (toSign.wire) intrinsic.push(fields, toSign.wire); // [0] IMPLICIT signedAttrs
|
|
238
|
+
intrinsic.push(fields, rc.scheme.sigAlgId, b.octetString(sig));
|
|
239
|
+
var ua = _buildUnsignedAttrs(opts.unsignedAttributes);
|
|
240
|
+
if (ua) intrinsic.push(fields, ua); // [1] IMPLICIT unsignedAttrs
|
|
241
|
+
// certDer is null for a key-only signer, and the certificates [0] embedding below filters it out --
|
|
242
|
+
// there is nothing to embed, which is the point.
|
|
243
|
+
return { si: b.sequence(fields), digestAlgId: rc.scheme.digestAlgId, version: rc.version, certDer: rc.certDer };
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
// Build one SignerInfo (RFC 5652 sec. 5.3) over a buffered content and sign it: resolve the context,
|
|
247
|
+
// digest the content (unless it is signed directly), then finish.
|
|
248
|
+
function _buildSignerInfo(signer, content, eContentType, opts) {
|
|
249
|
+
var rc = _resolveSignerContext(signer, opts);
|
|
250
|
+
var mdP = opts.signedAttributes === false ? Promise.resolve(null) : _digest(rc.scheme.digest, content);
|
|
251
|
+
return mdP.then(function (md) { return _finishSignerInfo(rc, md, content, eContentType, opts); });
|
|
232
252
|
}
|
|
233
253
|
|
|
234
254
|
|
|
@@ -250,7 +270,7 @@ function _buildSignerInfo(signer, content, eContentType, opts) {
|
|
|
250
270
|
// This is the same post-sign self-check every other signer in the toolkit runs,
|
|
251
271
|
// through the same shared helper. It costs one verification per SignerInfo, which
|
|
252
272
|
// is what a certificate, a CRL, a CSR and a CMP message have always paid here.
|
|
253
|
-
function _assertKeyMatchesSpki(keyOnly, soKey, soSpki, scheme, sig, signedBytes, cert) {
|
|
273
|
+
async function _assertKeyMatchesSpki(keyOnly, soKey, soSpki, scheme, sig, signedBytes, cert) {
|
|
254
274
|
var declared = keyOnly ? soSpki : (cert && cert.subjectPublicKeyInfo && cert.subjectPublicKeyInfo.bytes);
|
|
255
275
|
// A parsed certificate that does not retain its SPKI bytes cannot be checked this
|
|
256
276
|
// way. That is not a shape x509.parse produces, and if it ever were, refusing is
|
|
@@ -260,20 +280,26 @@ function _assertKeyMatchesSpki(keyOnly, soKey, soSpki, scheme, sig, signedBytes,
|
|
|
260
280
|
"a signer certificate did not surface its subjectPublicKeyInfo, so the signature it produced could " +
|
|
261
281
|
"not be checked against the key the SignerInfo declares");
|
|
262
282
|
}
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
283
|
+
// await, never Promise.resolve()/.then: reached from _finishSignerInfo after a streamed content's
|
|
284
|
+
// iterator ran, so a replaced global Promise.resolve would otherwise disrupt this self-check.
|
|
285
|
+
try {
|
|
286
|
+
await _b.assertSignatureVerifies(signedBytes, sig, declared, scheme);
|
|
287
|
+
} catch (e) {
|
|
288
|
+
if (e && typeof e.code === "string" && e.code.indexOf("cms/") === 0) throw e;
|
|
289
|
+
throw _signE("bad-input",
|
|
290
|
+
"a signer's `key` does not match the public key its SignerInfo declares (" +
|
|
291
|
+
(keyOnly ? "`spki`" : "its certificate") + "): the signature it produced does not verify under that key", e);
|
|
292
|
+
}
|
|
271
293
|
}
|
|
272
294
|
|
|
273
295
|
// A signing-time Time value: UTCTime before 2050, GeneralizedTime from 2050 (RFC 5652 sec. 11.3 /
|
|
274
|
-
// RFC 5280 sec. 4.1.2.5). A caller Date overrides; false omits the attribute (handled above).
|
|
296
|
+
// RFC 5280 sec. 4.1.2.5). A caller Date overrides; false omits the attribute (handled above). The
|
|
297
|
+
// DEFAULT instant is read from the captured Date intrinsic, never the live global: a streamed content's
|
|
298
|
+
// iterator runs during the digest, one turn before this attribute is built, and a replaced global Date
|
|
299
|
+
// would otherwise let it stamp the signature with an attacker-chosen instant (byte-identical, under a
|
|
300
|
+
// deterministic scheme, to signing with that instant explicitly).
|
|
275
301
|
function _timeValue(when) {
|
|
276
|
-
var d = guard.time.isDate(when) ? when : new Date();
|
|
302
|
+
var d = guard.time.isDate(when) ? when : new intrinsic.Date();
|
|
277
303
|
return d.getUTCFullYear() < 2050 ? b.utcTime(d) : b.generalizedTime(d);
|
|
278
304
|
}
|
|
279
305
|
|
|
@@ -351,29 +377,114 @@ function _keyOnlyKeyId(so) {
|
|
|
351
377
|
|
|
352
378
|
function _normCertDer(c) {
|
|
353
379
|
if (c == null) throw _err("cms/bad-input", "each signer requires a certificate (cert)");
|
|
354
|
-
if (
|
|
355
|
-
c = guard.bytes.
|
|
380
|
+
if (guard.bytes.isByteSource(c)) {
|
|
381
|
+
c = guard.bytes.snapshotSource(c, CmsError, "cms/bad-input", "a signer certificate");
|
|
356
382
|
return c[0] === 0x30 ? c : _pemToDer(c.toString("latin1")); // DER as-is, else PEM
|
|
357
383
|
}
|
|
358
384
|
if (typeof c === "string") return _pemToDer(c);
|
|
359
385
|
throw _err("cms/bad-input", "a signer certificate must be a DER Buffer or a PEM string");
|
|
360
386
|
}
|
|
361
387
|
function _pemToDer(text) {
|
|
362
|
-
var
|
|
363
|
-
if (
|
|
364
|
-
return
|
|
388
|
+
var der = pkix.pemDecodeLenient(text, "CERTIFICATE");
|
|
389
|
+
if (der === null) throw _err("cms/bad-input", "a signer certificate PEM is not a CERTIFICATE block");
|
|
390
|
+
return der;
|
|
365
391
|
}
|
|
366
392
|
|
|
367
393
|
// pki.cms.sign -- documented by the @primitive block in cms-verify.js (the @module pki.cms home).
|
|
368
|
-
// Documented `-> Promise`, so a fault leaves as a REJECTION (guard-async)
|
|
369
|
-
//
|
|
394
|
+
// Documented `-> Promise`, so a fault leaves as a REJECTION (guard-async). The stream/buffer routing
|
|
395
|
+
// classifies `content` at the door, which reads `content[Symbol.asyncIterator]` -- a caller accessor
|
|
396
|
+
// that can throw -- so the whole dispatch runs inside the rejection boundary rather than fixedCall's
|
|
397
|
+
// alone, which the classification precedes. The checks the impls run stay synchronous because they
|
|
398
|
+
// read the caller's mutable content and signer list.
|
|
370
399
|
function sign(content, signers, opts) {
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
400
|
+
return guard.async.deferred(function () { return _signDispatch(content, signers, opts); });
|
|
401
|
+
}
|
|
402
|
+
|
|
403
|
+
function _signDispatch(content, signers, opts) {
|
|
404
|
+
// The signer list and options are copied FIRST, before the content is classified. Classifying reads
|
|
405
|
+
// content[Symbol.asyncIterator] -- a caller accessor that runs in-realm -- and a probe run before the
|
|
406
|
+
// copy could mutate the still-uncopied signers/opts, or replace the Array.prototype method the copy
|
|
407
|
+
// uses, and steer the emitted signature. With the copies taken first, the probe cannot reach them.
|
|
374
408
|
return guard.bytes.fixedCall(CmsError, "cms/bad-input", [
|
|
375
|
-
[
|
|
376
|
-
],
|
|
409
|
+
[signers, "the signer list"], [opts, "pki.cms.sign options"],
|
|
410
|
+
], function (copiedSigners, copiedOpts) {
|
|
411
|
+
// A streaming content is classified by guard.bytes.asyncStreamOf, which reads Symbol.asyncIterator a
|
|
412
|
+
// single time and returns a LAZY stream (its underlying iterator is acquired only when the digest
|
|
413
|
+
// engine drives it, so a pre-hash rejection never opens a resource to leak). The stream, consumed
|
|
414
|
+
// exactly once, is passed through; it CANNOT be deep-copied itself. A byte source (or any
|
|
415
|
+
// non-iterable) yields null here and takes the buffered path.
|
|
416
|
+
var stream = guard.bytes.asyncStreamOf(content);
|
|
417
|
+
if (stream) return _signStream(stream, copiedSigners, copiedOpts);
|
|
418
|
+
// Buffered: the content byte source is copied too, so the value that decides the attribute-shaped-
|
|
419
|
+
// content refusal and the value that gets signed are the same read.
|
|
420
|
+
return guard.bytes.fixedCall(CmsError, "cms/bad-input", [
|
|
421
|
+
[content, "content"],
|
|
422
|
+
], function (copiedContent) { return _sign(copiedContent, copiedSigners, copiedOpts); });
|
|
423
|
+
});
|
|
424
|
+
}
|
|
425
|
+
|
|
426
|
+
// Sign a streamed content (an async iterable of byte chunks) as a DETACHED SignedData. The payload
|
|
427
|
+
// is hashed once, incrementally, under every distinct SignerInfo digest algorithm (RFC 5652
|
|
428
|
+
// sec. 5.4), so an arbitrarily large content is never held whole and a single-use stream serves
|
|
429
|
+
// every signer in one pass. Detached is the only conformant streamed form: an attached SignedData
|
|
430
|
+
// embeds the content as a definite-length OCTET STRING, which needs the whole content buffered to
|
|
431
|
+
// state its length, and signed attributes are required (a stream cannot be signed directly, and the
|
|
432
|
+
// message-digest attribute is exactly what the streamed hash produces).
|
|
433
|
+
async function _signStream(stream, signers, opts) {
|
|
434
|
+
opts = guard.identifier.optionsObject(opts, _err, "cms/bad-input", "pki.cms.sign options");
|
|
435
|
+
guard.identifier.assertKnownKeys(opts, KNOWN_SIGN_OPTS, _err, "cms/bad-input", "unknown opts field ");
|
|
436
|
+
if (opts.detached !== true) {
|
|
437
|
+
throw _err("cms/bad-input", "a streaming (async-iterable) content requires opts.detached: true; an attached " +
|
|
438
|
+
"SignedData embeds the content as a definite-length OCTET STRING, which cannot be produced without buffering it");
|
|
439
|
+
}
|
|
440
|
+
if (opts.signedAttributes === false) {
|
|
441
|
+
throw _err("cms/bad-input", "a streaming content cannot be signed without signed attributes: the message-digest " +
|
|
442
|
+
"attribute is the streamed hash, and there is no content to sign directly (RFC 5652 sec. 5.4)");
|
|
443
|
+
}
|
|
444
|
+
var list = intrinsic.isArray(signers) ? signers : [signers];
|
|
445
|
+
if (!list.length) throw _err("cms/bad-input", "pki.cms.sign requires at least one signer");
|
|
446
|
+
var eContentType = opts.eContentType ? O(opts.eContentType) : OID_DATA;
|
|
447
|
+
if (eContentType === OID_PKI_DATA && list.length > 1 &&
|
|
448
|
+
intrinsic.some(list, function (s) { return s && s.cert == null && s.spki != null; })) {
|
|
449
|
+
throw _err("cms/bad-input",
|
|
450
|
+
"a key-only signer must be the ONLY SignerInfo in a Full PKI Request (RFC 5272 sec. 3.2)");
|
|
451
|
+
}
|
|
452
|
+
if (opts.signingTime != null && opts.signingTime !== false) guard.time.assertValid(opts.signingTime, _err, "cms/bad-input", "signingTime");
|
|
453
|
+
// Resolve every signer's context first (no stream consumed yet): this yields each SignerInfo's
|
|
454
|
+
// digest algorithm, so the stream can be hashed once under the distinct set.
|
|
455
|
+
var rcs = intrinsic.map(list, function (s) { return _resolveSignerContext(s, opts); });
|
|
456
|
+
var digestNames = [], seenDigest = {};
|
|
457
|
+
intrinsic.forEach(rcs, function (r) { if (!seenDigest[r.scheme.digest]) { seenDigest[r.scheme.digest] = 1; intrinsic.push(digestNames, r.scheme.digest); } });
|
|
458
|
+
var wcNames = intrinsic.map(digestNames, function (n) { return DIGEST_HASH[n]; });
|
|
459
|
+
// `await`, not `.then`: the streamed content's iterator runs during digestStream and could replace
|
|
460
|
+
// Promise.prototype.then (or Promise.all, whose spec-internal element handling dispatches through it)
|
|
461
|
+
// to drop SignerInfos from the emitted DER or bypass the non-empty-signer check. await and a captured
|
|
462
|
+
// await-each aggregation dispatch through no such caller-replaceable method.
|
|
463
|
+
var digests;
|
|
464
|
+
try {
|
|
465
|
+
digests = await subtle.digestStream(wcNames, stream);
|
|
466
|
+
} catch (e) {
|
|
467
|
+
// The engine reports malformed streamed content in its own domain; report it in this verb's domain
|
|
468
|
+
// instead, exactly as a buffered content's bad bytes are, rather than leaking the engine code. A
|
|
469
|
+
// non-byte chunk is webcrypto/data; a source whose async-iterator protocol is malformed (a
|
|
470
|
+
// non-callable next, or a factory returning a non-iterator) is webcrypto/syntax. The digest
|
|
471
|
+
// algorithm names are this module's own, so webcrypto/syntax here is never an algorithm fault.
|
|
472
|
+
if (e && e.code === "webcrypto/data") throw _err("cms/bad-input", "a streamed content chunk is not a byte source", e);
|
|
473
|
+
if (e && e.code === "webcrypto/syntax") throw _err("cms/bad-input", "the streamed content is not a valid async iterable of byte chunks", e);
|
|
474
|
+
throw e;
|
|
475
|
+
}
|
|
476
|
+
var byName = {};
|
|
477
|
+
intrinsic.forEach(digestNames, function (n, i) { byName[n] = intrinsic.bufferFrom(digests[i]); });
|
|
478
|
+
// Build each SignerInfo sequentially -- START it only as it is awaited -- rather than starting them
|
|
479
|
+
// all and awaiting each: awaiting is the capture-safe combinator (no Promise.all/.then the streamed
|
|
480
|
+
// content could replace), but starting all first would leave a later signer's rejection unobserved
|
|
481
|
+
// if an earlier one rejects, which Node can escalate to an unhandled-rejection crash. Per-signer work
|
|
482
|
+
// is small, so the sequential cost is negligible.
|
|
483
|
+
var built = [];
|
|
484
|
+
for (var _ip = 0; _ip < rcs.length; _ip++) {
|
|
485
|
+
intrinsic.push(built, await _finishSignerInfo(rcs[_ip], byName[rcs[_ip].scheme.digest], null, eContentType, opts));
|
|
486
|
+
}
|
|
487
|
+
return _assembleSignedData(built, eContentType, opts, null);
|
|
377
488
|
}
|
|
378
489
|
|
|
379
490
|
function _sign(content, signers, opts) {
|
|
@@ -422,45 +533,55 @@ function _sign(content, signers, opts) {
|
|
|
422
533
|
// silently-ignored non-Date or an Invalid Date that would encode a garbage Time.
|
|
423
534
|
if (opts.signingTime != null && opts.signingTime !== false) guard.time.assertValid(opts.signingTime, _err, "cms/bad-input", "signingTime");
|
|
424
535
|
|
|
425
|
-
return Promise.all(list.map(function (s) { return _buildSignerInfo(s, contentBuf, eContentType, opts); }))
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
536
|
+
return Promise.all(list.map(function (s) { return _buildSignerInfo(s, contentBuf, eContentType, opts); }))
|
|
537
|
+
.then(function (built) { return _assembleSignedData(built, eContentType, opts, contentBuf); });
|
|
538
|
+
}
|
|
539
|
+
|
|
540
|
+
// Assemble the SignedData ContentInfo (RFC 5652 sec. 5.1) from the built SignerInfos: the deduped
|
|
541
|
+
// digestAlgorithms set, the coupled CMSVersion, the EncapsulatedContentInfo (eContent omitted when
|
|
542
|
+
// detached, the only form a streamed content takes), the deduped certificates [0], and the
|
|
543
|
+
// signerInfos SET OF. `contentBuf` is the content bytes for the attached form, or null when detached.
|
|
544
|
+
function _assembleSignedData(built, eContentType, opts, contentBuf) {
|
|
545
|
+
// digestAlgorithms: the distinct SignerInfo digestAlgorithm AlgorithmIdentifiers, deduped. Captured
|
|
546
|
+
// enumeration throughout: this assembly runs after a streamed content's iterator may have replaced
|
|
547
|
+
// Array.prototype.map / forEach / Buffer.prototype.toString, and a live one could drop SignerInfos
|
|
548
|
+
// from the emitted CMS or mis-dedup the algorithm / certificate sets.
|
|
549
|
+
var seen = {}, digestAlgs = [];
|
|
550
|
+
intrinsic.forEach(built, function (x) { var k = intrinsic.bufToString(x.digestAlgId, "hex"); if (!seen[k]) { seen[k] = 1; intrinsic.push(digestAlgs, x.digestAlgId); } });
|
|
551
|
+
// CMSVersion (RFC 5652 sec. 5.1): 3 if any SignerInfo is v3 (ski) or eContentType != id-data;
|
|
552
|
+
// otherwise 1 (v1 emits only X.509 certificates, so the v4/v5 attribute-certificate cases
|
|
553
|
+
// do not arise).
|
|
554
|
+
var v3 = intrinsic.some(built, function (x) { return x.version === 3; }) || eContentType !== OID_DATA;
|
|
555
|
+
var version = v3 ? 3 : 1;
|
|
556
|
+
// EncapsulatedContentInfo: eContentType + [0] EXPLICIT eContent (omitted when detached).
|
|
557
|
+
var encapFields = [b.oid(eContentType)];
|
|
558
|
+
if (!opts.detached) intrinsic.push(encapFields, b.explicit(0, b.octetString(contentBuf)));
|
|
559
|
+
var encap = b.sequence(encapFields);
|
|
560
|
+
// certificates [0] IMPLICIT SET OF (the signer certs), deduped + SET-OF-ordered, when embedded.
|
|
561
|
+
var sdFields = [b.integer(BigInt(version)), b.set(digestAlgs), encap];
|
|
562
|
+
if (opts.certificates !== false) {
|
|
563
|
+
// A key-only signer contributes no certificate (certDer is null), so the
|
|
564
|
+
// set can end up EMPTY -- and an empty certificates [0] is not the same as
|
|
565
|
+
// an absent one, so the field is omitted entirely in that case.
|
|
566
|
+
var certDers = intrinsic.sort(_dedupe(intrinsic.filter(intrinsic.map(built, function (x) { return x.certDer; }),
|
|
567
|
+
function (d) { return d != null; })), intrinsic.compare); // X.690 sec. 11.6
|
|
568
|
+
if (certDers.length) intrinsic.push(sdFields, b.contextConstructed(0, intrinsic.bufferConcat(certDers))); // [0] IMPLICIT SET OF
|
|
569
|
+
}
|
|
570
|
+
intrinsic.push(sdFields, b.set(intrinsic.map(built, function (x) { return x.si; }))); // signerInfos SET OF
|
|
571
|
+
var signedData = b.sequence(sdFields);
|
|
572
|
+
var contentInfo = b.sequence([b.oid(OID_SIGNED_DATA), b.explicit(0, signedData)]); // ContentInfo
|
|
573
|
+
return opts.pem ? pkix.pemEncode(contentInfo, "CMS", frameworkError.PemError) : contentInfo;
|
|
453
574
|
}
|
|
454
575
|
|
|
455
576
|
// Dedupe certificate DERs (two signers may share a cert, so embed it once).
|
|
456
577
|
function _dedupe(ders) {
|
|
457
578
|
var seen = {}, out = [];
|
|
458
|
-
|
|
579
|
+
intrinsic.forEach(ders, function (d) { var k = intrinsic.bufToString(d, "hex"); if (!seen[k]) { seen[k] = 1; intrinsic.push(out, d); } });
|
|
459
580
|
return out;
|
|
460
581
|
}
|
|
461
582
|
|
|
462
583
|
function _toBuf(v, what) {
|
|
463
|
-
if (
|
|
584
|
+
if (guard.bytes.isByteSource(v)) return guard.bytes.snapshotSource(v, CmsError, "cms/bad-input", what);
|
|
464
585
|
throw _err("cms/bad-input", what + " must be a Buffer");
|
|
465
586
|
}
|
|
466
587
|
|