@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
package/lib/x509-sign.js CHANGED
@@ -22,7 +22,12 @@ var x509 = require("./schema-x509");
22
22
  var signScheme = require("./sign-scheme");
23
23
  var guard = require("./guard-all");
24
24
  var intrinsic = require("./guard-intrinsic");
25
- var _isArray = intrinsic.isArray, _bufferEquals = intrinsic.bufferEquals;
25
+ var _isArray = intrinsic.isArray, _bufferEquals = intrinsic.bufferEquals, _isBuffer = intrinsic.isBuffer;
26
+ var _arrSlice = intrinsic.arraySlice, _promiseResolve = intrinsic.uncurry(intrinsic.promiseResolve);
27
+ /** @internal The RECEIVER for the captured `Promise.resolve`, captured too. `resolve` builds through
28
+ * its receiver, so handing it the live `globalThis.Promise` put the capture back under a constructor a
29
+ * replacement could take over and decide what the awaited value became. */
30
+ var _Promise = intrinsic.Promise;
26
31
  var _dnCharCodeAt = intrinsic.uncurry(String.prototype.charCodeAt);
27
32
  var _dnKeys = intrinsic.keys;
28
33
  var _dnHasOwn = intrinsic.hasOwn;
@@ -31,6 +36,7 @@ var schema = require("./schema-engine");
31
36
  var pkix = require("./schema-pkix");
32
37
  var pkiBuild = require("./pki-build");
33
38
  var ct = require("./ct");
39
+ var relatedCert = require("./related-cert");
34
40
  var lint = require("./lint");
35
41
 
36
42
  var CertificateError = frameworkError.CertificateError;
@@ -46,8 +52,11 @@ function O(n) { return oid.byName(n); }
46
52
  var KNOWN_SPEC_KEYS = intrinsic.assign(intrinsic.create(null), {
47
53
  subject: 1, subjectPublicKey: 1, notBefore: 1, notAfter: 1, serialNumber: 1, extensions: 1,
48
54
  });
49
- var KNOWN_ISSUER_CERT_KEYS = intrinsic.assign(intrinsic.create(null), { key: 1, cert: 1 });
50
- var KNOWN_ISSUER_EXPLICIT_KEYS = intrinsic.assign(intrinsic.create(null), { key: 1, name: 1, publicKey: 1 });
55
+ /** @internal `altKey` and `altPublicKey` are the issuer's ALTERNATIVE signing key and its public half,
56
+ * for an ITU-T X.509 (2019) clause 9.8 certificate. They appear in both issuer forms because the
57
+ * alternative key is the issuer's own and is unrelated to how the native issuer was named. */
58
+ var KNOWN_ISSUER_CERT_KEYS = intrinsic.assign(intrinsic.create(null), { key: 1, cert: 1, altKey: 1, altPublicKey: 1 });
59
+ var KNOWN_ISSUER_EXPLICIT_KEYS = intrinsic.assign(intrinsic.create(null), { key: 1, name: 1, publicKey: 1, altKey: 1, altPublicKey: 1 });
51
60
  var KNOWN_SIGN_OPTS = intrinsic.assign(intrinsic.create(null), { digestAlgorithm: 1, pem: 1, pss: 1, profile: 1 });
52
61
 
53
62
  /** @internal The signature the build-time gate lints against, standing in for one that does not exist
@@ -66,6 +75,11 @@ var KNOWN_EXT_KEYS = intrinsic.assign(intrinsic.create(null), {
66
75
  msCertificateTemplate: 1, msEnrollCertType: 1, msCaVersion: 1, msPreviousCertHash: 1,
67
76
  msApplicationPolicies: 1,
68
77
  subjectInfoAccess: 1, subjectDirectoryAttributes: 1, ocspNoCheck: 1,
78
+ relatedCertificate: 1,
79
+ /** @internal The clause 9.8 alternative set. `altSignatureAlgorithm` and `altSignatureValue` are
80
+ * accepted as names so a caller that supplies either is told they are resolved and computed here,
81
+ * rather than being told the name is unknown. */
82
+ subjectAltPublicKeyInfo: 1, altSignatureAlgorithm: 1, altSignatureValue: 1,
69
83
  });
70
84
 
