@blamejs/pki 0.8.30 → 0.8.48

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.
Files changed (87) hide show
  1. package/CHANGELOG.md +411 -0
  2. package/README.md +67 -16
  3. package/bin/pki.js +716 -30
  4. package/examples/issue-a-certificate.js +90 -0
  5. package/examples/post-quantum.js +66 -0
  6. package/examples/read-a-certificate.js +56 -0
  7. package/examples/revoke-and-check.js +86 -0
  8. package/examples/sign-and-verify-cms.js +63 -0
  9. package/index.js +18 -5
  10. package/lib/acme.js +223 -74
  11. package/lib/alt-sig.js +441 -0
  12. package/lib/asn1-der.js +48 -25
  13. package/lib/attrcert-sign.js +27 -19
  14. package/lib/cbor-det.js +24 -14
  15. package/lib/cmc-build.js +9 -2
  16. package/lib/cmc-verify.js +14 -3
  17. package/lib/cmp-build.js +17 -5
  18. package/lib/cmp-session.js +24 -11
  19. package/lib/cmp-verify.js +28 -19
  20. package/lib/cms-compress.js +3 -1
  21. package/lib/cms-decrypt.js +102 -7
  22. package/lib/cms-encrypt.js +59 -7
  23. package/lib/cms-sign.js +31 -9
  24. package/lib/cms-verify.js +22 -18
  25. package/lib/composite-kem.js +30 -18
  26. package/lib/composite-sig.js +29 -14
  27. package/lib/constants.js +28 -0
  28. package/lib/crl-sign.js +111 -19
  29. package/lib/crmf-sign.js +167 -15
  30. package/lib/csr-sign.js +117 -6
  31. package/lib/ct.js +939 -48
  32. package/lib/est.js +29 -16
  33. package/lib/framework-error.js +12 -0
  34. package/lib/guard-async.js +1 -1
  35. package/lib/guard-bytes.js +103 -20
  36. package/lib/guard-crypto.js +205 -3
  37. package/lib/guard-encoding.js +6 -1
  38. package/lib/guard-identifier.js +55 -8
  39. package/lib/guard-intrinsic.js +43 -0
  40. package/lib/guard-json.js +122 -9
  41. package/lib/guard-limits.js +12 -1
  42. package/lib/guard-list.js +63 -8
  43. package/lib/guard-name.js +167 -20
  44. package/lib/guard-range.js +43 -5
  45. package/lib/guard-text.js +41 -0
  46. package/lib/guard-verdict.js +39 -8
  47. package/lib/hpke.js +688 -42
  48. package/lib/http-transport.js +156 -4
  49. package/lib/identity-match.js +4 -4
  50. package/lib/inspect.js +448 -2
  51. package/lib/jose.js +93 -25
  52. package/lib/key.js +18 -13
  53. package/lib/lint.js +165 -1
  54. package/lib/merkle.js +233 -27
  55. package/lib/ocsp.js +27 -14
  56. package/lib/oid.js +14 -4
  57. package/lib/path-validate.js +143 -61
  58. package/lib/pkcs12-build.js +7 -2
  59. package/lib/pki-build.js +31 -7
  60. package/lib/possession.js +575 -0
  61. package/lib/related-cert.js +708 -0
  62. package/lib/revocation-fetch.js +6 -6
  63. package/lib/scep.js +15 -5
  64. package/lib/schema-c509.js +5 -2
  65. package/lib/schema-cmc.js +1 -1
  66. package/lib/schema-cmp.js +8 -8
  67. package/lib/schema-cms.js +2 -15
  68. package/lib/schema-csr.js +69 -0
  69. package/lib/schema-engine.js +10 -3
  70. package/lib/schema-pkix.js +267 -4
  71. package/lib/schema-tsp.js +7 -6
  72. package/lib/sign-scheme.js +22 -9
  73. package/lib/sigstore.js +684 -168
  74. package/lib/sleep.js +8 -1
  75. package/lib/smime.js +22 -1
  76. package/lib/tlog.js +1235 -0
  77. package/lib/tls-cert-compress.js +25 -8
  78. package/lib/tsp-sign.js +120 -35
  79. package/lib/tuf.js +1395 -0
  80. package/lib/validator-tpm.js +4 -1
  81. package/lib/verify-seam.js +7 -1
  82. package/lib/webauthn-mds.js +41 -4
  83. package/lib/webauthn.js +43 -25
  84. package/lib/webcrypto.js +48 -46
  85. package/lib/x509-sign.js +225 -19
  86. package/package.json +2 -1
  87. package/sbom.cdx.json +6 -6
