@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/lib/guard-time.js CHANGED
@@ -19,17 +19,112 @@ var _dateGetUTCMinutes = require("./guard-intrinsic").uncurry(Date.prototype.get
19
19
  var _dateGetUTCSeconds = require("./guard-intrinsic").uncurry(Date.prototype.getUTCSeconds);
20
20
  var _dateGetUTCMilliseconds = require("./guard-intrinsic").uncurry(Date.prototype.getUTCMilliseconds);
21
21
  var _dateSetUTCFullYear = require("./guard-intrinsic").uncurry(Date.prototype.setUTCFullYear);
22
+ var _dateToISOString = require("./guard-intrinsic").uncurry(Date.prototype.toISOString);
23
+ var _charCodeAt = require("./guard-intrinsic").uncurry(String.prototype.charCodeAt);
24
+ var _Number = require("./guard-intrinsic").Number;
25
+ var constants = require("./constants");
26
+ /** @internal RFC 3161 clause 2.4.2 states an Accuracy's third field in microseconds, which no
27
+ * `C.TIME` scale names because no PKI structure carries a microsecond duration anywhere else. */
28
+ var MICROS_PER_MILLISECOND = 1000;
22
29
  var _dateUTC = Date.UTC;
23
30
  var _floor = Math.floor;
24
31
  var _ceil = Math.ceil;
25
32
  /** @internal A DER GeneralizedTime carries a four-digit year and a UTCTime a narrower one, so a Date
26
33
  * outside 0000..9999 has no PKI encoding. */
27
34
  var DER_YEAR_MIN = 0, DER_YEAR_MAX = 9999;
35
+ /** @internal The widest accuracy a date comparison can use, the DER year range in seconds, and the
36
+ * instant a wider one saturates to: the last instant a DER time can carry, which is past every
37
+ * certificate date and, unlike an arbitrary ceiling, is a `Date` a verdict can render. Declared
38
+ * after the range it derives from, since a `var` read above its own initializer is `undefined`. */
39
+ var MAX_ACCURACY_SECONDS = constants.TIME.days((DER_YEAR_MAX + 1) * 366) / constants.TIME.seconds(1);
40
+ var MAX_INSTANT = _dateUTC(DER_YEAR_MAX, 11, 31, 23, 59, 59, 999);
28
41
 
29
42
  // @enforced-by guard-shape-reinlined
30
43
  // @guard-shape \.getTime\s*\(
31
44
  function instantOf(value) { return _dateGetTime(value); }
32
45
 
46
+ /** The instant an error message or a verdict names, rendered through the operation captured at load.
47
+ * A replaced `toISOString` chooses the time a refusal reports, which is what an operator reads when
48
+ * deciding whether the refusal was right, so the rendering is taken from the same place every other
49
+ * read of a Date is.
50
+ *
51
+ * @enforced-by behavioral -- the live-read budget each module declares is what holds a call-time
52
+ * `toISOString` out of a converted file; a `@guard-shape` here would name the read in every module
53
+ * that renders a date and the budget already answers for those.
54
+ */
55
+ function isoOf(value) { return _dateToISOString(value); }
56
+
57
+ /** The instant a DER GeneralizedTime names, rounded UP to the next whole millisecond where its
58
+ * fractional seconds carry a digit a `Date` cannot hold. `fraction` is the fraction's digit string;
59
+ * the first three digits ARE the milliseconds, so a nonzero digit after them puts the real instant
60
+ * strictly between `value` and `value + 1ms`.
61
+ *
62
+ * An upper bound is decided against this rather than against the Date, because the Date truncates:
63
+ * a time stamped at `...000000.000001Z` reads as `...000000.000Z`, which compares equal to a bound
64
+ * of `...000000Z` and so passes an "after the bound" test it is on the wrong side of. The two
65
+ * consumers are the timestamp verifier's own check of the authority's certificate window and the
66
+ * signature-timestamp row's check of the signing certificate's expiry.
67
+ *
68
+ * @enforced-by behavioral -- a truncating comparison has no rename-proof shape; the RED vector is a
69
+ * token minted with a sub-millisecond fraction one tick past a certificate's notAfter.
70
+ */
71
+ function ceilInstantOf(value, fraction) { return latestInstantOf(value, fraction, null); }
72
+
73
+ /** @internal The part of a DER GeneralizedTime's fractional seconds a `Date` cannot hold, in whole
74
+ * microseconds. The first three digits ARE the milliseconds; the next three are microseconds, and
75
+ * anything finer than a microsecond counts as one, since a bound is only a bound if it is not
76
+ * exceeded. Read digit by digit through the captured accessor: a fraction is caller bytes. */
77
+ function _fractionMicros(fraction) {
78
+ if (typeof fraction !== "string" || fraction.length <= 3) return 0;
79
+ var micros = 0;
80
+ var finer = false;
81
+ for (var i = 3; i < fraction.length; i++) {
82
+ var c = _charCodeAt(fraction, i);
83
+ if (c < 48 || c > 57) break;
84
+ var digit = c - 48;
85
+ if (i === 3) micros += digit * 100;
86
+ else if (i === 4) micros += digit * 10;
87
+ else if (i === 5) micros += digit;
88
+ else if (digit > 0) finer = true;
89
+ }
90
+ return finer ? micros + 1 : micros;
91
+ }
92
+
93
+ /** The LATEST instant a timestamp could have been created at: its `genTime` ceiling plus the
94
+ * accuracy the token states. RFC 3161 clause 2.4.2 defines accuracy as the bound on the difference
95
+ * between the stated time and the real one, so a token saying it is accurate to two seconds places
96
+ * its creation anywhere in a four-second window, and an upper bound that reads only `genTime`
97
+ * concludes something the token does not establish. `accuracy` is the parsed `{ seconds, millis,
98
+ * micros }` or null; `seconds` arrives as a BigInt, the other two as numbers, and a micros
99
+ * remainder rounds up because a bound is only a bound if it is not exceeded.
100
+ *
101
+ * @enforced-by behavioral -- a bound that ignores a field has no rename-proof shape; the RED vector
102
+ * is a token whose genTime precedes a certificate's notAfter while its accuracy window does not.
103
+ */
104
+ function latestInstantOf(value, fraction, accuracy) {
105
+ var at = instantOf(value);
106
+ var ms = 0;
107
+ /** @internal The sub-millisecond parts are summed and rounded ONCE. Rounding the genTime fraction
108
+ * and the accuracy up separately overstates the bound by a millisecond when both carry one, which
109
+ * refuses a token whose real window ends before the instant it is compared against. */
110
+ var micros = _fractionMicros(fraction);
111
+ if (accuracy !== null && typeof accuracy === "object") {
112
+ /** @internal An Accuracy's seconds field is an arbitrary-size INTEGER, so it is bounded BEFORE
113
+ * it is scaled: a token claiming more than the DER time range can hold is saturated to the
114
+ * widest instant rather than scaled into a value the scale helper refuses. The refusal would
115
+ * escape the row this builds and take the whole verify call down, where what the token has
116
+ * established is nothing about which side of any certificate date it falls on, which is the
117
+ * answer a saturated bound gives. */
118
+ if (accuracy.seconds > MAX_ACCURACY_SECONDS) return MAX_INSTANT;
119
+ if (typeof accuracy.seconds === "bigint") ms += constants.TIME.seconds(_Number(accuracy.seconds));
120
+ else if (typeof accuracy.seconds === "number") ms += constants.TIME.seconds(accuracy.seconds);
121
+ if (typeof accuracy.millis === "number") ms += constants.TIME.milliseconds(accuracy.millis);
122
+ if (typeof accuracy.micros === "number") micros += accuracy.micros;
123
+ }
124
+ if (micros > 0) ms += _ceil(micros / MICROS_PER_MILLISECOND);
125
+ return ms > 0 ? at + ms : at;
126
+ }
127
+
33
128
  // @enforced-by nan-date-comparison-unguarded
34
129
  function assertValid(value, E, code, label) {
35
130
  if (!util.types.isDate(value) || _isNaN(instantOf(value))) {
@@ -120,6 +215,9 @@ module.exports = _freeze({
120
215
  assertEncodable: assertEncodable,
121
216
  addMonths: addMonths,
122
217
  instantOf: instantOf,
218
+ isoOf: isoOf,
219
+ ceilInstantOf: ceilInstantOf,
220
+ latestInstantOf: latestInstantOf,
123
221
  isDate: isDate,
124
222
  toDate: toDate,
125
223
  within: within,
package/lib/schema-all.js CHANGED
@@ -379,7 +379,7 @@ module.exports = {
379
379
  pemDecode: trustanchor.pemDecode, pemEncode: trustanchor.pemEncode },
380
380
  attrcert: { parse: attrcert.parse, pemDecode: attrcert.pemDecode, pemEncode: attrcert.pemEncode,
381
381
  decodeExtensions: attrcert.decodeExtensions, decodeExtension: attrcert.decodeExtension },
382
- smime: { parseSigningCertificate: smime.parseSigningCertificate, parseSigningCertificateV2: smime.parseSigningCertificateV2, parseSmimeCapabilities: smime.parseSmimeCapabilities, decodeAttribute: smime.decodeAttribute },
382
+ smime: { buildSigningCertificateV2: smime.buildSigningCertificateV2, parseSigningCertificate: smime.parseSigningCertificate, parseSigningCertificateV2: smime.parseSigningCertificateV2, parseSmimeCapabilities: smime.parseSmimeCapabilities, decodeAttribute: smime.decodeAttribute },
383
383
  cmc: { parse: cmc.parse, parsePkiData: cmc.parsePkiData, parsePkiResponse: cmc.parsePkiResponse },
384
384
  pem: {
385
385
  decodeBundle: function (text, opts) { return pkix.pemDecodeBundle(text, opts, PemError, _detectOrNull); },
@@ -22,6 +22,9 @@
22
22
  * (raw), the hash algorithm (decoded for v2, or the implied SHA-1 for v1), and
23
23
  * the optional `issuerSerial` (issuer `GeneralNames` validated + surfaced raw,
24
24
  * serial as a BigInt + hex)) plus the optional certificate policies.
25
+ * `buildSigningCertificateV2` encodes the v2 attribute value a signer attaches:
26
+ * one `ESSCertIDv2` per certificate, the SHA-256 form with the DEFAULT algorithm
27
+ * omitted and SHA-384 / SHA-512 with an absent parameters field.
25
28
  * `parseSmimeCapabilities` decodes the ordered `SMIMECapabilities` list (each a
26
29
  * capability OID + raw parameters). `decodeAttribute` takes a CMS-shaped
27
30
  * `{ type, values }` attribute, enforces the single-`AttributeValue` rule
@@ -46,9 +49,14 @@ var schema = require("./schema-engine");
46
49
  var pkix = require("./schema-pkix");
47
50
  var oid = require("./oid");
48
51
  var frameworkError = require("./framework-error");
52
+ var guard = require("./guard-all");
53
+ var intrinsic = require("./guard-intrinsic");
54
+ var constants = require("./constants");
55
+ var x509 = require("./schema-x509");
49
56
 
50
57
  var SmimeError = frameworkError.SmimeError;
51
58
  var PemError = frameworkError.PemError;
59
+ var b = asn1.build;
52
60
 
53
61
  var NS = pkix.makeNS("smime", SmimeError, oid);
54
62
  var TAGS = asn1.TAGS;
@@ -61,6 +69,12 @@ var OID_SIGNING_CERTIFICATE = oid.byName("signingCertificate");
61
69
  var OID_SIGNING_CERTIFICATE_V2 = oid.byName("signingCertificateV2");
62
70
  var OID_SMIME_CAPABILITIES = oid.byName("smimeCapabilities");
63
71
 
72
+ /** @internal The digests this module will BIND a certificate with. RFC 5035 sec. 4 leaves
73
+ * `hashAlgorithm` open, and the two readers that consume the field disagree on the set they admit,
74
+ * so the builder emits only what the strictest consumer accepts: SHA-1 is excluded because a
75
+ * binding is an identity claim, and an identifier a verifier refuses is a token nobody can use. */
76
+ var BIND_DIGESTS = intrinsic.assign(intrinsic.create(null), { sha256: 1, sha384: 1, sha512: 1 });
77
+
64
78
  var GENERAL_NAME_DIRECTORY = 4;
65
79
  var ISSUER_SERIAL = schema.seq([
66
80
  schema.field("issuer", pkix.generalNames(NS, { code: "smime/bad-general-names" })),
@@ -71,7 +85,7 @@ var ISSUER_SERIAL = schema.seq([
71
85
  return {
72
86
  issuer: m.fields.issuer.value.result,
73
87
  serialNumber: m.fields.serialNumber.value,
74
- serialNumberHex: m.fields.serialNumber.node.content.toString("hex"),
88
+ serialNumberHex: intrinsic.bufToString(m.fields.serialNumber.node.content, "hex"),
75
89
  };
76
90
  },
77
91
  });
@@ -105,7 +119,7 @@ var POLICY_INFORMATION = schema.seq([
105
119
  });
106
120
  var POLICIES = schema.seqOf(POLICY_INFORMATION, {
107
121
  assert: "sequence", min: 0, code: "smime/bad-policies", what: "policies",
108
- build: function (m) { return m.items.map(function (it) { return it.value.result; }); },
122
+ build: function (m) { return intrinsic.map(m.items, function (it) { return it.value.result; }); },
109
123
  });
110
124
 
111
125
  var ESS_CERT_ID = schema.seq([
@@ -155,7 +169,7 @@ function signingCertificateSchema(essCertId, code, what) {
155
169
  ], {
156
170
  assert: "sequence", code: code, what: what,
157
171
  build: function (m, ctx) {
158
- var certs = m.fields.certs.value.items.map(function (it) { return it.value.result; });
172
+ var certs = intrinsic.map(m.fields.certs.value.items, function (it) { return it.value.result; });
159
173
  assertSignerIssuerIsDirectoryName(certs, function (msg) { throw ctx.E("smime/bad-issuer-serial", msg); });
160
174
  return {
161
175
  certs: certs,
@@ -182,7 +196,7 @@ var SMIME_CAPABILITY = schema.seq([
182
196
  });
183
197
  var SMIME_CAPABILITIES = schema.seqOf(SMIME_CAPABILITY, {
184
198
  assert: "sequence", min: 0, code: "smime/bad-capabilities", what: "SMIMECapabilities",
185
- build: function (m) { return { capabilities: m.items.map(function (it) { return it.value.result; }) }; },
199
+ build: function (m) { return { capabilities: intrinsic.map(m.items, function (it) { return it.value.result; }) }; },
186
200
  });
187
201
 
188
202
  /**
@@ -266,6 +280,197 @@ var parseSigningCertificateV2 = pkix.makeParser({ pemLabel: null, PemError: PemE
266
280
  */
267
281
  var parseSmimeCapabilities = pkix.makeParser({ pemLabel: null, PemError: PemError, ErrorClass: SmimeError, prefix: "smime", what: "SMIMECapabilities", topSchema: SMIME_CAPABILITIES, ns: NS });
268
282
 
283
+ function _err(code, message) { return new SmimeError(code, message); }
284
+
285
+ /** @internal The digests a signing-certificate identifier may BIND a certificate with, on the
286
+ * reading side. It is the set the builder emits, and it is narrower than the set the ESS decoder
287
+ * will decode: an identifier is an identity claim, so admitting a digest nobody should bind with
288
+ * would let the claim be satisfied by a certificate other than the one that signed. SHA-1 is not
289
+ * here and is reported as weak by its own flag, because the v1 attribute has no algorithm field
290
+ * and is SHA-1 by definition (RFC 2634), so a reader must be able to tell "weak" from "unknown". */
291
+ var BIND_VERIFY_DIGESTS = intrinsic.assign(intrinsic.create(null), { sha256: 1, sha384: 1, sha512: 1 });
292
+ var GENERAL_NAME = pkix.generalName(NS, { decodeValue: true, code: "smime/bad-general-names" });
293
+
294
+ /** @internal What ONE signing-certificate attribute claims about `certDer`, as facts rather than a
295
+ * verdict. Each claim is read and compared here so there is one implementation of "does this
296
+ * identifier name that certificate", and the two consumers apply their own policy to the result:
297
+ * `pki.tsp.verify` refuses a token, and `pki.cms.verify` reports a CAdES conformance finding.
298
+ *
299
+ * An ESSCertID states the signer in two independent ways, and both are compared: the hash under
300
+ * the identifier's own digest, and the issuer and serial where it carries them (RFC 5035 clause 5,
301
+ * RFC 2634 clause 5.4). A hash that agrees beside an issuer and serial naming another certificate
302
+ * is a contradiction, not a match. Identifiers after the first are not read: RFC 2634 clause 5.4
303
+ * makes the first the signing certificate and leaves the rest as the chain. */
304
+ function _bindingClaim(attr) {
305
+ var out = { form: attr.type === OID_SIGNING_CERTIFICATE_V2 ? "v2" : "v1", readable: false,
306
+ digestName: null, supported: false, weak: false, certHash: null, issuerSerial: null };
307
+ var decoded;
308
+ try { decoded = decodeAttribute(attr); }
309
+ catch (_e) { return out; }
310
+ var ess = decoded.certs && decoded.certs[0];
311
+ if (!ess) return out;
312
+ /** @internal The digest identifier is held to its own encoding before its NAME is read. A SHA-2
313
+ * identifier carries absent or NULL parameters and nothing else (RFC 5754 clause 2, "MUST accept
314
+ * SHA2 AlgorithmIdentifiers with absent parameters ... with NULL parameters"), and the same two
315
+ * forms are what RFC 3370 clause 2.1 states for SHA-1. The ANY parameters field is carried
316
+ * through the parse unvalidated, so an identifier naming a digest this build computes while
317
+ * carrying arbitrary bytes beside it would otherwise be hashed as a well-formed binding. */
318
+ if (!guard.der.paramsAbsentOrNull(ess.hashAlgorithm == null ? null : ess.hashAlgorithm.parameters)) return out;
319
+ out.readable = true;
320
+ out.digestName = ess.hashAlgorithm == null ? "sha1" : ess.hashAlgorithm.name;
321
+ out.weak = intrinsic.hasOwn(constants.NAMES.WEAK_DIGESTS, out.digestName);
322
+ out.supported = out.weak || intrinsic.hasOwn(BIND_VERIFY_DIGESTS, out.digestName);
323
+ out.certHash = ess.certHash;
324
+ out.issuerSerial = ess.issuerSerial || null;
325
+ return out;
326
+ }
327
+
328
+ /** @internal One claim compared against one certificate. The claim is read ONCE, by
329
+ * `certBindingClaims`, and this is what runs per certificate: a signer identifier can match many
330
+ * candidates, and re-reading the attribute for each would multiply the attribute's size by the
331
+ * candidate count before any signature is checked. Only the first identifier is ever compared, so
332
+ * only it is kept.
333
+ *
334
+ * @internal
335
+ */
336
+ function certBindingAgainst(claim, certDer) {
337
+ var out = { form: claim.form, readable: claim.readable, digestName: claim.digestName,
338
+ supported: claim.supported, weak: claim.weak, matches: null, certUnreadable: false,
339
+ issuerSerialPresent: claim.issuerSerial !== null, issuerSerialMatches: null };
340
+ if (!claim.readable || !claim.supported) return out;
341
+ /** @internal Through the captured digest and the constant-time comparison: this IS the binding
342
+ * between a signature and the certificate its attribute names, so neither what the digest covers
343
+ * nor how the result is compared is the caller's to choose. */
344
+ out.matches = guard.crypto.constantTimeEqual(guard.crypto.digest(claim.digestName, certDer), claim.certHash);
345
+ if (claim.issuerSerial === null) return out;
346
+ /** @internal A certificate that does not parse is its own fact, kept apart from an attribute that
347
+ * does not decode: one says the identifier is malformed and the other says the thing it would be
348
+ * compared against is, and a consumer orders the two itself. Folding them into one flag moved a
349
+ * refusal from "this identifier names another certificate" to "this attribute is unreadable". */
350
+ var cert;
351
+ try { cert = x509.parse(certDer); }
352
+ catch (_e2) { out.certUnreadable = true; return out; }
353
+ if (claim.issuerSerial.serialNumber !== cert.serialNumber) { out.issuerSerialMatches = false; return out; }
354
+ var names = claim.issuerSerial.issuer.names || [];
355
+ var gn = null;
356
+ try {
357
+ if (names.length === 1) {
358
+ gn = schema.embeddedDer(GENERAL_NAME, names[0].bytes, NS,
359
+ { code: "smime/bad-general-names", what: "GeneralName" });
360
+ }
361
+ } catch (_e3) { gn = null; }
362
+ out.issuerSerialMatches = !!gn && gn.tagNumber === GENERAL_NAME_DIRECTORY && !!gn.value &&
363
+ guard.name.dnEqual(gn.value.rdns, cert.issuer.rdns, _err, "smime/bad-issuer-serial",
364
+ "the ESSCertID issuer");
365
+ return out;
366
+ }
367
+
368
+ /** @internal Every signing-certificate attribute in `signedAttrs`, read against `certDer`.
369
+ * `chosen` is the v2 attribute where there is one, which supersedes v1 (RFC 5035); `superseded`
370
+ * holds the rest, which a consumer still holds to the same claim, because one structure making two
371
+ * incompatible statements about one fact is a contradiction rather than something to resolve in
372
+ * favor of whichever attribute a reader prefers. EVERY attribute is collected rather than the first
373
+ * of each type: `signedAttrs` is a DER SET OF, so which of two comes first is a function of their
374
+ * encodings and a hostile producer picks it.
375
+ *
376
+ * @internal
377
+ */
378
+ function certBindingClaims(signedAttrs) {
379
+ var found = [];
380
+ intrinsic.forEach(signedAttrs || [], function (a) {
381
+ if (a && (a.type === OID_SIGNING_CERTIFICATE || a.type === OID_SIGNING_CERTIFICATE_V2)) {
382
+ guard.list.append(found, a);
383
+ }
384
+ });
385
+ if (!found.length) return { present: false, chosen: null, superseded: [] };
386
+ var chosenAt = 0;
387
+ for (var i = 0; i < found.length; i++) {
388
+ if (found[i].type === OID_SIGNING_CERTIFICATE_V2) { chosenAt = i; break; }
389
+ }
390
+ var superseded = [];
391
+ for (var j = 0; j < found.length; j++) {
392
+ if (j !== chosenAt) guard.list.append(superseded, _bindingClaim(found[j]));
393
+ }
394
+ return { present: true, chosen: _bindingClaim(found[chosenAt]), superseded: superseded };
395
+ }
396
+
397
+ /** @internal The claims read and compared in one call, for a consumer holding a single certificate.
398
+ * A consumer with several reads the claims once and calls `certBindingAgainst` per certificate.
399
+ *
400
+ * @internal
401
+ */
402
+ function certBindingFacts(signedAttrs, certDer) {
403
+ var claims = certBindingClaims(signedAttrs);
404
+ if (!claims.present) return { present: false, chosen: null, superseded: [] };
405
+ return {
406
+ present: true,
407
+ chosen: certBindingAgainst(claims.chosen, certDer),
408
+ superseded: intrinsic.map(claims.superseded, function (c) { return certBindingAgainst(c, certDer); }),
409
+ };
410
+ }
411
+
412
+ var _BUILD_V2_OPTS = intrinsic.assign(intrinsic.create(null), { hashAlgorithm: 1 });
413
+
414
+ /**
415
+ * @primitive pki.schema.smime.buildSigningCertificateV2
416
+ * @signature pki.schema.smime.buildSigningCertificateV2(certs, opts?) -> Buffer
417
+ * @since 0.8.52
418
+ * @status stable
419
+ * @spec RFC 5035, RFC 5754
420
+ * @related pki.schema.smime.parseSigningCertificateV2, pki.cms.sign, pki.tsp.sign
421
+ *
422
+ * Encode an ESS `SigningCertificateV2` attribute value (RFC 5035 sec. 5.4.1), the signed attribute
423
+ * that binds a signature to the certificate that made it. `certs` is the signer's certificate DER,
424
+ * or a list of certificate DERs whose first entry is the signer's (RFC 5035 sec. 5.4); each entry
425
+ * becomes an `ESSCertIDv2` carrying that certificate's hash. The result is the single
426
+ * `AttributeValue` of a `signingCertificateV2` signed attribute, which is what CAdES requires a
427
+ * signature to carry (ETSI EN 319 122-1 clauses 5.2.2.2 and 5.2.2.3).
428
+ *
429
+ * `opts.hashAlgorithm` names the binding digest: `sha256` (the default), `sha384` or `sha512`. The
430
+ * SHA-256 form omits the `hashAlgorithm` field, which is the RFC 5035 sec. 4 DEFAULT and so must be
431
+ * absent (X.690 sec. 11.5); the other two emit an `AlgorithmIdentifier` with the parameters field
432
+ * absent, which RFC 5754 sec. 2 requires an implementation to generate. SHA-1 is refused
433
+ * `smime/unsupported-algorithm`: a binding is an identity claim, and `pki.tsp.verify` refuses a
434
+ * SHA-1 one as a weak binding.
435
+ *
436
+ * The OPTIONAL `issuerSerial` and `policies` fields are not emitted; CAdES has the issuer serial
437
+ * omitted (ETSI EN 319 122-1 clause 6.3 requirement g). A caller that needs either field builds the
438
+ * value with `pki.asn1.build`, and `parseSigningCertificateV2` reads both back.
439
+ *
440
+ * @example
441
+ * var pair = await pki.key.generate("Ed25519");
442
+ * var key = await pki.key.export(pair.privateKey);
443
+ * var signerCertDer = await pki.x509.sign({ subject: "Signer", subjectPublicKey: await pki.key.export(pair.publicKey),
444
+ * notBefore: new Date("2026-01-01T00:00:00Z"), notAfter: new Date("2036-01-01T00:00:00Z") }, { key: key });
445
+ * var av = pki.schema.smime.buildSigningCertificateV2(signerCertDer);
446
+ * var msg = await pki.cms.sign(Buffer.from("hello"), { cert: signerCertDer, key: key },
447
+ * { additionalSignedAttributes: [{ type: "signingCertificateV2", values: [av] }] });
448
+ * (await pki.cms.verify(msg, { certs: [signerCertDer] })).signers[0].cadesBaseline.conformant; // true
449
+ */
450
+ function buildSigningCertificateV2(certs, opts) {
451
+ guard.identifier.assertKnownKeys(opts, _BUILD_V2_OPTS, _err, "smime/bad-input",
452
+ "pki.schema.smime.buildSigningCertificateV2 has an unknown option ");
453
+ var settings = guard.identifier.pickSettings(opts, ["hashAlgorithm"]);
454
+ var hashName = settings.hashAlgorithm === undefined || settings.hashAlgorithm === null ? "sha256" : settings.hashAlgorithm;
455
+ if (typeof hashName !== "string" || !BIND_DIGESTS[guard.text.keyOf(hashName)]) {
456
+ throw _err("smime/unsupported-algorithm", "the ESSCertIDv2 binding digest must be sha256, sha384 or sha512, got " +
457
+ guard.text.showValue(hashName));
458
+ }
459
+ var list = guard.list.snapshot(intrinsic.isArray(certs) ? certs : [certs]);
460
+ if (!list.length) {
461
+ throw _err("smime/bad-input", "a SigningCertificateV2 needs at least one certificate, the signer's first (RFC 5035 sec. 5.4)");
462
+ }
463
+ var ids = [];
464
+ for (var i = 0; i < list.length; i++) {
465
+ var der = guard.bytes.view(list[i], SmimeError, "smime/bad-input", "the certificate at certs[" + i + "]");
466
+ var certHash = b.octetString(guard.crypto.digest(hashName, der));
467
+ guard.list.append(ids, hashName === "sha256"
468
+ ? b.sequence([certHash])
469
+ : b.sequence([b.sequence([b.oid(oid.byName(hashName))]), certHash]));
470
+ }
471
+ return b.sequence([b.sequence(ids)]);
472
+ }
473
+
269
474
  /**
270
475
  * @primitive pki.schema.smime.decodeAttribute
271
476
  * @signature pki.schema.smime.decodeAttribute(attr) -> { kind, ... }
@@ -292,7 +497,7 @@ var parseSmimeCapabilities = pkix.makeParser({ pemLabel: null, PemError: PemErro
292
497
  * got.kind; // "signingCertificateV2"
293
498
  */
294
499
  function decodeAttribute(attr) {
295
- if (!attr || typeof attr !== "object" || typeof attr.type !== "string" || !Array.isArray(attr.values)) {
500
+ if (!attr || typeof attr !== "object" || typeof attr.type !== "string" || !intrinsic.isArray(attr.values)) {
296
501
  throw new SmimeError("smime/bad-input", "decodeAttribute expects a CMS attribute { type, values }");
297
502
  }
298
503
  if (attr.type === OID_SIGNING_CERTIFICATE || attr.type === OID_SIGNING_CERTIFICATE_V2 || attr.type === OID_SMIME_CAPABILITIES) {
@@ -313,6 +518,11 @@ function decodeAttribute(attr) {
313
518
  }
314
519
 
315
520
  module.exports = {
521
+ buildSigningCertificateV2: buildSigningCertificateV2,
522
+ certBindingFacts: certBindingFacts, // @internal
523
+ certBindingClaims: certBindingClaims, // @internal
524
+ certBindingAgainst: certBindingAgainst, // @internal
525
+
316
526
  parseSigningCertificate: parseSigningCertificate,
317
527
  parseSigningCertificateV2: parseSigningCertificateV2,
318
528
  parseSmimeCapabilities: parseSmimeCapabilities,