71
85
  var _b = pkiBuild.makeBuilder({ ErrorClass: CertificateError, prefix: "x509", O: O, NS: NS, NAME_SCHEMA: NAME_SCHEMA, SPKI_SCHEMA: SPKI_SCHEMA, EXT_DECODERS: EXT_DECODERS });
@@ -73,6 +87,7 @@ var _encodeName = _b.encodeName, _isEmptyName = _b.isEmptyName, _reqDer = _b.req
73
87
  _assertValidSpki = _b.assertValidSpki, _assertValidExtension = _b.assertValidExtension,
74
88
  _assertCertCriticality = _b.assertCertCriticality,
75
89
  _certLikeFromSpki = _b.certLikeFromSpki, _assertCertVerifies = _b.assertSignatureVerifies,
90
+ _altPublicKeyValueOf = _b.altPublicKeyValueOf,
76
91
  _ext = _b.ext, _extBasicConstraints = _b.extBasicConstraints, _validateBcSpec = _b.validateBcSpec,
77
92
  _extKeyUsage = _b.extKeyUsage, _extExtKeyUsage = _b.extExtKeyUsage, _extSki = _b.extSki,
78
93
  _extNameConstraints = _b.extNameConstraints, _extAia = _b.extAuthorityInfoAccess,
@@ -135,12 +150,26 @@ function _akiFields(val, ctx) {
135
150
  return [keyId, issuerName == null ? null : issuerName, serial == null ? null : serial];
136
151
  }
137
152
 