@@ -352,8 +352,8 @@ async function _tryOcsp(state, cert, issuer, ctx, candidate) {
352
352
  if (!state.nonce && v.nextUpdate && v.thisUpdate) {
353
353
  state.cache.put(key, hit, v.thisUpdate, v.nextUpdate, hit.length);
354
354
  }
355
- return { status: v.status, reason: "an OCSP response from " + candidate.url,
356
- revocationReason: v.revocationReason, revocationTime: v.revocationTime };
355
+ return guard.verdict.of({ status: v.status, reason: "an OCSP response from " + candidate.url,
356
+ revocationReason: v.revocationReason, revocationTime: v.revocationTime });
357
357
  }
358
358
 
359
359
  /** @internal The bytes the nonce extension carries, which is what the response echoes. The
@@ -490,14 +490,14 @@ async function _tryCrl(state, cert, issuer, ctx, candidates, certDeltas) {
490
490
  var interim = await state.crlChecker(gathered, { useDeltas: state.useDeltas }).check(cert, issuer, ctx);
491
491
  if (interim && (interim.status === "good" || interim.status === "revoked")) {
492
492
  if (authentic) _keep(state, url, parsed, fetchedBytes, cert.issuer.bytes, issuer.workingPublicKey);
493
- return { status: interim.status, reason: "a CRL from " + url,
494
- revocationReason: _reasonCodeOf(interim), revocationTime: interim.revocationTime };
493
+ return guard.verdict.of({ status: interim.status, reason: "a CRL from " + url,
494
+ revocationReason: _reasonCodeOf(interim), revocationTime: interim.revocationTime });
495
495
  }
496
496
  }
497
497
  if (gathered.length === 0) throw E("path/revocation-fetch-failed", "no CRL was fetched");
498
498
  var last = await state.crlChecker(gathered, { useDeltas: state.useDeltas }).check(cert, issuer, ctx);
499
- return { status: (last && last.status) || "unknown", reason: (last && last.reason) || "the fetched CRLs did not settle the status",
500
- revocationReason: _reasonCodeOf(last), revocationTime: last && last.revocationTime };
499
+ return guard.verdict.of({ status: (last && last.status) || "unknown", reason: (last && last.reason) || "the fetched CRLs did not settle the status",
500
+ revocationReason: _reasonCodeOf(last), revocationTime: last && last.revocationTime });
501
501
  }
502
502
 
503
503
  /** @internal `crlChecker` names the CRLReason integer `reasonCode` and `verifyOcspResponse` names it
package/lib/scep.js CHANGED
@@ -473,7 +473,7 @@ function _buildIssuerAndSerial(spec) {
473
473
  *
474
474
  * @opts
475
475
  * - `recipientKey` -- `{ cert, key }` for the recipient, to decrypt the pkcsPKIEnvelope into `messageData`.
476
- * - `signerCert` -- the expected signer certificate DER (the CA certificate for a CertRep); the message signer's public key must match it, or the message is refused.
476
+ * - `signerCert` -- the expected signer certificate DER (the CA certificate for a CertRep); the message signer's public key must match it, or the message is refused. It also decides whether an envelope using RSAES-PKCS1-v1_5 key transport over a content with no integrity tag is read: that combination is refused for a signer this option has not authenticated.
477
477
  * - `expectedSenderNonce` -- a 16-byte Buffer; a message whose `recipientNonce` does not echo it is refused.
478
478
  * @example
479
479
  * // requires: pkiMessage -- a SCEP pkiMessage DER (from pki.scep.build, or a CA's response)
@@ -571,7 +571,17 @@ async function parse(bytes, opts) {
571
571
  }
572
572
  if (recipientKey != null && carriesEnvelope && v.eContent != null) {
573
573
  var dec;
574
- try { dec = await cmsDecrypt.decrypt(v.eContent, recipientKey); }
574
+ /** @internal The opt-in is passed only once the signer has been authenticated, which is what the
575
+ * premise rests on. `pki.cms.decrypt` refuses RSAES-PKCS1-v1_5 key transport over a content with no
576
+ * integrity tag because acceptance tells an attacker submitting chosen ciphertexts that theirs
577
+ * decoded. With `opts.signerCert` matched, the envelope arrived inside a signature an authorized
578
+ * peer made, and an attacker who cannot sign as that peer cannot submit one. Without it the
579
+ * signature is any signer's, this verb reports `signerAuthenticated: false`, and the caller
580
+ * authenticates the surfaced certificate after the decrypt has already produced its outcome, so the
581
+ * refusal stands. RFC 8894 sec. 3.1 puts the envelope there, and v1.5 over a CBC content is what
582
+ * deployed SCEP peers send, so an authenticated exchange still reads them. */
583
+ var decOpts = signerAuthenticated ? { allowUnauthenticatedRsa15: true } : {};
584
+ try { dec = await cmsDecrypt.decrypt(v.eContent, recipientKey, decOpts); }
575
585
  catch (e) {
576
586
  if (e && e.isPkiError) throw _err("scep/decrypt-failed", "the pkcsPKIEnvelope could not be decrypted with the recipient key: " + e.message, e);
577
587
  throw e;
@@ -606,7 +616,7 @@ async function parse(bytes, opts) {
606
616
  if (isnCh[1].content.length > 20) throw _err("scep/bad-request-payload", "a " + messageType + " messageData serialNumber must not exceed 20 octets (RFC 5280 sec. 4.1.2.2)");
607
617
  }
608
618
  }
