@blamejs/pki 0.8.31 → 0.8.49

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 +401 -0
  2. package/README.md +66 -15
  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 +26 -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 +1008 -58
  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 +33 -6
  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 +228 -2
  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
  }
@@ -741,6 +741,68 @@ function algorithmIdentifier(ns, opts) {
741
741
  });
742
742
  }
743
743
 
744
+ /** @internal IssuerAndSerialNumber ::= SEQUENCE { issuer Name, serialNumber CertificateSerialNumber }
745
+ * (RFC 5652 sec. 10.2.4). CMS names the type and RFC 9763 sec. 3.1 reuses it inside a CSR attribute,
746
+ * so the shape is defined once and each caller supplies its own code and name for the refusal. */
747
+ function issuerAndSerialNumber(ns, opts) {
748
+ opts = opts || {};
749
+ return schema.seq([
750
+ schema.field("issuer", name(ns)),
751
+ schema.field("serialNumber", schema.integerLeaf()),
752
+ ], {
753
+ assert: "sequence", arity: { exact: 2 },
754
+ code: opts.code || (ns.prefix + "/bad-issuer-and-serial"), what: opts.what || "IssuerAndSerialNumber",
755
+ build: function (m) {
756
+ return {
757
+ issuer: m.fields.issuer.value.result,
758
+ serialNumber: m.fields.serialNumber.value,
759
+ serialNumberHex: _bufToString(m.fields.serialNumber.node.content, "hex"),
760
+ };
761
+ },
762
+ });
763
+ }
764
+
765
+ /** @internal The digest a signature algorithm OID names, where it names one. RFC 9763 sec. 4.1 reads
766
+ * this relation directly ("a hash algorithm explicitly indicated by the related certificate's
767
+ * signature OID"), and an algorithm whose OID fixes no digest, such as Ed25519 or ML-DSA, is absent
768
+ * rather than mapped to a default. The value is the registry name, so a caller that needs its own
769
+ * spelling maps from one table instead of carrying a second copy of the relation. */
770
+ var SIG_OID_DIGEST_ROWS = [
771
+ ["sha1WithRSAEncryption", "sha1"], ["sha256WithRSAEncryption", "sha256"],
772
+ ["sha384WithRSAEncryption", "sha384"], ["sha512WithRSAEncryption", "sha512"],
773
+ ["ecdsaWithSHA1", "sha1"], ["ecdsaWithSHA256", "sha256"],
774
+ ["ecdsaWithSHA384", "sha384"], ["ecdsaWithSHA512", "sha512"],
775
+ ["sha1", "sha1"], ["sha256", "sha256"], ["sha384", "sha384"], ["sha512", "sha512"],
776
+ ["sha3-256", "sha3-256"], ["sha3-512", "sha3-512"],
777
+ ["id-RSASSA-PSS-SHAKE128", "shake128"], ["id-RSASSA-PSS-SHAKE256", "shake256"],
778
+ ];
779
+ /** @internal The digest output length in octets, for the readers that hold a hash value to the length
780
+ * of the algorithm named beside it. A SHAKE is absent: its output length is a parameter of the use,
781
+ * not of the algorithm, so there is no single length to hold a value to. */
782
+ var DIGEST_LENGTH_ROWS = [
783
+ ["sha1", 20], ["sha256", 32], ["sha384", 48], ["sha512", 64],
784
+ ["sha3-256", 32], ["sha3-512", 64],
785
+ ];
786
+ function digestLengths() {
787
+ var out = _create(null);
788
+ _forEach(DIGEST_LENGTH_ROWS, function (r) { out[r[0]] = r[1]; });
789
+ return out;
790
+ }
791
+ var DIGEST_LENGTHS = digestLengths();
792
+ /** @internal Keyed by dotted OID through the registry the caller passes, this module taking its
793
+ * registry as a parameter rather than requiring it. A row whose name the registry does not carry
794
+ * throws rather than being skipped: a skipped row is a digest the readers silently stop deriving,
795
+ * which reads as "this certificate names no hash" on a certificate that names one. */
796
+ function signatureDigestNames(oidRegistry) {
797
+ var out = _create(null);
798
+ _forEach(SIG_OID_DIGEST_ROWS, function (r) {
799
+ var dotted = oidRegistry.byName(r[0]);
800
+ if (!dotted) throw new Error("schema-pkix: the signature-digest table names " + r[0] + ", which the OID registry does not carry");
801
+ out[dotted] = r[1];
802
+ });
803
+ return out;
804
+ }
805
+
744
806
  function pbkdf2Params(ns) {
745
807
  return schema.seq([
746
808
  schema.field("salt", schema.octetString()),
@@ -1386,7 +1448,12 @@ function crlExtensionDecoders(ns) {
1386
1448
  var doorScope = _create(null);
1387
1449
  doorScope.crl = _assign(_create(null), byScope.crl);
1388
1450
  doorScope.entry = _assign(_create(null), byScope.entry);
1389
- _forEach(["authorityKeyIdentifier", "issuerAltName", "authorityInfoAccess", "freshestCRL"], function (n) {
1451
+ /** @internal ITU-T X.509 (2019) clause 9.8 states altSignatureAlgorithm and altSignatureValue as CRL
1452
+ * extensions as well as certificate ones, and clause 7.10.3 gives the CRL the same procedure, so the
1453
+ * two share one decoder with the certificate scope. subjectAltPublicKeyInfo is absent: the NOTE in
1454
+ * clause 7.2.2 says it "is only used as a public-key certificate extension". */
1455
+ _forEach(["authorityKeyIdentifier", "issuerAltName", "authorityInfoAccess", "freshestCRL",
1456
+ "altSignatureAlgorithm", "altSignatureValue"], function (n) {
1390
1457
  doorScope.crl[O(n)] = scopedCertDecoder(ns, n);
1391
1458
  });
1392
1459
  doorScope.entry[O("certificateIssuer")] = _generalNamesDecoder(ns);
@@ -1707,10 +1774,93 @@ function _acceptExtensionRecord(ext, E, code, key) {
1707
1774
  record.oid = checked.oid;
1708
1775
  record.name = typeof checked.name === "string" ? checked.name : null;
1709
1776
  record.critical = checked.critical === true;
1710
- record.value = guard.bytes.snapshot(checked.value, E, code, "the extension value");
1777
+ /** @internal Normalized to a byte view before it is copied. The check above admits what
1778
+ * `guard.bytes.isByteSource` admits, which includes a DataView and an ArrayBuffer, and
1779
+ * `guard.bytes.snapshot` takes neither: a value this verb had just called acceptable was then refused
1780
+ * for being the wrong kind of container, so the two messages contradicted each other about one value.
1781
+ * `snapshotSource` views and copies in one call, and the view shares the caller's store rather than
1782
+ * copying it, so the copy is still the only allocation. */
1783
+ record.value = guard.bytes.snapshotSource(checked.value, E, code, "the extension value");
1711
1784
  return record;
1712
1785
  }
1713
1786
 
1787
+ /** @internal Whether a parsed certificate's keyUsage permits a named set of purposes, as one definition
1788
+ * three verifiers share. RFC 5280 sec. 4.2.1.3 confines a key to the purposes a present keyUsage asserts,
1789
+ * so a verifier checking a signature under a certificate's key owes this check: a certificate confined to
1790
+ * `keyAgreement` does not authorize a signature. Absent keyUsage is unconfined and permits; a keyUsage that
1791
+ * does not DECODE denies rather than permits, since an extension nothing could read has not been satisfied.
1792
+ * `allowed` is the set each caller's own specification names, which is the only thing that differs between
1793
+ * them: RFC 9483 sec. 3.2 requires `digitalSignature` alone, while a signature over content is also
1794
+ * authorized by `contentCommitment` or `nonRepudiation`. */
1795
+ function keyUsagePermits(ns, allowed) {
1796
+ var kuOid = ns.oid.byName("keyUsage");
1797
+ var decoders = certExtensionDecoders(ns).byOid;
1798
+ return function (parsedCert) {
1799
+ var exts = (parsedCert && parsedCert.extensions) || [];
1800
+ for (var i = 0; i < exts.length; i++) {
1801
+ if (exts[i].oid !== kuOid) continue;
1802
+ var ku;
1803
+ try { ku = decoders[kuOid](exts[i].value); }
1804
+ catch (e) { void e; return false; }
1805
+ for (var a = 0; a < allowed.length; a++) if (ku[allowed[a]] === true) return true;
1806
+ return false;
1807
+ }
1808
+ return true;
1809
+ };
1810
+ }
1811
+
1812
+ /** @internal The subject-key algorithms that can only ever SIGN, as one table the linter and the verifier
1813
+ * share. RFC 9883 sec. 6 states "The privateKeyPossessionStatement attribute MUST NOT be used to obtain a
1814
+ * signature certificate", and sec. 4 states the same requirement as a property of the request: "the
1815
+ * subjectPKInfo MUST contain the public key for the key establishment algorithm". The mechanism exists
1816
+ * because a key-establishment key cannot sign and so cannot prove possession the PKCS#10 way. A key that
1817
+ * can only sign has no such problem, so a request to certify one through that attribute asks for the
1818
+ * certificate those sentences forbid, whether or not the request names a key usage. Read off the requested
1819
+ * usages alone, the rule missed every request that omitted the keyUsage extension; read off this table
1820
+ * too, the two doors that apply it reach the same verdict on the same request.
1821
+ *
1822
+ * Only the signature-ONLY families are here. RSA and EC can do either, so an RSA or EC subject key says
1823
+ * nothing on its own and is left to the usages a request asks for. `rsassaPss` IS here and
1824
+ * `rsaEncryption` is NOT, which is the distinction the rule turns on: a bare RSA key can do key
1825
+ * transport, while an id-RSASSA-PSS SPKI is restricted to signing and can no more establish a key than
1826
+ * an EdDSA one. `dsa` and the stateful hash-based algorithms sign by construction. */
1827
+ var SIGNATURE_ONLY_KEY_NAMES = [
1828
+ "Ed25519", "Ed448", "rsassaPss", "dsa",
1829
+ "id-alg-hss-lms-hashsig", "id-alg-xmss-hashsig", "id-alg-xmssmt-hashsig",
1830
+ ];
1831
+ /** @internal The families, matched by the prefix the registry files them under rather than listed one at a
1832
+ * time: three ML-DSA sets, twelve SLH-DSA sets and eighteen composite ML-DSA arms, all of which sign and
1833
+ * none of which establishes a key. Taking them by family means a set the registry gains later belongs to
1834
+ * this rule without a second edit, which is what kept RSASSA-PSS out of an earlier hand-written list. */
1835
+ var SIGNATURE_ONLY_KEY_PREFIXES = ["id-ml-dsa-", "id-slh-dsa-", "id-MLDSA"];
1836
+
1837
+ var _signatureOnlyByNs = new _WeakMapI();
1838
+
1839
+ /** @internal Whether a dotted subject-key algorithm OID names a signature-only family. Built once per
1840
+ * OID registry and memoized on it, since the registry is enumerated to pick the families up by prefix. */
1841
+ function signatureOnlyKeyOid(ns) {
1842
+ var table = _weakGet(_signatureOnlyByNs, ns.oid);
1843
+ if (table === undefined) {
1844
+ table = _assign(_create(null), {});
1845
+ _forEach(SIGNATURE_ONLY_KEY_NAMES, function (n) {
1846
+ var dotted = ns.oid.byName(n);
1847
+ if (dotted !== undefined && dotted !== null) table[dotted] = n;
1848
+ });
1849
+ var registry = ns.oid.all();
1850
+ _forEach(intrinsic.keys(registry), function (dotted) {
1851
+ var n = registry[dotted];
1852
+ if (typeof n !== "string") return;
1853
+ _forEach(SIGNATURE_ONLY_KEY_PREFIXES, function (p) {
1854
+ if (_strIndexOf(n, p) === 0) table[dotted] = n;
1855
+ });
1856
+ });
1857
+ _weakSet(_signatureOnlyByNs, ns.oid, table);
1858
+ }
1859
+ return function (dotted) {
1860
+ return typeof dotted === "string" && _hasOwn(table, dotted);
1861
+ };
1862
+ }
1863
+
1714
1864
  function certExtensionDecoders(ns) {
1715
1865
  var GN_SUBTREE = generalName(ns, { decodeValue: true, subtreeBase: true, code: ns.prefix + "/bad-name-constraints" });
1716
1866
  var _h = _decodeHelpers(ns), decodeTop = _h.decodeTop, seqChildren = _h.seqChildren, readInt = _h.readInt, O = _h.O;
@@ -1913,6 +2063,67 @@ function certExtensionDecoders(ns) {
1913
2063
  return null;
1914
2064
  }
1915
2065
 
2066
+ /** @internal RelatedCertificate ::= SEQUENCE { hashAlgorithm DigestAlgorithmIdentifier,
2067
+ * hashValue OCTET STRING } (RFC 9763 sec. 4.1), the digest of "the entire related certificate".
2068
+ * The value is held to the length of the algorithm it names, since a hash of the wrong length for
2069
+ * its own algorithm identifies no certificate and would otherwise be carried forward as an
2070
+ * identity to compare against. */
2071
+ function relatedCertificate(buf) {
2072
+ var C = ns.prefix + "/bad-extension-value";
2073
+ var kids = seqChildren(buf, C, "RelatedCertificate");
2074
+ if (kids.length !== 2) throw ns.E(C, "RelatedCertificate must be a SEQUENCE { hashAlgorithm, hashValue } (RFC 9763 sec. 4.1)");
2075
+ var alg;
2076
+ try { alg = schema.walk(algorithmIdentifier(ns), kids[0], ns).result; }
2077
+ catch (e) { throw ns.E(C, "RelatedCertificate hashAlgorithm must be a DigestAlgorithmIdentifier (RFC 9763 sec. 4.1)", e); }
2078
+ var value;
2079
+ try { value = _bufferFrom(asn1.read.octetString(kids[1])); }
2080
+ catch (e) { throw ns.E(C, "RelatedCertificate hashValue must be an OCTET STRING (RFC 9763 sec. 4.1)", e); }
2081
+ if (value.length === 0) throw ns.E(C, "RelatedCertificate hashValue must not be empty (RFC 9763 sec. 4.1)");
2082
+ var want = DIGEST_LENGTHS[alg.name];
2083
+ if (want !== undefined && value.length !== want) {
2084
+ throw ns.E(C, "RelatedCertificate hashValue is " + value.length + " octets, and " + alg.name + " produces " + want);
2085
+ }
2086
+ return { hashAlgorithm: alg.name, hashAlgorithmOid: alg.oid, hashValue: value };
2087
+ }
2088
+
2089
+ /** @internal SubjectAltPublicKeyInfo ::= SEQUENCE { algorithm AlgorithmIdentifier,
2090
+ * subjectAltPublicKey BIT STRING } (ITU-T X.509 (2019) clause 9.8.2). Those are the two components
2091
+ * SubjectPublicKeyInfo has, in that order, and a field name is not encoded, so the two types share
2092
+ * one encoding and this reads through the shared SPKI sub-schema. The whole value is surfaced as
2093
+ * `bytes` because it IS an SPKI on the wire: an importer takes it unchanged. */
2094
+ function subjectAltPublicKeyInfo(buf) {
2095
+ var C = ns.prefix + "/bad-extension-value";
2096
+ var n = decodeTop(buf, C, "SubjectAltPublicKeyInfo");
2097
+ var v;
2098
+ try { v = schema.walk(spki(ns), n, ns).result; }
2099
+ catch (e) { throw ns.E(C, "SubjectAltPublicKeyInfo must be a SEQUENCE { algorithm, subjectAltPublicKey BIT STRING } (ITU-T X.509 (2019) clause 9.8.2)", e); }
2100
+ if (v.publicKey.unusedBits !== 0) throw ns.E(C, "the subjectAltPublicKey BIT STRING must hold whole octets");
2101
+ if (v.publicKey.bytes.length === 0) throw ns.E(C, "the subjectAltPublicKey must not be empty");
2102
+ return { algorithm: v.algorithm, subjectAltPublicKey: v.publicKey.bytes, bytes: v.bytes };
2103
+ }
2104
+
2105
+ /** @internal AltSignatureAlgorithm ::= AlgorithmIdentifier (clause 9.8.3). The extension value is the
2106
+ * AlgorithmIdentifier itself, not a SEQUENCE wrapping one. */
2107
+ function altSignatureAlgorithm(buf) {
2108
+ var C = ns.prefix + "/bad-extension-value";
2109
+ var n = decodeTop(buf, C, "AltSignatureAlgorithm");
2110
+ try { return schema.walk(algorithmIdentifier(ns), n, ns).result; }
2111
+ catch (e) { throw ns.E(C, "AltSignatureAlgorithm must be an AlgorithmIdentifier (ITU-T X.509 (2019) clause 9.8.3)", e); }
2112
+ }
2113
+
2114
+ /** @internal AltSignatureValue ::= BIT STRING (clause 9.8.4). Held to whole octets, a signature being
2115
+ * an octet string of bits and every algorithm here producing one. */
2116
+ function altSignatureValue(buf) {
2117
+ var C = ns.prefix + "/bad-extension-value";
2118
+ var n = decodeTop(buf, C, "AltSignatureValue");
2119
+ var bs;
2120
+ try { bs = asn1.read.bitString(n); }
2121
+ catch (e) { throw ns.E(C, "AltSignatureValue must be a BIT STRING (ITU-T X.509 (2019) clause 9.8.4)", e); }
2122
+ if (bs.unusedBits !== 0) throw ns.E(C, "the AltSignatureValue BIT STRING must hold whole octets");
2123
+ if (bs.bytes.length === 0) throw ns.E(C, "the AltSignatureValue must not be empty");
2124
+ return { unusedBits: bs.unusedBits, bytes: _bufferFrom(bs.bytes) };
2125
+ }
2126
+
1916
2127
  function extKeyUsage(buf) {
1917
2128
  var C = ns.prefix + "/bad-extension-value";
1918
2129
  var kids = seqChildren(buf, C, "ExtKeyUsage");
@@ -2270,6 +2481,10 @@ function certExtensionDecoders(ns) {
2270
2481
  byOid[O("subjectInfoAccess")] = subjectInfoAccess;
2271
2482
  byOid[O("subjectDirectoryAttributes")] = subjectDirectoryAttributes;
2272
2483
  byOid[O("ocspNoCheck")] = ocspNoCheck;
2484
+ byOid[O("relatedCertificate")] = relatedCertificate;
2485
+ byOid[O("subjectAltPublicKeyInfo")] = subjectAltPublicKeyInfo;
2486
+ byOid[O("altSignatureAlgorithm")] = altSignatureAlgorithm;
2487
+ byOid[O("altSignatureValue")] = altSignatureValue;
2273
2488
  byOid[O("msCertificateTemplate")] = msCertificateTemplate;
2274
2489
  byOid[O("msEnrollCertType")] = msEnrollCertType;
2275
2490
  byOid[O("msCaVersion")] = msCaVersion;
@@ -2377,6 +2592,12 @@ function signedEnvelope(ns, tbsSchema, opts) {
2377
2592
  schema.field("signatureAlgorithm", algorithmIdentifier(ns)),
2378
2593
  schema.field("signatureValue", schema.bitString()),
2379
2594
  ], {
2595
+ /** @internal Exactly three, with no trailing element. X.509 (2019) extended the SIGNED
2596
+ * parameterized type with optional `altAlgorithmIdentifier` and `altSignature` components after
2597
+ * the signature, and then forbade them for every structure it profiles: "only one digital
2598
+ * signature shall be generated, i.e., the altAlgorithmIdentifier and the altSignature components
2599
+ * shall be absent" (clauses 7.2.1, 7.10.2, 11.3 and 14.2). Declaring either as an optional field
2600
+ * here would admit a second signature the text excludes, in every format that composes this. */
2380
2601
  assert: "sequence", arity: { exact: 3 }, code: opts.code, what: opts.what,
2381
2602
  build: function (m, ctx) {
2382
2603
  var tbsMatch = m.fields.toBeSigned.value;
@@ -2452,6 +2673,9 @@ module.exports = {
2452
2673
  DN_SHORT: DN_SHORT,
2453
2674
  time: time,
2454
2675
  algorithmIdentifier: algorithmIdentifier,
2676
+ issuerAndSerialNumber: issuerAndSerialNumber,
2677
+ digestLengths: digestLengths,
2678
+ signatureDigestNames: signatureDigestNames,
2455
2679
  pbkdf2Params: pbkdf2Params,
2456
2680
  pbmac1Params: pbmac1Params,
2457
2681
  spki: spki,
@@ -2484,6 +2708,8 @@ module.exports = {
2484
2708
  crlFixedCriticality: crlFixedCriticality,
2485
2709
  crlEntryFixedCriticality: crlEntryFixedCriticality,
2486
2710
  certExtensionDecoders: certExtensionDecoders,
2711
+ keyUsagePermits: keyUsagePermits,
2712
+ signatureOnlyKeyOid: signatureOnlyKeyOid,
2487
2713
  scopedCertDecoder: scopedCertDecoder,
2488
2714
  crlExtensionDecoders: crlExtensionDecoders,
2489
2715
  decodeExtensionList: decodeExtensionList,