153
+ /** @internal The extensions this verb owns the value of, so a caller never supplies them pre-encoded. */
154
+ var RESERVED_ALT_EXTS = intrinsic.assign(intrinsic.create(null), (function () {
155
+ var o = intrinsic.create(null);
156
+ o[O("altSignatureValue")] = 1;
157
+ o[O("altSignatureAlgorithm")] = 1;
158
+ o[O("subjectAltPublicKeyInfo")] = 1;
159
+ return o;
160
+ })());
161
+
138
162
  function _buildExtensions(extSpec, ctx) {
139
163
  if (extSpec == null) extSpec = {};
140
164
  if (_isArray(extSpec)) {
141
165
  var seenExt = intrinsic.create(null), arrCa = false, arrKeyCertSign = false, arrPathLen = false, arrNameConstraints = false;
142
166
  var oidBc = O("basicConstraints"), oidKu = O("keyUsage"), oidNc = O("nameConstraints"), oidPm = O("policyMappings");
143
- var arr = extSpec.map(function (e, i) {
167
+ /** @internal The walk goes through `guard.list.copyMap`, never the caller's own `map`. Reached off
168
+ * the array a caller supplied, `map` is that caller's to replace: one returning extension DER the
169
+ * callback below never saw bypassed every per-element rule at once, the duplicate check, the
170
+ * criticality rule, the value decoders and the alternative-signature refusal among them, and the
171
+ * verb then emitted and natively signed a certificate carrying extensions nothing had validated. */
172
+ var arr = guard.list.copyMap(extSpec, function (e, i) {
144
173
  var der = _reqDer(e, "extension");
145
174
  _assertValidExtension(der, i, EXT_DECODERS);
146
175
  var n = asn1.decode(der);
@@ -164,6 +193,22 @@ function _buildExtensions(extSpec, ctx) {
164
193
  else if (extnId === oidKu) { arrKeyCertSign = decoded.keyCertSign === true; }
165
194
  else if (extnId === oidPm) { _assertNoAnyPolicyMapping(decoded); }
166
195
  }
196
+ /** @internal The three alternative-signature extensions are refused HERE as well as in the object
197
+ * form below, and AFTER the decode above so a malformed value is still reported as one. The
198
+ * object form's guards were the only ones, so the pre-encoded array reached none of them: a
199
+ * caller could supply `altSignatureValue` and `altSignatureAlgorithm` outright, and the verb then
200
+ * emitted and natively signed a certificate carrying an alternative signature it never computed
201
+ * or self-checked, which `pki.altSig.verify` refuses for the migrated client the extension exists
202
+ * for. `subjectAltPublicKeyInfo` is refused in this form rather than honored: clause 7.2.2 has
203
+ * all three travel together, the alternative signing side reads the subject key off the OBJECT
204
+ * form only, and a certificate carrying the subject's alternative key with no alternative
205
+ * signature has an alternative half nothing can verify. */
206
+ if (_dnHasOwn(RESERVED_ALT_EXTS, extnId)) {
207
+ throw _err("x509/bad-input", (oid.name(extnId) || extnId) + " is not supplied as a pre-encoded " +
208
+ "extension: this verb computes the alternative signature and resolves its algorithm from " +
209
+ "issuer.altKey, and clause 7.2.2 emits all three together. Pass issuer.altKey with the " +
210
+ "object form of spec.extensions instead (ITU-T X.509 (2019) clause 7.2.2).");
211
+ }
167
212
  return b.raw(der);
168
213
  });
169
214
  if (arrKeyCertSign && !arrCa) throw _err("x509/bad-input", "keyUsage keyCertSign requires basicConstraints cA=TRUE (RFC 5280 sec. 4.2.1.3)");
@@ -273,9 +318,122 @@ function _buildExtensions(extSpec, ctx) {
273
318
  if (extSpec.ocspNoCheck !== true) throw _err("x509/bad-input", "ocspNoCheck is a flag, so it is true or omitted");
274
319
  guard.list.append(out, _ext(O("ocspNoCheck"), false, asn1.build.nullValue()));
275
320
  }
321
+ /** @internal RFC 9763 sec. 4.1 names a second certificate of the same subject by the digest of the
322
+ * whole certificate, and the section says the extension SHOULD NOT be marked critical, so it is
323
+ * emitted non-critical with no option to mark it. */
324
+ if (extSpec.relatedCertificate != null) guard.list.append(out, _ext(O("relatedCertificate"), false, _relatedCertificate(extSpec.relatedCertificate)));
325
+ /** @internal ITU-T X.509 (2019) clause 9.8.2. The subject's alternative public key, and the algorithm
326
+ * identifier of the issuer's alternative signature beside it: clause 7.2.2 requires all three of the
327
+ * alternative extensions together, and the third is filled in by the signing pass below because its
328
+ * value is a signature over these two. The NOTEs in clauses 9.8.2 and 9.8.3 recommend non-critical
329
+ * and the clauses permit either, so these are emitted non-critical and there is no option to mark
330
+ * them: marking them would require every relying party to understand the alternative algorithm. */
331
+ if (ctx.altSigAlgId != null) {
332
+ guard.list.append(out, _ext(O("subjectAltPublicKeyInfo"), false, b.raw(ctx.subjectAltSpki)));
333
+ guard.list.append(out, _ext(O("altSignatureAlgorithm"), false, b.raw(ctx.altSigAlgId)));
334
+ }
276
335
  return out;
277
336
  }
278
337
 
338
+ var KNOWN_ALT_ISSUER_KEYS = intrinsic.assign(intrinsic.create(null), { altKey: 1, altPublicKey: 1 });
339
+ /** @internal The alternative signing side of clause 9.8, resolved before any signing happens. All three
340
+ * extensions travel together (clause 7.2.2), so a subject alternative key without an issuer
341
+ * alternative key is refused and so is the reverse: either alone emits a certificate whose alternative
342
+ * half cannot be verified. `altSignatureValue` is never a caller's to supply, because its value is a
343
+ * signature this verb computes and a supplied one would be emitted unchecked. */
344
+ function _resolveAltSigning(issuer, extSpec, issuerCert, selfSigned) {
345
+ var subjectAltRaw = (extSpec != null && !_isArray(extSpec)) ? extSpec.subjectAltPublicKeyInfo : null;
346
+ var hasAltKey = issuer.altKey != null;
347
+ if (extSpec != null && !_isArray(extSpec) && extSpec.altSignatureValue != null) {
348
+ throw _err("x509/bad-input", "altSignatureValue is computed by this verb from the alternative signing key, so it is not supplied (ITU-T X.509 (2019) clause 7.2.2)");
349
+ }
350
+ if (extSpec != null && !_isArray(extSpec) && extSpec.altSignatureAlgorithm != null) {
351
+ throw _err("x509/bad-input", "altSignatureAlgorithm is resolved from the alternative signing key, so it is not supplied (ITU-T X.509 (2019) clause 9.8.3)");
352
+ }
353
+ /** @internal An `altPublicKey` with no `altKey` is refused rather than passed over. Returning null on
354
+ * the pair of absences read `altPublicKey` as absent too, so an issuer that supplied it and omitted
355
+ * the private half got an ordinary certificate and no indication that the alternative signature it
356
+ * asked for was never made. `pki.crl.sign` refuses the same pair, and a configuration assembled a
357
+ * field at a time is exactly where the omission happens. */
358
+ if (subjectAltRaw == null && !hasAltKey) {
359
+ if (issuer.altPublicKey != null) {
360
+ throw _err("x509/bad-input", "issuer.altPublicKey needs issuer.altKey to sign the alternative signature with (ITU-T X.509 (2019) clause 7.2.2)");
361
+ }
362
+ return null;
363
+ }
364
+ if (subjectAltRaw == null) {
365
+ throw _err("x509/bad-input", "an issuer altKey needs extensions.subjectAltPublicKeyInfo: clause 7.2.2 requires all three alternative extensions together");
366
+ }
367
+ if (!hasAltKey) {
368
+ throw _err("x509/bad-input", "extensions.subjectAltPublicKeyInfo needs an issuer altKey to sign the alternative signature with (ITU-T X.509 (2019) clause 7.2.2)");
369
+ }
370
+ guard.identifier.assertKnownKeys({ altKey: issuer.altKey, altPublicKey: issuer.altPublicKey },
371
+ KNOWN_ALT_ISSUER_KEYS, _err, "x509/bad-input", "unknown alternative issuer field: ");
372
+ var subjectAltSpki = _reqDer(subjectAltRaw, "extensions.subjectAltPublicKeyInfo (the subject's alternative SPKI DER)");
373
+ _assertValidSpki(subjectAltSpki, "extensions.subjectAltPublicKeyInfo");
374
+ if (issuer.altPublicKey == null) {
375
+ throw _err("x509/bad-input", "an issuer altKey needs issuer.altPublicKey, the SPKI of its public half, to resolve the alternative signature algorithm");
376
+ }
377
+ var issuerAltSpki = _reqDer(issuer.altPublicKey, "issuer.altPublicKey (the issuer's alternative SPKI DER)");
378
+ _assertValidSpki(issuerAltSpki, "issuer.altPublicKey");
379
+ /** @internal Bound to the issuer, the way the native signature already is: its key is read OUT of
380
+ * issuer.cert, while this one arrives beside the certificate, so nothing otherwise stops a CA signing
381
+ * with a second alternative pair. Clause 9.8.4 has a relying party check an alternative signature with
382
+ * the issuer's alternative public key, meaning the one the issuer's own certificate publishes, so a
383
+ * signature made with any other key verifies for nobody and the certificate is unusable on the half it
384
+ * was issued for. A self-signed certificate publishes its own alternative key in the extension it is
385
+ * being given, so that is what it is held to. */
386
+ if (issuerCert !== null && issuerCert !== undefined) {
387
+ var published = _altPublicKeyValueOf(issuerCert);
388
+ if (published === null) {
389
+ throw _err("x509/bad-input",
390
+ "issuer.cert publishes no subjectAltPublicKeyInfo, so it names no alternative key for a relying party to check an alternative signature with (ITU-T X.509 (2019) clause 9.8.4)");
391
+ }
392
+ if (!_bufferEquals(published, issuerAltSpki)) {
393
+ throw _err("x509/bad-input",
394
+ "issuer.altPublicKey is not the alternative key issuer.cert publishes in its subjectAltPublicKeyInfo, so the alternative signature would verify under no key a relying party can find (ITU-T X.509 (2019) clause 9.8.4)");
395
+ }
396
+ } else if (selfSigned === true && !_bufferEquals(subjectAltSpki, issuerAltSpki)) {
397
+ throw _err("x509/bad-input",
398
+ "a self-signed certificate publishes its own alternative key, so issuer.altPublicKey must be extensions.subjectAltPublicKeyInfo");
399
+ }
400
+ var altScheme = signScheme.resolveSignScheme(_certLikeFromSpki(issuerAltSpki), { combinedRsaSig: true }, true, _signE);
401
+ return { subjectAltSpki: subjectAltSpki, issuerAltSpki: issuerAltSpki, scheme: altScheme, altSigAlgId: altScheme.sigAlgId };
402
+ }
403
+
404
+ var KNOWN_RELATED_CERTIFICATE_KEYS = intrinsic.assign(intrinsic.create(null), { hashAlgorithm: 1, hashValue: 1, relatedCertificate: 1 });
405
+ /** @internal RelatedCertificate ::= SEQUENCE { hashAlgorithm DigestAlgorithmIdentifier,
406
+ * hashValue OCTET STRING } (RFC 9763 sec. 4.1). `relatedCertificate` hands over the certificate and
407
+ * the digest is computed here, which is the form that cannot name one algorithm while carrying the
408
+ * output of another; `hashAlgorithm` + `hashValue` carries a digest already computed elsewhere. Both
409
+ * at once is refused, since the two can disagree and only one of them would be emitted. */
410
+ function _relatedCertificate(spec) {
411
+ if (spec === null || typeof spec !== "object" || _isBuffer(spec) || _isArray(spec)) {
412
+ throw _err("x509/bad-input", "relatedCertificate must be an object { relatedCertificate } or { hashAlgorithm, hashValue }");
413
+ }
414
+ guard.identifier.assertKnownKeys(spec, KNOWN_RELATED_CERTIFICATE_KEYS, _err, "x509/bad-input", "relatedCertificate has an unknown field: ");
415
+ var hasCert = spec.relatedCertificate != null, hasValue = spec.hashValue != null;
416
+ if (hasCert && hasValue) {
417
+ throw _err("x509/bad-input", "relatedCertificate takes either the related certificate to hash or a hashValue already computed, not both");
418
+ }
419
+ var value;
420
+ if (hasCert) {
421
+ try { value = relatedCert.certificateHash(spec.relatedCertificate, spec.hashAlgorithm); }
422
+ catch (e) { throw _err("x509/bad-input", "relatedCertificate: " + ((e && e.message) || intrinsic.String(e)), e); }
423
+ } else {
424
+ if (!hasValue) throw _err("x509/bad-input", "relatedCertificate needs either the related certificate to hash or a hashAlgorithm with a hashValue");
425
+ if (spec.hashAlgorithm == null) throw _err("x509/bad-input", "relatedCertificate hashValue must be accompanied by the hashAlgorithm that produced it");
426
+ var bytes = _reqDer(spec.hashValue, "relatedCertificate hashValue");
427
+ /** @internal Routed through the same reader that holds a parsed value to its algorithm's length,
428
+ * so a digest this build emits and a digest it reads back are held to one rule. */
429
+ try { value = relatedCert.digestValue(spec.hashAlgorithm, bytes); }
430
+ catch (e) { throw _err("x509/bad-input", "relatedCertificate: " + ((e && e.message) || intrinsic.String(e)), e); }
431
+ }
432
+ var algOid = O(value.hashAlgorithm);
433
+ if (!algOid) throw _err("x509/bad-input", "relatedCertificate hashAlgorithm " + value.hashAlgorithm + " has no registered OID");
434
+ return asn1.build.sequence([asn1.build.sequence([asn1.build.oid(algOid)]), asn1.build.octetString(value.hashValue)]);
435
+ }
436
+
279
437
 
280
438
  /** @internal RFC 6962 sec. 3.1 and sec. 3.2 put these at the two ends of one exchange: the poison
281
439
  * names the precertificate submitted to a log, and the SCT list names the certificate issued once
@@ -589,7 +747,13 @@ function _sign(spec, issuer, opts) {
589
747
  _err, "x509/bad-input", "issuer/subject DN");
590
748
  var selfSignedCert = selfIssued && _b.samePublicKey(issuerSpki, spki);
591
749
  var extSpec = spec.extensions;
592
- var exts = _buildExtensions(extSpec, { spki: spki, issuerSpki: issuerSpki, issuerCert: issuerCert, subjectEmpty: subjectEmpty, selfSigned: selfSignedCert });
750
+ /** @internal `selfSignedCert`, not `selfSigned`: a root is self-signed whether the caller omits the
751
+ * issuer or names one whose name and key are the subject's, and the alternative-key binding applies to
752
+ * both. `selfSigned` is only the omitted form, and it is what the extension builder already passes
753
+ * `selfSignedCert` for the same reason. */
754
+ var alt = _resolveAltSigning(issuer, extSpec, issuerCert, selfSignedCert);
755
+ var exts = _buildExtensions(extSpec, { spki: spki, issuerSpki: issuerSpki, issuerCert: issuerCert, subjectEmpty: subjectEmpty, selfSigned: selfSignedCert,
756
+ altSigAlgId: alt === null ? null : alt.altSigAlgId, subjectAltSpki: alt === null ? null : alt.subjectAltSpki });
593
757
  if (subjectEmpty && !_hasCriticalSan(extSpec)) {
594
758
  throw _err("x509/bad-input", "an empty subject requires a critical subjectAltName (RFC 5280 sec. 4.1.2.6)");
595
759
  }
@@ -602,22 +766,63 @@ function _sign(spec, issuer, opts) {
602
766
  }
603
767
  var version = exts.length ? 3 : 1;
604
768
 
605
- var tbsChildren = [];
606
- if (version !== 1) tbsChildren.push(b.explicit(0, b.integer(BigInt(version - 1))));
607
- tbsChildren.push(serialTlv);
608
- tbsChildren.push(scheme.sigAlgId);
609
- tbsChildren.push(issuerDer);
610
- tbsChildren.push(validityDer);
611
- tbsChildren.push(subjectDer);
612
- tbsChildren.push(b.raw(spki));
613
- if (exts.length) tbsChildren.push(b.explicit(3, b.sequence(exts)));
614
- var tbsDer = b.sequence(tbsChildren);
615
- lint.assertBuildClean("certificate",
616
- b.sequence([tbsDer, scheme.sigAlgId, b.bitString(GATE_PLACEHOLDER_SIG, 0)]),
617
- opts.profile, _err, "x509/profile-violation");
769
+ /** @internal The tbsCertificate over a given extension list, so the two passes an alternative
770
+ * signature needs build it the same way rather than twice. `omitSignatureAlg` drops the `signature`
771
+ * AlgorithmIdentifier, which turns the TBSCertificate into the PreTBSCertificate the alternative
772
+ * signature covers (draft-truskovsky-lamps-pq-hybrid-x509 sec. 4). */
773
+ function tbsOver(extList, omitSignatureAlg) {
774
+ var tbsChildren = [];
775
+ if (version !== 1) tbsChildren.push(b.explicit(0, b.integer(BigInt(version - 1))));
776
+ tbsChildren.push(serialTlv);
777
+ if (omitSignatureAlg !== true) tbsChildren.push(scheme.sigAlgId);
778
+ tbsChildren.push(issuerDer);
779
+ tbsChildren.push(validityDer);
780
+ tbsChildren.push(subjectDer);
781
+ tbsChildren.push(b.raw(spki));
782
+ if (extList.length) tbsChildren.push(b.explicit(3, b.sequence(extList)));
783
+ return b.sequence(tbsChildren);
784
+ }
618
785
 
786
+ /** @internal Clause 7.2.2 fixes the order: the alternative signature is generated first, over the
787
+ * PreTBSCertificate, which carries neither it nor the `signature` AlgorithmIdentifier, and the native
788
+ * signature then covers the tbsCertificate WITH both. Doing it the other way round leaves a native
789
+ * signature that does not cover the alternative one, which is the ordering error the two-pass exists
790
+ * to make impossible. draft-truskovsky-lamps-pq-hybrid-x509 sec. 4.1 adds the `signature` field in
791
+ * step (d), after the alternative signature of step (b), which is why it is absent here. */
792
+ /** @internal The alternative signature is self-checked against issuer.altPublicKey before it is
793
+ * embedded, the same gate the native pass applies to its own signature. Without it an altKey and an
794
+ * altPublicKey that are different keys of one algorithm both resolve to the same scheme, so signing
795
+ * succeeds and what ships is a certificate whose alternative signature does not verify under the
796
+ * alternative key its own issuer names. */
797
+ var altFirst;
798
+ if (alt === null) {
799
+ altFirst = _promiseResolve(_Promise, null);
800
+ } else {
801
+ var altPreimage = tbsOver(exts, true);
802
+ altFirst = signScheme.signOverTbs(alt.scheme, issuer.altKey, altPreimage, _signE)
803
+ .then(function (altSigBytes) {
804
+ return _promiseResolve(_Promise,
805
+ _assertCertVerifies(altPreimage, altSigBytes, alt.issuerAltSpki, alt.scheme)
806
+ ).then(function () { return altSigBytes; });
807
+ });
808
+ }
809
+
810
+ return altFirst.then(function (altSigBytes) {
811
+ var finalExts = exts;
812
+ if (alt !== null) {
813
+ finalExts = _arrSlice(exts);
814
+ guard.list.append(finalExts, _ext(O("altSignatureValue"), false, b.bitString(altSigBytes, 0)));
815
+ }
816
+ var tbsDer = tbsOver(finalExts);
817
+ lint.assertBuildClean("certificate",
818
+ b.sequence([tbsDer, scheme.sigAlgId, b.bitString(GATE_PLACEHOLDER_SIG, 0)]),
819
+ opts.profile, _err, "x509/profile-violation");
820
+ return _signNative(tbsDer);
821
+ });
822
+
823
+ function _signNative(tbsDer) {
619
824
  return signScheme.signOverTbs(scheme, issuer.key, tbsDer, _signE).then(function (sig) {
620
- return Promise.resolve(_assertCertVerifies(tbsDer, sig, issuerSpki, scheme)).then(function () {
825
+ return _promiseResolve(_Promise, _assertCertVerifies(tbsDer, sig, issuerSpki, scheme)).then(function () {
621
826
  var certDer = b.sequence([tbsDer, scheme.sigAlgId, b.bitString(sig, 0)]);
622
827
  return opts.pem ? x509.pemEncode(certDer, "CERTIFICATE") : certDer;
623
828
  });
@@ -625,6 +830,7 @@ function _sign(spec, issuer, opts) {
625
830
  if (e instanceof CertificateError) throw e;
626
831
  throw _err("x509/bad-input", "signing the certificate failed -- the signing key does not match the resolved algorithm or is invalid", e);
627
832
  });
833
+ }
628
834
  }
629
835
 
630
836
  /**
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@blamejs/pki",
3
- "version": "0.8.30",
3
+ "version": "0.8.48",
4
4
  "description": "Pure-JavaScript PKI toolkit that owns its stack — X.509, ASN.1/DER, CMS, PQC-first.",
5
5
  "license": "Apache-2.0",
6
6
  "author": "blamejs contributors",
@@ -57,6 +57,7 @@
57
57
  "index.js",
58
58
  "bin/",
59
59
  "lib/",
60
+ "examples/*.js",
60
61
  "CHANGELOG.md",
61
62
  "LICENSE",
62
63
  "MIGRATING.md",
package/sbom.cdx.json CHANGED
@@ -2,10 +2,10 @@
2
2
  "$schema": "http://cyclonedx.org/schema/bom-1.5.schema.json",
3
3
  "bomFormat": "CycloneDX",
4
4
  "specVersion": "1.5",
5
- "serialNumber": "urn:uuid:b9b5ff1a-3017-4ab5-ab52-4eb332fecb31",
5
+ "serialNumber": "urn:uuid:0f0eb9a3-9ce3-42bb-ba73-3ad3528eccf7",
6
6
  "version": 1,
7
7
  "metadata": {
8
- "timestamp": "2026-09-29T03:10:30.011Z",
8
+ "timestamp": "2026-10-05T06:06:36.873Z",
9
9
  "lifecycles": [
10
10
  {
11
11
  "phase": "build"
@@ -19,14 +19,14 @@
19
19
  }
20
20
  ],
21
21
  "component": {
22
- "bom-ref": "@blamejs/pki@0.8.30",
22
+ "bom-ref": "@blamejs/pki@0.8.48",
23
23
  "type": "application",
24
24
  "name": "pki",
25
- "version": "0.8.30",
25
+ "version": "0.8.48",
26
26
  "scope": "required",
27
27
  "author": "blamejs contributors",
28
28
  "description": "Pure-JavaScript PKI toolkit that owns its stack — X.509, ASN.1/DER, CMS, PQC-first.",
29
- "purl": "pkg:npm/%40blamejs/pki@0.8.30",
29
+ "purl": "pkg:npm/%40blamejs/pki@0.8.48",
30
30
  "properties": [],
31
31
  "externalReferences": [
32
32
  {
@@ -54,7 +54,7 @@
54
54
  "components": [],
55
55
  "dependencies": [
56
56
  {
57
- "ref": "@blamejs/pki@0.8.30",
57
+ "ref": "@blamejs/pki@0.8.48",
58
58
  "dependsOn": []
59
59
  }
60
60
  ]