609
- return out;
619
+ return guard.verdict.shield(out);
610
620
  }
611
621
  var _PARSE_KEYS = Object.assign(Object.create(null), { recipientKey: 1, expectedSenderNonce: 1, signerCert: 1 });
612
622
 
@@ -1006,7 +1016,7 @@ function _dispatchCertRep(rep, transactionId, spki) {
1006
1016
  if (findIssuedCert(repCerts.filter(function (c) { return c !== issued; }), spki.bytes) != null) {
1007
1017
  throw _err("scep/ambiguous-issued-cert", "the SUCCESS CertRep carried more than one certificate matching the request public key; the issued certificate is ambiguous (RFC 8894 sec. 3.3)");
1008
1018
  }
1009
- return { pending: false, result: { status: "SUCCESS", certificate: issued, certificates: repCerts, transactionId: rep.transactionId } };
1019
+ return guard.verdict.of({ pending: false, result: guard.verdict.of({ status: "SUCCESS", certificate: issued, certificates: repCerts, transactionId: rep.transactionId }) });
1010
1020
  }
1011
1021
 
1012
1022
  function _pollExhausted(which, transactionId, senderNonce) {
@@ -1089,7 +1099,7 @@ async function _pkiOperation(messageType, baseUrl, opts, verbName) {
1089
1099
  var rep = await parse(body, { signerCert: signerCert, expectedSenderNonce: senderNonce, recipientKey: recipientKey });
1090
1100
  var d = _dispatchCertRep(rep, transactionId, spki);
1091
1101
  if (!d.pending) return d.result;
1092
- if (pollCount <= 0) return { status: "PENDING", transactionId: rep.transactionId, senderNonce: senderNonce };
1102
+ if (pollCount <= 0) return guard.verdict.of({ status: "PENDING", transactionId: rep.transactionId, senderNonce: senderNonce });
1093
1103
  return _pollForIssuance({
1094
1104
  firstResponse: res, firstSenderNonce: senderNonce, postUrl: postUrl, httpMethod: httpMethod, clientOpts: clientOpts,
1095
1105
  caCert: caCert, csrSubject: csrSubject, issuerCert: issuerCert, signer: signer, signerCert: signerCert, recipientKey: recipientKey,
@@ -1282,7 +1282,7 @@ function _requireStrictDerTlv(content, code, label) {
1282
1282
 
1283
1283
  function _derSetInDeclaredOrder(vals) {
1284
1284
  for (var i = 1; i < vals.length; i++) {
1285
- if (Buffer.compare(vals[i - 1], vals[i]) > 0) throw _err("c509/bad-extensions", "a subjectDirectoryAttributes attributeValue list must be in DER ascending order (X.690 sec. 11.6)");
1285
+ if (guard.bytes.compare(vals[i - 1], vals[i]) > 0) throw _err("c509/bad-extensions", "a subjectDirectoryAttributes attributeValue list must be in DER ascending order (X.690 sec. 11.6)");
1286
1286
  }
1287
1287
  return b.set(vals);
1288
1288
  }
@@ -2033,7 +2033,10 @@ function encode(input, opts) {
2033
2033
  var origDer = pkix.coerceToDer(input, { pemLabel: "CERTIFICATE", PemError: frameworkError.PemError, ErrorClass: C509Error, prefix: "c509" });
2034
2034
  var encoded = _encodeC509Array(_derToType3(origDer, opts));
2035
2035
  var recon = parse(encoded).reconstructedDer;
2036
- if (!recon || Buffer.compare(recon, origDer) !== 0) throw _err("c509/non-invertible", "the type-3 C509 does not reconstruct the source certificate byte-for-byte");
2036
+ /** @internal Through the captured comparator: this IS the byte-for-byte claim a type-3 C509 makes
2037
+ * about the certificate it re-encodes, so a replacement answering zero asserts it over bytes that
2038
+ * do not match. */
2039
+ if (!recon || guard.bytes.compare(recon, origDer) !== 0) throw _err("c509/non-invertible", "the type-3 C509 does not reconstruct the source certificate byte-for-byte");
2037
2040
  return encoded;
2038
2041
  }
2039
2042
 
package/lib/schema-cmc.js CHANGED
@@ -245,7 +245,7 @@ function parseStatusInfo(valueDer, version, bodyPartID, ctx) {
245
245
  extendedFailInfo: other ? other.extendedFailInfo : null,
246
246
  };
247
247
  _assertStatusCoherent(out);
248
- return out;
248
+ return guard.verdict.shield(out);
249
249
  }
250
250
 
251
251
  function _assertStatusCoherent(s) {
package/lib/schema-cmp.js CHANGED
@@ -231,11 +231,11 @@ var PKI_STATUS_INFO = schema.seq([
231
231
  ], {
232
232
  assert: "sequence", code: "cmp/bad-status-info", what: "PKIStatusInfo",
233
233
  build: function (m) {
234
- return {
234
+ return guard.verdict.of({
235
235
  status: m.fields.status.value,
236
236
  statusString: m.fields.statusString.present ? m.fields.statusString.value.result : null,
237
237
  failInfo: m.fields.failInfo.present ? m.fields.failInfo.value : null,
238
- };
238
+ });
239
239
  },
240
240
  });
241
241
 
@@ -329,11 +329,11 @@ var REV_REP_CONTENT = schema.seq([
329
329
  ], {
330
330
  assert: "sequence", code: "cmp/bad-rev-rep", what: "RevRepContent",
331
331
  build: function (m) {
332
- return {
332
+ return guard.verdict.of({
333
333
  status: m.fields.status.value.result,
334
334
  revCerts: m.fields.revCerts.present ? m.fields.revCerts.value.result : null,
335
335
  crls: m.fields.crls.present ? m.fields.crls.value.result : null,
336
- };
336
+ });
337
337
  },
338
338
  });
339
339
 
@@ -411,12 +411,12 @@ var CERT_RESPONSE = schema.seq([
411
411
  throw ctx.E("cmp/bad-cert-response",
412
412
  "a CertResponse certifiedKeyPair is allowed only under a granting status (accepted or grantedWithMods) (RFC 9810 sec. 5.3.4)");
413
413
  }
414
- return {
414
+ return guard.verdict.of({
415
415
  certReqId: m.fields.certReqId.value,
416
416
  status: status,
417
417
  certifiedKeyPair: certifiedKeyPair,
418
418
  rspInfo: m.fields.rspInfo.present ? m.fields.rspInfo.value : null,
419
- };
419
+ });
420
420
  },
421
421
  });
422
422
 
@@ -455,12 +455,12 @@ var KEY_REC_REP_CONTENT = schema.seq([
455
455
  ], {
456
456
  assert: "sequence", code: "cmp/bad-key-rec-rep", what: "KeyRecRepContent",
457
457
  build: function (m) {
458
- return {
458
+ return guard.verdict.of({
459
459
  status: m.fields.status.value.result,
460
460
  newSigCert: m.fields.newSigCert.present ? m.fields.newSigCert.value : null,
461
461
  caCerts: m.fields.caCerts.present ? m.fields.caCerts.value.result : null,
462
462
  keyPairHist: m.fields.keyPairHist.present ? m.fields.keyPairHist.value.result : null,
463
- };
463
+ });
464
464
  },
465
465
  });
466
466
 
package/lib/schema-cms.js CHANGED
@@ -60,7 +60,6 @@ var NS = pkix.makeNS("cms", CmsError, oid);
60
60
 
61
61
  var ALGORITHM_IDENTIFIER = pkix.algorithmIdentifier(NS);
62
62
  var ATTRIBUTE = pkix.attribute(NS);
63
- var NAME = pkix.name(NS);
64
63
 
65
64
  var SIGNED_DATA_VERSION = pkix.versionReader(NS, { "1": 1, "3": 3, "4": 4, "5": 5 });
66
65
  var SIGNER_VERSION = pkix.versionReader(NS, { "1": 1, "3": 3 });
@@ -179,7 +178,7 @@ function _readsAsTime(vals) {
179
178
 
180
179
  function _ascendingDer(nodes) {
181
180
  for (var i = 1; i < nodes.length; i++) {
182
- if (Buffer.compare(nodes[i - 1].bytes, nodes[i].bytes) > 0) return false;
181
+ if (guard.bytes.compare(nodes[i - 1].bytes, nodes[i].bytes) > 0) return false;
183
182
  }
184
183
  return true;
185
184
  }
@@ -330,19 +329,7 @@ var ENCAP_CONTENT_INFO = schema.seq([
330
329
  },
331
330
  });
332
331
 
333
- var ISSUER_AND_SERIAL = schema.seq([
334
- schema.field("issuer", NAME),
335
- schema.field("serialNumber", schema.integerLeaf()),
336
- ], {
337
- assert: "sequence", arity: { exact: 2 }, code: "cms/bad-issuer-and-serial", what: "IssuerAndSerialNumber",
338
- build: function (m) {
339
- return {
340
- issuer: m.fields.issuer.value.result,
341
- serialNumber: m.fields.serialNumber.value,
342
- serialNumberHex: m.fields.serialNumber.node.content.toString("hex"),
343
- };
344
- },
345
- });
332
+ var ISSUER_AND_SERIAL = pkix.issuerAndSerialNumber(NS, { code: "cms/bad-issuer-and-serial" });
346
333
 
347
334
  var SIGNER_IDENTIFIER = schema.choice([
348
335
  { when: { tagClass: "universal", tagNumber: asn1.TAGS.SEQUENCE }, schema: ISSUER_AND_SERIAL },
package/lib/schema-csr.js CHANGED
@@ -35,6 +35,7 @@ var pkix = require("./schema-pkix");
35
35
  var oid = require("./oid");
36
36
  var frameworkError = require("./framework-error");
37
37
  var intrinsic = require("./guard-intrinsic");
38
+ var constants = require("./constants");
38
39
 
39
40
  var CsrError = frameworkError.CsrError;
40
41
  var PemError = frameworkError.PemError;
@@ -73,6 +74,74 @@ RECOGNIZED_ATTRIBUTE_VALUE[oid.byName("challengePassword")] = function (node, ct
73
74
  }
74
75
  };
75
76
 
77
+ /** @internal RequesterCertificate ::= SEQUENCE { certID IssuerAndSerialNumber, requestTime BinaryTime,
78
+ * locationInfo UniformResourceIdentifiers, signature BIT STRING } (RFC 9763 sec. 3.1), where
79
+ * UniformResourceIdentifiers ::= SEQUENCE SIZE (1..MAX) OF URI and URI ::= IA5String. locationInfo is
80
+ * a SEQUENCE OF rather than one URI, and it is the one field the signature does not cover. */
81
+ var REQUESTER_CERTIFICATE = schema.seq([
82
+ schema.field("certID", pkix.issuerAndSerialNumber(NS, { code: "csr/bad-attribute-value" })),
83
+ schema.field("requestTime", schema.integerLeaf()),
84
+ /** @internal A SEQUENCE OF, so the DER SET-OF ordering rule does not apply and the URIs keep the
85
+ * order the requester wrote them in. */
86
+ schema.field("locationInfo", schema.seqOf(schema.decode(function (n, ctx) {
87
+ if (n.tagClass !== "universal" || n.tagNumber !== asn1.TAGS.IA5_STRING) {
88
+ throw ctx.E("csr/bad-attribute-value", "a RequesterCertificate locationInfo entry must be an IA5String URI (RFC 9763 sec. 3.1)");
89
+ }
90
+ return asn1.read.string(n);
91
+ }), { assert: "sequence", min: 1, max: constants.LIMITS.ATTRIBUTE_MAX_VALUES,
92
+ code: "csr/bad-attribute-value", what: "RequesterCertificate locationInfo" })),
93
+ schema.field("signature", schema.bitString()),
94
+ ], {
95
+ assert: "sequence", arity: { exact: 4 }, code: "csr/bad-attribute-value", what: "RequesterCertificate",
96
+ build: function (m, ctx) {
97
+ var t = m.fields.requestTime.value;
98
+ if (t < 0n) throw ctx.E("csr/bad-attribute-value", "a RequesterCertificate requestTime must not be negative (BinaryTime is INTEGER (0..MAX), RFC 6019 sec. 2)");
99
+ var bits = m.fields.signature.value;
100
+ if (bits.unusedBits !== 0) throw ctx.E("csr/bad-attribute-value", "a RequesterCertificate signature BIT STRING must be octet-aligned");
101
+ if (bits.bytes.length === 0) throw ctx.E("csr/bad-attribute-value", "a RequesterCertificate signature must not be empty");
102
+ return {
103
+ certID: m.fields.certID.value.result,
104
+ requestTime: t,
105
+ locationInfo: intrinsic.map(m.fields.locationInfo.value.items, function (it) { return it.value; }),
106
+ signature: { unusedBits: bits.unusedBits, bytes: bits.bytes },
107
+ };
108
+ },
109
+ });
110
+ /** @internal PrivateKeyPossessionStatement ::= SEQUENCE { signer IssuerAndSerialNumber,
111
+ * cert Certificate OPTIONAL } (RFC 9883 sec. 3). The signer is the shared IssuerAndSerialNumber; the
112
+ * certificate is surfaced as raw DER, because it is a whole Certificate a caller parses and verifies
113
+ * rather than a field read here. */
114
+ var POSSESSION_STATEMENT = schema.seq([
115
+ schema.field("signer", pkix.issuerAndSerialNumber(NS, { code: "csr/bad-attribute-value" })),
116
+ schema.optional("certificate", schema.any(), { whenUniversal: [asn1.TAGS.SEQUENCE] }),
117
+ ], {
118
+ assert: "sequence", arity: { min: 1, max: 2 },
119
+ code: "csr/bad-attribute-value", what: "PrivateKeyPossessionStatement",
120
+ build: function (m) {
121
+ return {
122
+ signer: m.fields.signer.value.result,
123
+ certificate: m.fields.certificate.present ? m.fields.certificate.node.bytes : null,
124
+ };
125
+ },
126
+ });
127
+ RECOGNIZED_ATTRIBUTE_VALUE[oid.byName("statementOfPossession")] = function (node, ctx) {
128
+ try { return { privateKeyPossessionStatement: schema.walk(POSSESSION_STATEMENT, node, ctx).result }; }
129
+ catch (e) {
130
+ if (e instanceof CsrError) throw e;
131
+ throw ctx.E("csr/bad-attribute-value",
132
+ "statementOfPossession value must be a well-formed PrivateKeyPossessionStatement SEQUENCE (RFC 9883 sec. 3): " + ((e && e.message) || intrinsic.String(e)), e);
133
+ }
134
+ };
135
+
136
+ RECOGNIZED_ATTRIBUTE_VALUE[oid.byName("relatedCertRequest")] = function (node, ctx) {
137
+ try { return { relatedCertRequest: schema.walk(REQUESTER_CERTIFICATE, node, ctx).result }; }
138
+ catch (e) {
139
+ if (e instanceof CsrError) throw e;
140
+ throw ctx.E("csr/bad-attribute-value",
141
+ "relatedCertRequest value must be a well-formed RequesterCertificate SEQUENCE (RFC 9763 sec. 3.1): " + ((e && e.message) || intrinsic.String(e)), e);
142
+ }
143
+ };
144
+
76
145
  var CERTIFICATION_REQUEST_INFO = schema.seq([
77
146
  schema.field("version", CSR_VERSION),
78
147
  schema.field("subject", NAME),
@@ -28,6 +28,10 @@
28
28
 
29
29
  var asn1 = require("./asn1-der.js");
30
30
  var intrinsic = require("./guard-intrinsic");
31
+ /** @internal Just the list guard, not the whole family: this module sits below the format layer and
32
+ * takes the one primitive it needs. The copy-and-sort it provides reads `length` once and orders
33
+ * through operations captured at load, which is what a DER SET ordering rests on. */
34
+ var guardList = require("./guard-list");
31
35
 
32
36
  var TAGS = asn1.TAGS;
33
37
 
@@ -310,7 +314,7 @@ function _walkRepeat(schema, node, ctx) {
310
314
  }
311
315
  if (schema.derSetOrder) {
312
316
  for (var s = 1; s < kids.length; s++) {
313
- if (Buffer.compare(kids[s - 1].bytes, kids[s].bytes) > 0) {
317
+ if (intrinsic.compare(kids[s - 1].bytes, kids[s].bytes) > 0) {
314
318
  _fail(ctx, schema.code, (schema.what || "value") + " components must be in ascending DER order (X.690 sec. 11.6)");
315
319
  }
316
320
  }
@@ -474,7 +478,7 @@ function _encodeSeq(schema, value, ctx) {
474
478
  if (!isDefault) parts.push(fld.explicit ? asn1.build.explicit(fld.tag, enc) : enc);
475
479
  }
476
480
  } else if (fld.fkind === "trailing") {
477
- var members = fld.members.slice().sort(function (a, b) { return a.tag - b.tag; });
481
+ var members = guardList.sortedCopy(fld.members, function (a, b) { return a.tag - b.tag; });
478
482
  for (var i = 0; i < members.length; i++) {
479
483
  var mm = members[i];
480
484
  if (_present(value[mm.name])) {
@@ -509,7 +513,10 @@ function _encodeRepeat(schema, items, ctx) {
509
513
  seen.add(key);
510
514
  });
511
515
  }
512
- if (schema.derSetOrder) parts = parts.slice().sort(Buffer.compare);
516
+ /** @internal The comparator goes through the captured operation, as the copy and the sort do. This
517
+ * is the engine's own SET OF emission, so whatever decides "before" decides the bytes every schema
518
+ * built on it emits. */
519
+ if (schema.derSetOrder) parts = guardList.sortedCopy(parts, intrinsic.compare);
513
520
  if (schema.implicitTag != null) return asn1.build.contextConstructed(schema.implicitTag, Buffer.concat(parts));
514
521
  return schema.assert === "set" ? asn1.build.set(parts) : asn1.build.sequence(parts);
515
522
  }