node-opcua-pki 6.20.0 → 6.22.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/bin/pki.mjs CHANGED
@@ -93,6 +93,9 @@ var init_hostname = __esm({
93
93
 
94
94
  // packages/node-opcua-pki/lib/toolbox/common.ts
95
95
  import assert2 from "assert";
96
+ function isOpaqueSigner(privateKey) {
97
+ return typeof privateKey !== "string" && typeof privateKey.sign === "function";
98
+ }
96
99
  async function resolvePrivateKeyPassphrase(passphrase) {
97
100
  if (passphrase === void 0) {
98
101
  return void 0;
@@ -276,8 +279,9 @@ async function createCertificateSigningRequestAsync(certificateSigningRequestFil
276
279
  assert4(typeof certificateSigningRequestFilename === "string");
277
280
  const subject = params.subject ? new Subject(params.subject).toString() : void 0;
278
281
  displaySubtitle("- Creating a Certificate Signing Request with subtile");
279
- const privateKeyPem = typeof params.privateKey === "string" ? await fs2.promises.readFile(params.privateKey, "utf-8") : coercePrivateKeyPem(params.privateKey);
280
- const privateKey = await pemToPrivateKey(privateKeyPem);
282
+ const privateKey = isOpaqueSigner(params.privateKey) ? params.privateKey : await pemToPrivateKey(
283
+ typeof params.privateKey === "string" ? await fs2.promises.readFile(params.privateKey, "utf-8") : coercePrivateKeyPem(params.privateKey)
284
+ );
281
285
  const { csr } = await createCertificateSigningRequest({
282
286
  privateKey,
283
287
  dns: params.dns,
@@ -287,13 +291,16 @@ async function createCertificateSigningRequestAsync(certificateSigningRequestFil
287
291
  purpose: params.purpose
288
292
  });
289
293
  await fs2.promises.writeFile(certificateSigningRequestFilename, csr, "utf-8");
290
- display(`- privateKey ${typeof params.privateKey === "string" ? params.privateKey : "<in-memory>"}`);
294
+ display(
295
+ `- privateKey ${typeof params.privateKey === "string" ? params.privateKey : isOpaqueSigner(params.privateKey) ? "<opaque-signer>" : "<in-memory>"}`
296
+ );
291
297
  display(`- certificateSigningRequestFilename ${certificateSigningRequestFilename}`);
292
298
  }
293
299
  var init_create_certificate_signing_request = __esm({
294
300
  "packages/node-opcua-pki/lib/toolbox/without_openssl/create_certificate_signing_request.ts"() {
295
301
  "use strict";
296
302
  init_esm_shims();
303
+ init_common();
297
304
  init_display();
298
305
  }
299
306
  });
@@ -327,8 +334,9 @@ async function createSelfSignedCertificateAsync(certificate, params) {
327
334
  subject = subject.toString();
328
335
  const purpose = params.purpose;
329
336
  displayTitle("Generate a certificate request");
330
- const privateKeyPem = typeof params.privateKey === "string" ? await fs3.promises.readFile(params.privateKey, "utf-8") : coercePrivateKeyPem2(params.privateKey);
331
- const privateKey = await pemToPrivateKey2(privateKeyPem);
337
+ const privateKey = isOpaqueSigner(params.privateKey) ? params.privateKey : await pemToPrivateKey2(
338
+ typeof params.privateKey === "string" ? await fs3.promises.readFile(params.privateKey, "utf-8") : coercePrivateKeyPem2(params.privateKey)
339
+ );
332
340
  const { cert } = await createSelfSignedCertificate1({
333
341
  privateKey,
334
342
  notBefore: params.startDate,
@@ -383,11 +391,14 @@ import { drainPendingLocks, withLock } from "@ster5/global-mutex";
383
391
  import chalk3 from "chalk";
384
392
  import chokidar from "chokidar";
385
393
  import {
394
+ caSignerFromKeyOperations,
386
395
  exploreCertificate,
387
396
  exploreCertificateInfo,
388
397
  exploreCertificateRevocationList,
389
398
  generatePrivateKeyFile,
399
+ keyOperationsFromPrivateKey,
390
400
  makeSHA1Thumbprint,
401
+ PrivateKeyUnavailableError,
391
402
  readCertificateChain,
392
403
  readCertificateChainAsync,
393
404
  readCertificateRevocationList,
@@ -519,7 +530,31 @@ var init_certificate_manager = __esm({
519
530
  // even if the consumer forgets to call dispose().
520
531
  static #activeInstances = /* @__PURE__ */ new Set();
521
532
  static #cleanupInstalled = false;
522
- static #installProcessCleanup() {
533
+ static #exitHandler;
534
+ /**
535
+ * Install a best-effort `exit` hook that closes any watcher
536
+ * still open when the process terminates.
537
+ *
538
+ * **This library never terminates the host process.** No
539
+ * SIGINT/SIGTERM handler is installed: deciding how (and
540
+ * whether) to shut down on a signal is the application's
541
+ * responsibility, and a listener registered here would both
542
+ * pre-empt the application's own graceful shutdown and
543
+ * silently suppress Node's default signal behaviour.
544
+ *
545
+ * Nothing here is load-bearing for process exit. The native
546
+ * `fs.watch` handles are `unref()`'d when the watchers are
547
+ * created (see `#readCertificates`), so an undisposed
548
+ * CertificateManager never keeps the event loop alive. This
549
+ * hook is only tidiness on the way out.
550
+ *
551
+ * `exit` rather than `beforeExit`: `beforeExit` fires when
552
+ * the loop merely drains and the loop can subsequently be
553
+ * resurrected, which would leave a still-in-use instance
554
+ * marked Disposed. `exit` is terminal, synchronous-only and
555
+ * cannot alter the exit code.
556
+ */
557
+ static #installExitCleanup() {
523
558
  if (_CertificateManager.#cleanupInstalled) return;
524
559
  _CertificateManager.#cleanupInstalled = true;
525
560
  const closeDanglingWatchers = () => {
@@ -535,13 +570,22 @@ var init_certificate_manager = __esm({
535
570
  }
536
571
  _CertificateManager.#activeInstances.clear();
537
572
  };
538
- process.on("beforeExit", closeDanglingWatchers);
539
- for (const signal of ["SIGINT", "SIGTERM"]) {
540
- process.once(signal, () => {
541
- closeDanglingWatchers();
542
- process.exit();
543
- });
573
+ _CertificateManager.#exitHandler = closeDanglingWatchers;
574
+ process.on("exit", closeDanglingWatchers);
575
+ }
576
+ /**
577
+ * Remove the `exit` hook once the last instance is disposed,
578
+ * so a library that is initialized and disposed repeatedly
579
+ * does not accumulate process listeners. A later
580
+ * `initialize()` re-arms it.
581
+ */
582
+ static #uninstallExitCleanupIfIdle() {
583
+ if (_CertificateManager.#activeInstances.size > 0) return;
584
+ if (_CertificateManager.#exitHandler) {
585
+ process.removeListener("exit", _CertificateManager.#exitHandler);
586
+ _CertificateManager.#exitHandler = void 0;
544
587
  }
588
+ _CertificateManager.#cleanupInstalled = false;
545
589
  }
546
590
  /**
547
591
  * Dispose **all** active CertificateManager instances,
@@ -607,6 +651,9 @@ var init_certificate_manager = __esm({
607
651
  #disableFileWatchers;
608
652
  #privateKeyPassphrase;
609
653
  #privateKeyProvider;
654
+ #keyOperations;
655
+ /** The stable lazy wrap handed out by {@link getKeyOperations} for non-opaque configurations. */
656
+ #lazyLocalKeyOperations;
610
657
  /**
611
658
  * The on-disk key, decrypted once and kept for the instance's lifetime,
612
659
  * so the passphrase (or its resolver function) is consulted at most
@@ -650,6 +697,17 @@ var init_certificate_manager = __esm({
650
697
  this.#disableFileWatchers = options.disableFileWatchers ?? process.env.OPCUA_PKI_DISABLE_FILE_WATCHERS === "true";
651
698
  this.#privateKeyPassphrase = options.privateKeyPassphrase;
652
699
  this.#privateKeyProvider = options.privateKeyProvider;
700
+ this.#keyOperations = options.keyOperations;
701
+ if (this.#keyOperations && this.#privateKeyProvider) {
702
+ throw new Error(
703
+ "CertificateManager: 'keyOperations' and 'privateKeyProvider' are mutually exclusive: one hides the key, the other sources its material"
704
+ );
705
+ }
706
+ if (this.#keyOperations && this.#privateKeyPassphrase) {
707
+ throw new Error(
708
+ "CertificateManager: 'privateKeyPassphrase' is meaningless with 'keyOperations': there is no key material for a passphrase to protect"
709
+ );
710
+ }
653
711
  mkdirRecursiveSync(options.location);
654
712
  if (!fs4.existsSync(this.#location)) {
655
713
  throw new Error(`CertificateManager cannot access location ${this.#location}`);
@@ -691,6 +749,11 @@ var init_certificate_manager = __esm({
691
749
  * authority on what the current key is.
692
750
  */
693
751
  async getPrivateKey() {
752
+ if (this.#keyOperations) {
753
+ throw new PrivateKeyUnavailableError(
754
+ "CertificateManager.getPrivateKey is not available when keyOperations is configured: the private key is held by the key-operations provider (HSM/KMS) and cannot be read \u2014 use getKeyOperations() instead"
755
+ );
756
+ }
694
757
  if (this.#privateKeyProvider) {
695
758
  return await this.#privateKeyProvider.getPrivateKey();
696
759
  }
@@ -716,6 +779,75 @@ var init_certificate_manager = __esm({
716
779
  }
717
780
  /** In-flight first read of the on-disk key, so concurrent callers share one passphrase resolution. */
718
781
  #privateKeyPromise;
782
+ /**
783
+ * True when this manager's key is opaque — configured through
784
+ * `keyOperations`, held by an HSM/KMS, never obtainable as material.
785
+ * When true, {@link getPrivateKey} throws `PrivateKeyUnavailableError`
786
+ * and {@link getKeyOperations} is the only way to use the key.
787
+ */
788
+ isPrivateKeyOpaque() {
789
+ return !!this.#keyOperations;
790
+ }
791
+ /**
792
+ * The key as an opaque {@link IKeyOperations} — the recommended way to
793
+ * *use* the private key regardless of where it lives.
794
+ *
795
+ * Returns the configured `keyOperations` object when the key is opaque.
796
+ * Otherwise returns a stable lazy wrap over {@link getPrivateKey}: its
797
+ * methods resolve the key on first use (disk read, passphrase,
798
+ * `privateKeyProvider` — all async), so the wrap offers no synchronous
799
+ * fast path; callers that need one resolve the key themselves and build
800
+ * a `LocalKeyOperations` over it. The wrap follows key rotation: a
801
+ * `privateKeyProvider` that starts returning a different key gets a
802
+ * fresh underlying `LocalKeyOperations`.
803
+ */
804
+ getKeyOperations() {
805
+ if (this.#keyOperations) {
806
+ return this.#keyOperations;
807
+ }
808
+ if (!this.#lazyLocalKeyOperations) {
809
+ let cached;
810
+ const resolve = async () => {
811
+ const key = await this.getPrivateKey();
812
+ if (!cached || cached.key !== key) {
813
+ cached = { key, ops: keyOperationsFromPrivateKey(key) };
814
+ }
815
+ return cached.ops;
816
+ };
817
+ this.#lazyLocalKeyOperations = {
818
+ sign: async (data, params) => (await resolve()).sign(data, params),
819
+ decryptBlock: async (block, params) => (await resolve()).decryptBlock(block, params),
820
+ getKeyMetadata: async () => (await resolve()).getKeyMetadata(),
821
+ getPublicKey: async () => (await resolve()).getPublicKey()
822
+ };
823
+ }
824
+ return this.#lazyLocalKeyOperations;
825
+ }
826
+ /**
827
+ * Fail closed at `initialize()` time, not on the first certificate
828
+ * operation, if the key cannot actually be used: wrong/missing
829
+ * passphrase on an encrypted key, a broken `privateKeyProvider`, or an
830
+ * unreachable `keyOperations` provider (probed via `getKeyMetadata`).
831
+ */
832
+ async #probePrivateKey() {
833
+ if (this.#keyOperations) {
834
+ await this.#keyOperations.getKeyMetadata();
835
+ return;
836
+ }
837
+ await this.getPrivateKey();
838
+ }
839
+ /**
840
+ * The key to hand to a certificate-issuance primitive: the raw
841
+ * {@link PrivateKey} for a local configuration, or a {@link CaSigner}
842
+ * adapted from `keyOperations` when the key is opaque — which requires
843
+ * the provider to implement `getPublicKey` (the adapter says so if not).
844
+ */
845
+ async #resolveSigningKey() {
846
+ if (this.#keyOperations) {
847
+ return caSignerFromKeyOperations(this.#keyOperations);
848
+ }
849
+ return await this.getPrivateKey();
850
+ }
719
851
  /**
720
852
  * Enable, disable, or rotate the passphrase protecting the on-disk
721
853
  * private key: decrypt with `oldPassphrase` (omit if the key is
@@ -737,6 +869,11 @@ var init_certificate_manager = __esm({
737
869
  * disk file for this method to rewrite).
738
870
  */
739
871
  async reencryptPrivateKey(oldPassphrase, newPassphrase) {
872
+ if (this.#keyOperations) {
873
+ throw new PrivateKeyUnavailableError(
874
+ "reencryptPrivateKey: not supported when keyOperations is configured \u2014 there is no key material to rewrite"
875
+ );
876
+ }
740
877
  if (this.#privateKeyProvider) {
741
878
  throw new Error("reencryptPrivateKey: not supported when a privateKeyProvider is configured");
742
879
  }
@@ -966,23 +1103,19 @@ var init_certificate_manager = __esm({
966
1103
  return "BadCertificateUntrusted" /* BadCertificateUntrusted */;
967
1104
  }
968
1105
  }
969
- const _c2 = chain[1] ? exploreCertificateInfo(chain[1]) : "non";
970
- debugLog("chain[1] info=", _c2);
971
- const certificateInfo = exploreCertificateInfo(chain[0]);
1106
+ const { validity } = exploreCertificate(chain[0]).tbsCertificate;
972
1107
  const now = /* @__PURE__ */ new Date();
973
1108
  let isTimeInvalid = false;
974
- if (certificateInfo.notBefore.getTime() > now.getTime()) {
1109
+ if (validity.notBefore.getTime() > now.getTime()) {
975
1110
  debugLog(
976
- `${chalk3.red("certificate is invalid : certificate is not active yet !")} not before date =${certificateInfo.notBefore}`
1111
+ `${chalk3.red("certificate is invalid : certificate is not active yet !")} not before date =${validity.notBefore}`
977
1112
  );
978
1113
  if (!options.acceptPendingCertificate) {
979
1114
  isTimeInvalid = true;
980
1115
  }
981
1116
  }
982
- if (certificateInfo.notAfter.getTime() <= now.getTime()) {
983
- debugLog(
984
- `${chalk3.red("certificate is invalid : certificate has expired !")} not after date =${certificateInfo.notAfter}`
985
- );
1117
+ if (validity.notAfter.getTime() <= now.getTime()) {
1118
+ debugLog(`${chalk3.red("certificate is invalid : certificate has expired !")} not after date =${validity.notAfter}`);
986
1119
  if (!options.acceptOutdatedCertificate) {
987
1120
  isTimeInvalid = true;
988
1121
  }
@@ -1021,7 +1154,7 @@ var init_certificate_manager = __esm({
1021
1154
  const chain = coerceCertificateChain(certificate);
1022
1155
  for (const element of chain) {
1023
1156
  try {
1024
- exploreCertificateInfo(element);
1157
+ exploreCertificate(element);
1025
1158
  } catch (_err) {
1026
1159
  return "BadCertificateInvalid" /* BadCertificateInvalid */;
1027
1160
  }
@@ -1074,7 +1207,7 @@ var init_certificate_manager = __esm({
1074
1207
  this.#initializingPromise = void 0;
1075
1208
  this.state = 2 /* Initialized */;
1076
1209
  _CertificateManager.#activeInstances.add(this);
1077
- _CertificateManager.#installProcessCleanup();
1210
+ _CertificateManager.#installExitCleanup();
1078
1211
  }
1079
1212
  async #initialize() {
1080
1213
  const pkiDir = this.#location;
@@ -1089,7 +1222,7 @@ var init_certificate_manager = __esm({
1089
1222
  mkdirRecursiveSync(path2.join(pkiDir, "issuers"));
1090
1223
  mkdirRecursiveSync(path2.join(pkiDir, "issuers/certs"));
1091
1224
  mkdirRecursiveSync(path2.join(pkiDir, "issuers/crl"));
1092
- const ownsDiskKey = !this.#privateKeyProvider;
1225
+ const ownsDiskKey = !this.#privateKeyProvider && !this.#keyOperations;
1093
1226
  const needsKeyGeneration = ownsDiskKey && !fs4.existsSync(this.privateKey);
1094
1227
  const needsKeyEncryption = ownsDiskKey && !needsKeyGeneration && this.#privateKeyPassphrase !== void 0 && !isEncryptedPrivateKeyFile(this.privateKey);
1095
1228
  if (!fs4.existsSync(this.configFile) || needsKeyGeneration || needsKeyEncryption) {
@@ -1115,14 +1248,14 @@ var init_certificate_manager = __esm({
1115
1248
  if (ownsDiskKey) {
1116
1249
  restrictPrivateFilePermissions(this.privateKey, 384);
1117
1250
  }
1118
- await this.getPrivateKey();
1251
+ await this.#probePrivateKey();
1119
1252
  await this.#readCertificates();
1120
1253
  });
1121
1254
  } else {
1122
1255
  if (ownsDiskKey) {
1123
1256
  restrictPrivateFilePermissions(this.privateKey, 384);
1124
1257
  }
1125
- await this.getPrivateKey();
1258
+ await this.#probePrivateKey();
1126
1259
  await this.#readCertificates();
1127
1260
  }
1128
1261
  }
@@ -1161,6 +1294,7 @@ var init_certificate_manager = __esm({
1161
1294
  this.#cachedPrivateKey = void 0;
1162
1295
  this.#privateKeyPromise = void 0;
1163
1296
  _CertificateManager.#activeInstances.delete(this);
1297
+ _CertificateManager.#uninstallExitCleanupIfIdle();
1164
1298
  }
1165
1299
  }
1166
1300
  /**
@@ -1206,7 +1340,7 @@ var init_certificate_manager = __esm({
1206
1340
  if (typeof params.applicationUri !== "string") {
1207
1341
  throw new Error("createSelfSignedCertificate: expecting applicationUri to be a string");
1208
1342
  }
1209
- if (!this.#privateKeyProvider && !fs4.existsSync(this.privateKey)) {
1343
+ if (!this.#privateKeyProvider && !this.#keyOperations && !fs4.existsSync(this.privateKey)) {
1210
1344
  throw new Error(`Cannot find private key ${this.privateKey}`);
1211
1345
  }
1212
1346
  let certificateFilename = path2.join(this.rootDir, "own/certs/self_signed_certificate.pem");
@@ -1215,7 +1349,7 @@ var init_certificate_manager = __esm({
1215
1349
  ...params,
1216
1350
  rootDir: this.rootDir,
1217
1351
  configFile: this.configFile,
1218
- privateKey: await this.getPrivateKey(),
1352
+ privateKey: await this.#resolveSigningKey(),
1219
1353
  subject: params.subject || "CN=FIXME"
1220
1354
  };
1221
1355
  await this.withLock2(async () => {
@@ -1243,7 +1377,7 @@ var init_certificate_manager = __esm({
1243
1377
  ...params,
1244
1378
  rootDir: path2.resolve(this.rootDir),
1245
1379
  configFile: path2.resolve(this.configFile),
1246
- privateKey: await this.getPrivateKey()
1380
+ privateKey: await this.#resolveSigningKey()
1247
1381
  };
1248
1382
  return await this.withLock2(async () => {
1249
1383
  const now = /* @__PURE__ */ new Date();
@@ -1509,17 +1643,12 @@ var init_certificate_manager = __esm({
1509
1643
  return "BadSecurityChecksFailed" /* BadSecurityChecksFailed */;
1510
1644
  }
1511
1645
  if (!opts.acceptExpiredCertificate) {
1512
- let certDetails;
1513
- try {
1514
- certDetails = exploreCertificateInfo(currentCert);
1515
- } catch (_err) {
1516
- return "BadCertificateInvalid" /* BadCertificateInvalid */;
1517
- }
1646
+ const { validity } = currentInfo.tbsCertificate;
1518
1647
  const now = /* @__PURE__ */ new Date();
1519
- if (certDetails.notBefore.getTime() > now.getTime()) {
1648
+ if (validity.notBefore.getTime() > now.getTime()) {
1520
1649
  return "BadCertificateTimeInvalid" /* BadCertificateTimeInvalid */;
1521
1650
  }
1522
- if (certDetails.notAfter.getTime() <= now.getTime()) {
1651
+ if (validity.notAfter.getTime() <= now.getTime()) {
1523
1652
  return depth === 1 ? "BadCertificateTimeInvalid" /* BadCertificateTimeInvalid */ : "BadCertificateIssuerTimeInvalid" /* BadCertificateIssuerTimeInvalid */;
1524
1653
  }
1525
1654
  }
@@ -1869,6 +1998,7 @@ var init_certificate_manager = __esm({
1869
1998
  const chokidarOptions = {
1870
1999
  usePolling,
1871
2000
  ...usePolling ? { interval: pollingInterval } : {},
2001
+ depth: 0,
1872
2002
  persistent: false
1873
2003
  };
1874
2004
  const allCapturedHandles = [];
@@ -2145,21 +2275,17 @@ function getEnvironmentVarNames() {
2145
2275
  return { key: varName, pattern: `\\$ENV\\:\\:${varName}` };
2146
2276
  });
2147
2277
  }
2278
+ function buildSubjectAltNameString(params) {
2279
+ return [
2280
+ `URI:${params.applicationUri}`,
2281
+ ...(params.dns ?? []).map((d) => `DNS:${d}`),
2282
+ ...(params.ip ?? []).map((d) => `IP:${d}`)
2283
+ ].join(", ");
2284
+ }
2148
2285
  function processAltNames(params) {
2149
2286
  params.dns = params.dns || [];
2150
2287
  params.ip = params.ip || [];
2151
- let subjectAltName = [];
2152
- subjectAltName.push(`URI:${params.applicationUri}`);
2153
- subjectAltName = [].concat(
2154
- subjectAltName,
2155
- params.dns.map((d) => `DNS:${d}`)
2156
- );
2157
- subjectAltName = [].concat(
2158
- subjectAltName,
2159
- params.ip.map((d) => `IP:${d}`)
2160
- );
2161
- const subjectAltNameString = subjectAltName.join(", ");
2162
- setEnv("ALTNAME", subjectAltNameString);
2288
+ setEnv("ALTNAME", buildSubjectAltNameString(params));
2163
2289
  }
2164
2290
  var SAFE_ENV_PASSTHROUGH, exportedEnvVars;
2165
2291
  var init_env = __esm({
@@ -2511,9 +2637,6 @@ import chalk5 from "chalk";
2511
2637
  function passinArg(passphrase = "") {
2512
2638
  return { args: ["-passin", `env:${PASSIN_ENV_VAR}`], env: { [PASSIN_ENV_VAR]: passphrase } };
2513
2639
  }
2514
- function passoutArg(passphrase = "") {
2515
- return { args: ["-passout", `env:${PASSOUT_ENV_VAR}`], env: { [PASSOUT_ENV_VAR]: passphrase } };
2516
- }
2517
2640
  function renderForDisplay(file, args) {
2518
2641
  return [file, ...args].map((a) => a === "" || /[\s"'`$\\]/.test(a) ? JSON.stringify(a) : a).join(" ");
2519
2642
  }
@@ -2534,7 +2657,7 @@ async function execute2(file, args, options) {
2534
2657
  stdio: ["ignore", "pipe", "pipe"]
2535
2658
  });
2536
2659
  const fail = (message) => {
2537
- if (!options.hideErrorMessage) {
2660
+ if (!options.hideErrorMessage && !g_config.silent) {
2538
2661
  const fence = "###########################################";
2539
2662
  console.error(chalk5.bgWhiteBright.redBright(`${fence} OPENSSL ERROR ${fence}`));
2540
2663
  console.error(chalk5.bgWhiteBright.redBright(`CWD = ${options.cwd}`));
@@ -2628,7 +2751,7 @@ async function execute_openssl(args, options) {
2628
2751
  await ensure_openssl_installed();
2629
2752
  return await execute2(opensslPath, args, options);
2630
2753
  }
2631
- var opensslPath, n, PASSIN_ENV_VAR, PASSOUT_ENV_VAR;
2754
+ var opensslPath, n, PASSIN_ENV_VAR;
2632
2755
  var init_execute_openssl = __esm({
2633
2756
  "packages/node-opcua-pki/lib/toolbox/with_openssl/execute_openssl.ts"() {
2634
2757
  "use strict";
@@ -2640,7 +2763,6 @@ var init_execute_openssl = __esm({
2640
2763
  init_install_prerequisite();
2641
2764
  n = makePath;
2642
2765
  PASSIN_ENV_VAR = "NODE_OPCUA_PKI_OPENSSL_PASSIN";
2643
- PASSOUT_ENV_VAR = "NODE_OPCUA_PKI_OPENSSL_PASSOUT";
2644
2766
  }
2645
2767
  });
2646
2768
 
@@ -2656,19 +2778,27 @@ function openssl_require2DigitYearInDate() {
2656
2778
  }
2657
2779
  return g_config.opensslVersion.match(/OpenSSL 0\.9/);
2658
2780
  }
2659
- function stripConditionalBlocks(template) {
2781
+ function stripConditionalBlocks(template, envOverrides) {
2660
2782
  return template.replace(/\{\{#([A-Z_][A-Z0-9_]*)\}\}([\s\S]*?)\{\{\/\1\}\}\r?\n?/g, (_match, key, content) => {
2661
- const keep = hasEnv(key) && getEnv(key) !== "";
2783
+ const keep = envOverrides && Object.prototype.hasOwnProperty.call(envOverrides, key) ? envOverrides[key] !== "" : hasEnv(key) && getEnv(key) !== "";
2662
2784
  return keep ? content : "";
2663
2785
  });
2664
2786
  }
2665
- function generateStaticConfig(configPath, options) {
2787
+ function generateStaticConfig(configPath, options, envOverrides) {
2666
2788
  const prePath = options?.cwd || "";
2667
2789
  const originalFilename = !path4.isAbsolute(configPath) ? path4.join(prePath, configPath) : configPath;
2668
2790
  let staticConfig = fs7.readFileSync(originalFilename, { encoding: "utf8" });
2669
- staticConfig = stripConditionalBlocks(staticConfig);
2791
+ staticConfig = stripConditionalBlocks(staticConfig, envOverrides);
2670
2792
  for (const envVar of getEnvironmentVarNames()) {
2671
- staticConfig = staticConfig.replace(new RegExp(envVar.pattern, "gi"), getEnv(envVar.key));
2793
+ if (envOverrides && Object.prototype.hasOwnProperty.call(envOverrides, envVar.key)) {
2794
+ continue;
2795
+ }
2796
+ staticConfig = staticConfig.replace(new RegExp(envVar.pattern, "gi"), () => getEnv(envVar.key));
2797
+ }
2798
+ if (envOverrides) {
2799
+ for (const [key, value] of Object.entries(envOverrides)) {
2800
+ staticConfig = staticConfig.replace(new RegExp(`\\$ENV\\:\\:${key}`, "gi"), () => value);
2801
+ }
2672
2802
  }
2673
2803
  const staticConfigPath = `${configPath}.${process.pid}-${_counter++}.tmp`;
2674
2804
  const temporaryConfigPath = !path4.isAbsolute(configPath) ? path4.join(prePath, staticConfigPath) : staticConfigPath;
@@ -2679,6 +2809,9 @@ function generateStaticConfig(configPath, options) {
2679
2809
  return temporaryConfigPath;
2680
2810
  }
2681
2811
  }
2812
+ async function cleanupStaticConfig(configFile, options) {
2813
+ await fs7.promises.rm(path4.resolve(options?.cwd ?? "", configFile), { force: true });
2814
+ }
2682
2815
  async function getPublicKeyFromPrivateKey(privateKeyFilename, publicKeyFilename, passphrase) {
2683
2816
  assert7(fs7.existsSync(privateKeyFilename));
2684
2817
  const passin = passinArg(passphrase);
@@ -2747,26 +2880,30 @@ async function createCertificateSigningRequestWithOpenSSL(certificateSigningRequ
2747
2880
  assert8(typeof certificateSigningRequestFilename === "string");
2748
2881
  processAltNames(params);
2749
2882
  const configFile = generateStaticConfig(params.configFile, { cwd: params.rootDir });
2750
- const options = { cwd: params.rootDir, openssl_conf: path5.relative(params.rootDir, configFile) };
2751
- const subject = params.subject ? new Subject3(params.subject).toString() : void 0;
2752
- displaySubtitle("- Creating a Certificate Signing Request with openssl");
2753
- await execute_openssl(
2754
- [
2755
- "req",
2756
- "-new",
2757
- "-sha256",
2758
- "-batch",
2759
- "-text",
2760
- "-config",
2761
- n3(configFile),
2762
- "-key",
2763
- n3(params.privateKey),
2764
- ...subject ? ["-subj", subject] : [],
2765
- "-out",
2766
- n3(certificateSigningRequestFilename)
2767
- ],
2768
- options
2769
- );
2883
+ try {
2884
+ const options = { cwd: params.rootDir, openssl_conf: path5.relative(params.rootDir, configFile) };
2885
+ const subject = params.subject ? new Subject3(params.subject).toString() : void 0;
2886
+ displaySubtitle("- Creating a Certificate Signing Request with openssl");
2887
+ await execute_openssl(
2888
+ [
2889
+ "req",
2890
+ "-new",
2891
+ "-sha256",
2892
+ "-batch",
2893
+ "-text",
2894
+ "-config",
2895
+ n3(configFile),
2896
+ "-key",
2897
+ n3(params.privateKey),
2898
+ ...subject ? ["-subj", subject] : [],
2899
+ "-out",
2900
+ n3(certificateSigningRequestFilename)
2901
+ ],
2902
+ options
2903
+ );
2904
+ } finally {
2905
+ await cleanupStaticConfig(configFile, { cwd: params.rootDir });
2906
+ }
2770
2907
  }
2771
2908
  var n3;
2772
2909
  var init_create_certificate_signing_request2 = __esm({
@@ -2797,57 +2934,875 @@ var init_with_openssl = __esm({
2797
2934
  }
2798
2935
  });
2799
2936
 
2800
- // packages/node-opcua-pki/lib/pki/toolbox_pfx.ts
2801
- import assert9 from "assert";
2937
+ // packages/node-opcua-pki/lib/ca/core/ca_database.ts
2802
2938
  import fs9 from "fs";
2803
- async function createPFX(options) {
2804
- const { certificateFile, privateKeyFile, privateKeyPassphrase, outputFile, passphrase = "", caCertificateFiles } = options;
2805
- assert9(fs9.existsSync(certificateFile), `Certificate file does not exist: ${certificateFile}`);
2806
- assert9(fs9.existsSync(privateKeyFile), `Private key file does not exist: ${privateKeyFile}`);
2807
- const args = ["pkcs12", "-export", "-in", n4(certificateFile), "-inkey", n4(privateKeyFile)];
2808
- if (caCertificateFiles) {
2809
- for (const caFile of caCertificateFiles) {
2810
- assert9(fs9.existsSync(caFile), `CA certificate file does not exist: ${caFile}`);
2811
- args.push("-certfile", n4(caFile));
2812
- }
2939
+ import path6 from "path";
2940
+ import { convertPEMtoDER, readCertificatePEM } from "node-opcua-crypto";
2941
+ function parseOpenSSLDate(dateStr) {
2942
+ const raw = dateStr?.split(",")[0] ?? "";
2943
+ if (raw.length < 12) return "";
2944
+ const yy = parseInt(raw.substring(0, 2), 10);
2945
+ const year = yy >= 70 ? 1900 + yy : 2e3 + yy;
2946
+ const month = raw.substring(2, 4);
2947
+ const day = raw.substring(4, 6);
2948
+ const hour = raw.substring(6, 8);
2949
+ const min = raw.substring(8, 10);
2950
+ const sec = raw.substring(10, 12);
2951
+ return `${year}-${month}-${day}T${hour}:${min}:${sec}Z`;
2952
+ }
2953
+ function formatOpenSSLDate(date) {
2954
+ const yy = String(date.getUTCFullYear() % 100).padStart(2, "0");
2955
+ const mm = String(date.getUTCMonth() + 1).padStart(2, "0");
2956
+ const dd = String(date.getUTCDate()).padStart(2, "0");
2957
+ const hh = String(date.getUTCHours()).padStart(2, "0");
2958
+ const mi = String(date.getUTCMinutes()).padStart(2, "0");
2959
+ const ss = String(date.getUTCSeconds()).padStart(2, "0");
2960
+ return `${yy}${mm}${dd}${hh}${mi}${ss}Z`;
2961
+ }
2962
+ function parseRevocationReason(dateStr) {
2963
+ const parts = dateStr?.split(",");
2964
+ return parts && parts.length > 1 ? parts[1] : void 0;
2965
+ }
2966
+ function evenLengthHex(value) {
2967
+ const hex = value.toString(16).toUpperCase();
2968
+ return hex.length % 2 === 0 ? hex : `0${hex}`;
2969
+ }
2970
+ function escapeIndexField(value) {
2971
+ return value.replace(/[\x00-\x1f\x7f\\]/g, (c) => `\\x${c.charCodeAt(0).toString(16).toUpperCase().padStart(2, "0")}`);
2972
+ }
2973
+ function assertHexSerial(serial) {
2974
+ if (!/^[0-9A-Fa-f]+$/.test(serial)) {
2975
+ throw new Error(`Invalid certificate serial number: ${JSON.stringify(serial)}`);
2813
2976
  }
2814
- const passin = passinArg(privateKeyPassphrase);
2815
- const passout = passoutArg(passphrase);
2816
- args.push("-out", n4(outputFile), ...passin.args, ...passout.args);
2817
- await execute_openssl(args, { env: { ...passin.env, ...passout.env } });
2977
+ return serial.toUpperCase();
2818
2978
  }
2819
- var n4;
2820
- var init_toolbox_pfx = __esm({
2821
- "packages/node-opcua-pki/lib/pki/toolbox_pfx.ts"() {
2979
+ var CaDatabase;
2980
+ var init_ca_database = __esm({
2981
+ "packages/node-opcua-pki/lib/ca/core/ca_database.ts"() {
2822
2982
  "use strict";
2823
2983
  init_esm_shims();
2824
- init_common2();
2825
- init_execute_openssl();
2826
- n4 = makePath;
2984
+ CaDatabase = class {
2985
+ #rootDir;
2986
+ constructor(rootDir) {
2987
+ this.#rootDir = rootDir;
2988
+ }
2989
+ /** Path to the OpenSSL certificate database file (`index.txt`). */
2990
+ get indexFile() {
2991
+ return path6.join(this.#rootDir, "index.txt");
2992
+ }
2993
+ /**
2994
+ * Parse the OpenSSL `index.txt` certificate database.
2995
+ *
2996
+ * Each line has tab-separated fields:
2997
+ * ```
2998
+ * status expiry [revocationDate] serial unknown subject
2999
+ * ```
3000
+ *
3001
+ * - status: `V` (valid), `R` (revoked), `E` (expired)
3002
+ * - expiry: `YYMMDDHHmmssZ`
3003
+ * - revocationDate: present only for revoked certs
3004
+ * - serial: hex string
3005
+ * - unknown: always `"unknown"`
3006
+ * - subject: X.500 slash-delimited string
3007
+ */
3008
+ readIndex() {
3009
+ const indexPath = this.indexFile;
3010
+ if (!fs9.existsSync(indexPath)) {
3011
+ return [];
3012
+ }
3013
+ const content = fs9.readFileSync(indexPath, "utf-8");
3014
+ const lines = content.split("\n").filter((l) => l.trim().length > 0);
3015
+ const records = [];
3016
+ for (const line of lines) {
3017
+ const fields = line.split(" ");
3018
+ if (fields.length < 4) continue;
3019
+ const statusChar = fields[0];
3020
+ const expiryStr = fields[1];
3021
+ let serial;
3022
+ let subject;
3023
+ let revocationDate;
3024
+ if (statusChar === "R") {
3025
+ revocationDate = fields[2];
3026
+ serial = fields[3];
3027
+ subject = fields.length >= 6 ? fields[5] : "";
3028
+ } else {
3029
+ serial = fields[3];
3030
+ subject = fields.length >= 6 ? fields[5] : "";
3031
+ }
3032
+ let status;
3033
+ switch (statusChar) {
3034
+ case "V":
3035
+ status = "valid";
3036
+ break;
3037
+ case "R":
3038
+ status = "revoked";
3039
+ break;
3040
+ case "E":
3041
+ status = "expired";
3042
+ break;
3043
+ default:
3044
+ continue;
3045
+ }
3046
+ records.push({
3047
+ serial,
3048
+ status,
3049
+ subject,
3050
+ expiryDate: parseOpenSSLDate(expiryStr),
3051
+ revocationDate: revocationDate ? parseOpenSSLDate(revocationDate) : void 0,
3052
+ reason: revocationDate ? parseRevocationReason(revocationDate) : void 0
3053
+ });
3054
+ }
3055
+ return records;
3056
+ }
3057
+ /** Look up one record by serial number (case-insensitive). */
3058
+ findBySerial(serial) {
3059
+ const upper = serial.toUpperCase();
3060
+ return this.readIndex().find((r) => r.serial.toUpperCase() === upper);
3061
+ }
3062
+ /**
3063
+ * Read a specific issued certificate by serial number.
3064
+ *
3065
+ * OpenSSL stores signed certificates in the `certs/` directory using
3066
+ * the naming convention `<SERIAL>.pem`.
3067
+ *
3068
+ * @param serial - hex-encoded serial number (e.g. `"1000"`)
3069
+ * @returns the DER buffer, or `undefined` if not found
3070
+ */
3071
+ getCertificateBySerial(serial) {
3072
+ const upper = serial.toUpperCase();
3073
+ const certFile = path6.join(this.#rootDir, "certs", `${upper}.pem`);
3074
+ if (!fs9.existsSync(certFile)) {
3075
+ return void 0;
3076
+ }
3077
+ const pem = readCertificatePEM(certFile);
3078
+ return convertPEMtoDER(pem);
3079
+ }
3080
+ /**
3081
+ * Read-increment-write a hex counter file (`serial`/`crlnumber`),
3082
+ * returning the value to hand out — matching `openssl ca`'s own
3083
+ * behavior: the file holds the *next* value to assign, and is bumped
3084
+ * to the following one immediately after being read. A `.old` backup
3085
+ * of the pre-bump content is kept, as `openssl` itself does.
3086
+ */
3087
+ #nextHexCounter(fileName) {
3088
+ const counterFile = path6.join(this.#rootDir, fileName);
3089
+ const current = BigInt(`0x${fs9.readFileSync(counterFile, "utf-8").trim()}`);
3090
+ fs9.copyFileSync(counterFile, `${counterFile}.old`);
3091
+ fs9.writeFileSync(counterFile, evenLengthHex(current + 1n));
3092
+ return evenLengthHex(current);
3093
+ }
3094
+ /** Hand out the next certificate serial number, bumping the `serial` file. */
3095
+ nextSerial() {
3096
+ return this.#nextHexCounter("serial");
3097
+ }
3098
+ /** Hand out the next CRL number, bumping the `crlnumber` file. */
3099
+ nextCrlNumber() {
3100
+ return this.#nextHexCounter("crlnumber");
3101
+ }
3102
+ /** Append a newly-issued certificate's `V` (valid) row to `index.txt`. */
3103
+ appendIssued(record) {
3104
+ const serial = assertHexSerial(record.serial);
3105
+ const subject = escapeIndexField(record.subject);
3106
+ const line = `V ${formatOpenSSLDate(record.expiryDate)} ${serial} unknown ${subject}
3107
+ `;
3108
+ fs9.appendFileSync(this.indexFile, line);
3109
+ }
3110
+ /**
3111
+ * Rewrite a certificate's `index.txt` row from `V` to `R` (revoked).
3112
+ * Throws if the serial is not found, or is already revoked — the same
3113
+ * "already revoked" case `openssl ca -revoke` itself rejects.
3114
+ */
3115
+ markRevoked(serial, revocationDate, reason) {
3116
+ const upper = assertHexSerial(serial);
3117
+ if (!/^[A-Za-z]+$/.test(reason)) {
3118
+ throw new Error(`Invalid CRL reason: ${JSON.stringify(reason)}`);
3119
+ }
3120
+ const lines = fs9.readFileSync(this.indexFile, "utf-8").split("\n").filter((l) => l.trim().length > 0);
3121
+ let found = false;
3122
+ const rewritten = lines.map((line) => {
3123
+ const fields = line.split(" ");
3124
+ if (fields.length < 4 || fields[3]?.toUpperCase() !== upper) {
3125
+ return line;
3126
+ }
3127
+ if (fields[0] === "R") {
3128
+ throw new Error(`Certificate ${upper} is already revoked`);
3129
+ }
3130
+ found = true;
3131
+ const subject = fields.length >= 6 ? fields[5] : "";
3132
+ return `R ${fields[1]} ${formatOpenSSLDate(revocationDate)},${reason} ${upper} unknown ${subject}`;
3133
+ });
3134
+ if (!found) {
3135
+ throw new Error(`Certificate ${upper} not found in the certificate database`);
3136
+ }
3137
+ fs9.writeFileSync(this.indexFile, `${rewritten.join("\n")}
3138
+ `);
3139
+ }
3140
+ /** Store a signed certificate's PEM under `certs/<SERIAL>.pem`, as `openssl ca` does. */
3141
+ storeCertificate(serial, pem) {
3142
+ const certFile = path6.join(this.#rootDir, "certs", `${assertHexSerial(serial)}.pem`);
3143
+ fs9.writeFileSync(certFile, pem);
3144
+ }
3145
+ };
2827
3146
  }
2828
3147
  });
2829
3148
 
2830
- // packages/node-opcua-pki/lib/ca/templates/ca_config_template.cnf.ts
2831
- var config2, ca_config_template_cnf_default;
2832
- var init_ca_config_template_cnf = __esm({
2833
- "packages/node-opcua-pki/lib/ca/templates/ca_config_template.cnf.ts"() {
3149
+ // packages/node-opcua-pki/lib/ca/backends/native_ca_backend.ts
3150
+ import fs10 from "fs";
3151
+ import path7 from "path";
3152
+ import {
3153
+ CertificatePurpose as CertificatePurpose2,
3154
+ createCertificateFromCsr,
3155
+ createCertificateSigningRequest as createCertificateSigningRequest2,
3156
+ createCrl,
3157
+ isCaSigner,
3158
+ privateKeyToCryptoKey,
3159
+ readPrivateKey as readPrivateKey2,
3160
+ Subject as Subject4,
3161
+ x509
3162
+ } from "node-opcua-crypto";
3163
+ function signingAlgorithmOf(key) {
3164
+ return isCaSigner(key) ? key.algorithm : RSA_SHA256;
3165
+ }
3166
+ function daysFromNow(notBefore, days) {
3167
+ return new Date(notBefore.getTime() + days * 24 * 60 * 60 * 1e3);
3168
+ }
3169
+ function caSanUri(ca) {
3170
+ return `urn:${ca.subject.commonName || "NodeOPCUA-CA"}`;
3171
+ }
3172
+ function sanFromCsr(csrPem) {
3173
+ const request = new x509.Pkcs10CertificateRequest(csrPem);
3174
+ const extension = request.getExtension("2.5.29.17");
3175
+ const dns2 = [];
3176
+ const ip = [];
3177
+ let applicationUri;
3178
+ for (const name of extension?.names.toJSON() ?? []) {
3179
+ if (name.type === "dns") {
3180
+ dns2.push(name.value);
3181
+ } else if (name.type === "ip") {
3182
+ ip.push(name.value);
3183
+ } else if (name.type === "url" && applicationUri === void 0) {
3184
+ applicationUri = name.value;
3185
+ }
3186
+ }
3187
+ return { dns: dns2, ip, applicationUri };
3188
+ }
3189
+ function toOpenSslSubjectString(subjectName) {
3190
+ const parts = [];
3191
+ for (const rdn of subjectName.toJSON()) {
3192
+ for (const [type, values] of Object.entries(rdn)) {
3193
+ for (const value of values) {
3194
+ parts.push(`${type}=${value}`);
3195
+ }
3196
+ }
3197
+ }
3198
+ return `/${parts.join("/")}`;
3199
+ }
3200
+ var CA_CERT_VALIDITY_DAYS, RSA_SHA256, REASON_TO_X509_CRL_REASON, NativeCaBackend;
3201
+ var init_native_ca_backend = __esm({
3202
+ "packages/node-opcua-pki/lib/ca/backends/native_ca_backend.ts"() {
2834
3203
  "use strict";
2835
3204
  init_esm_shims();
2836
- config2 = `#.........DO NOT MODIFY BY HAND .........................
2837
- [ ca ]
2838
- default_ca = CA_default
2839
- [ CA_default ]
2840
- dir = "%%ROOT_FOLDER%%" # the main CA folder (quoted: see renderCaConfig)
2841
- certs = $dir/certs # where to store certificates
2842
- new_certs_dir = $dir/certs #
2843
- database = $dir/index.txt # the certificate database
2844
- serial = $dir/serial # the serial number counter
2845
- certificate = $dir/public/cacert.pem # The root CA certificate
2846
- private_key = $dir/private/cakey.pem # the CA private key
2847
- x509_extensions = usr_cert #
2848
- default_days = 3650 # default validity : 10 years
3205
+ init_toolbox();
3206
+ init_ca_database();
3207
+ CA_CERT_VALIDITY_DAYS = 3650;
3208
+ RSA_SHA256 = { name: "RSASSA-PKCS1-v1_5", hash: { name: "SHA-256" } };
3209
+ REASON_TO_X509_CRL_REASON = {
3210
+ unspecified: x509.X509CrlReason.unspecified,
3211
+ keyCompromise: x509.X509CrlReason.keyCompromise,
3212
+ CACompromise: x509.X509CrlReason.cACompromise,
3213
+ affiliationChanged: x509.X509CrlReason.affiliationChanged,
3214
+ superseded: x509.X509CrlReason.superseded,
3215
+ cessationOfOperation: x509.X509CrlReason.cessationOfOperation,
3216
+ certificateHold: x509.X509CrlReason.certificateHold,
3217
+ removeFromCRL: x509.X509CrlReason.removeFromCRL
3218
+ };
3219
+ NativeCaBackend = class {
3220
+ /** Nothing to check: this backend spawns no process and needs no tool on PATH. */
3221
+ /** Signing goes through `ca._getSigningKey()`, which may be an external signer. */
3222
+ supportsExternalSigner = true;
3223
+ async preflight() {
3224
+ }
3225
+ /**
3226
+ * Write a CSR for the CA's own key, the `[v3_ca_req]` equivalent. The
3227
+ * key comes from the CA rather than from `privateKeyFile`: on a
3228
+ * signer-backed CA that file does not exist.
3229
+ */
3230
+ async generateCaCsr(ca, _caRootDir, _privateKeyFile, csrFile) {
3231
+ displayTitle("Generate a certificate request for the CA key");
3232
+ const signingKey = await ca._getSigningKey();
3233
+ const { csr } = await createCertificateSigningRequest2({
3234
+ privateKey: signingKey,
3235
+ subject: ca.subject.toString(),
3236
+ applicationUri: caSanUri(ca),
3237
+ purpose: CertificatePurpose2.ForCertificateAuthority
3238
+ });
3239
+ await fs10.promises.writeFile(csrFile, csr);
3240
+ }
3241
+ async bootstrap(ca) {
3242
+ const caRootDir = path7.resolve(ca.rootDir);
3243
+ const csrFile = path7.join(caRootDir, "private/cakey.csr");
3244
+ await this.generateCaCsr(ca, caRootDir, path7.join(caRootDir, "private/cakey.pem"), csrFile);
3245
+ const issuerCA = ca._issuerCA;
3246
+ if (issuerCA) {
3247
+ displayTitle("Generate CA Certificate (signed by issuer CA)");
3248
+ const signWithIssuer = () => this.signSubordinateCsr(issuerCA, csrFile, ca.caCertificate, CA_CERT_VALIDITY_DAYS);
3249
+ if (path7.resolve(issuerCA.rootDir) === caRootDir) {
3250
+ await signWithIssuer();
3251
+ } else {
3252
+ await issuerCA._withCaDirectoryLock(signWithIssuer);
3253
+ }
3254
+ } else {
3255
+ displayTitle("Generate CA Certificate (self-signed)");
3256
+ const csrPem = await fs10.promises.readFile(csrFile, "utf-8");
3257
+ const signingKey = await ca._getSigningKey();
3258
+ const request = new x509.Pkcs10CertificateRequest(csrPem);
3259
+ const notBefore = /* @__PURE__ */ new Date();
3260
+ const { cert } = await createCertificateFromCsr({
3261
+ csr: csrPem,
3262
+ // the CSR's own parsed subject, so issuer and subject are
3263
+ // byte-identical on a self-signed root: re-encoding a string
3264
+ // form could differ and break chain building
3265
+ issuerName: request.subjectName,
3266
+ issuerPublicKey: request.publicKey,
3267
+ signingKey,
3268
+ signingAlgorithm: signingAlgorithmOf(signingKey),
3269
+ notBefore,
3270
+ notAfter: daysFromNow(notBefore, CA_CERT_VALIDITY_DAYS),
3271
+ purpose: CertificatePurpose2.ForCertificateAuthority,
3272
+ applicationUri: caSanUri(ca),
3273
+ revocation: {
3274
+ crlDistributionUrl: ca.crlDistributionUrl,
3275
+ ocspResponderUrl: ca.ocspResponderUrl,
3276
+ caIssuersUrl: ca.caIssuersUrl
3277
+ }
3278
+ });
3279
+ await fs10.promises.writeFile(ca.caCertificate, cert);
3280
+ }
3281
+ displaySubtitle("generate initial CRL (Certificate Revocation List)");
3282
+ await this.#regenerateCrlLocked(ca, new CaDatabase(ca.rootDir));
3283
+ displayTitle("Create Certificate Authority (CA) ---> DONE");
3284
+ }
3285
+ /** Sign a subordinate CA's request with this CA's key, the `[v3_ca]` equivalent. */
3286
+ async signSubordinateCsr(ca, csrFile, certFile, validityDays) {
3287
+ const { signingKey, signingAlgorithm, issuerPublicKey, issuerName } = await this.#issuerContext(ca);
3288
+ const csrPem = await fs10.promises.readFile(csrFile, "utf-8");
3289
+ const db = new CaDatabase(ca.rootDir);
3290
+ const serialNumber = db.nextSerial();
3291
+ const notBefore = /* @__PURE__ */ new Date();
3292
+ const notAfter = daysFromNow(notBefore, validityDays);
3293
+ const { cert } = await createCertificateFromCsr({
3294
+ csr: csrPem,
3295
+ issuerName,
3296
+ issuerPublicKey,
3297
+ signingKey,
3298
+ signingAlgorithm,
3299
+ serialNumber,
3300
+ notBefore,
3301
+ notAfter,
3302
+ purpose: CertificatePurpose2.ForCertificateAuthority,
3303
+ // the subordinate's own SAN, not the issuer's
3304
+ ...sanFromCsr(csrPem),
3305
+ revocation: {
3306
+ crlDistributionUrl: ca.crlDistributionUrl,
3307
+ ocspResponderUrl: ca.ocspResponderUrl,
3308
+ caIssuersUrl: ca.caIssuersUrl
3309
+ }
3310
+ });
3311
+ await fs10.promises.writeFile(certFile, cert);
3312
+ this.#record(db, serialNumber, cert, notAfter);
3313
+ }
3314
+ /**
3315
+ * Self-sign a certificate with a caller-supplied key file and record it
3316
+ * in this CA's database: the native equivalent of `openssl req -new`
3317
+ * followed by `openssl ca -selfsign`, which likewise applies the
3318
+ * end-entity profile and updates `index.txt`.
3319
+ */
3320
+ async createSelfSignedCertificate(ca, certificateFile, privateKeyFile, params) {
3321
+ displaySubtitle("- the certificate signing request");
3322
+ const key = await privateKeyToCryptoKey(readPrivateKey2(privateKeyFile, await ca._privateKeyPassphrase()));
3323
+ const { csr } = await createCertificateSigningRequest2({
3324
+ privateKey: key,
3325
+ subject: params.subject ? new Subject4(params.subject).toString() : ca.subject.toString(),
3326
+ dns: params.dns,
3327
+ ip: params.ip,
3328
+ applicationUri: params.applicationUri,
3329
+ purpose: CertificatePurpose2.ForApplication
3330
+ });
3331
+ displaySubtitle("- creating the self-signed certificate");
3332
+ const request = new x509.Pkcs10CertificateRequest(csr);
3333
+ const db = new CaDatabase(ca.rootDir);
3334
+ const serialNumber = db.nextSerial();
3335
+ const notBefore = params.startDate ?? /* @__PURE__ */ new Date();
3336
+ const notAfter = params.endDate ?? daysFromNow(notBefore, params.validity ?? 365);
3337
+ const { cert } = await createCertificateFromCsr({
3338
+ csr,
3339
+ issuerName: request.subjectName,
3340
+ issuerPublicKey: request.publicKey,
3341
+ signingKey: key,
3342
+ signingAlgorithm: RSA_SHA256,
3343
+ serialNumber,
3344
+ notBefore,
3345
+ notAfter,
3346
+ purpose: CertificatePurpose2.ForApplication,
3347
+ ...sanFromCsr(csr)
3348
+ });
3349
+ await fs10.promises.writeFile(certificateFile, cert);
3350
+ this.#record(db, serialNumber, cert, notAfter);
3351
+ }
3352
+ /** `certs/<SERIAL>.pem` plus the `V` row, exactly as `openssl ca` writes them. */
3353
+ #record(db, serialNumber, certPem, notAfter) {
3354
+ db.storeCertificate(serialNumber, certPem);
3355
+ db.appendIssued({
3356
+ serial: serialNumber,
3357
+ expiryDate: notAfter,
3358
+ subject: toOpenSslSubjectString(new x509.X509Certificate(certPem).subjectName)
3359
+ });
3360
+ }
3361
+ /**
3362
+ * The CA's signing key and issuer identity, resolved fresh on every
3363
+ * call and never cached across operations. The identity is read back
3364
+ * from the CA certificate on disk so that the issuer field of anything
3365
+ * this CA signs is byte-identical to that certificate's subject.
3366
+ */
3367
+ async #issuerContext(ca) {
3368
+ const signingKey = await ca._getSigningKey();
3369
+ const caCertPem = await fs10.promises.readFile(ca.caCertificate, "utf-8");
3370
+ const caCert = new x509.X509Certificate(caCertPem);
3371
+ return {
3372
+ signingKey,
3373
+ signingAlgorithm: signingAlgorithmOf(signingKey),
3374
+ issuerPublicKey: caCert.publicKey,
3375
+ issuerName: caCert.subjectName
3376
+ };
3377
+ }
3378
+ async signEndEntityCsr(ca, certificate, csr, params, sanOverride) {
3379
+ const { signingKey, signingAlgorithm, issuerPublicKey, issuerName } = await this.#issuerContext(ca);
3380
+ const db = new CaDatabase(ca.rootDir);
3381
+ const serialNumber = db.nextSerial();
3382
+ const csrPem = await fs10.promises.readFile(csr, "utf-8");
3383
+ const notBefore = params.startDate ?? /* @__PURE__ */ new Date();
3384
+ const notAfter = params.endDate ?? new Date(notBefore.getTime() + (params.validity ?? 365) * 24 * 60 * 60 * 1e3);
3385
+ const { cert } = await createCertificateFromCsr({
3386
+ csr: csrPem,
3387
+ issuerName,
3388
+ issuerPublicKey,
3389
+ signingKey,
3390
+ signingAlgorithm,
3391
+ serialNumber,
3392
+ notBefore,
3393
+ notAfter,
3394
+ purpose: CertificatePurpose2.ForApplication,
3395
+ dns: sanOverride.dns,
3396
+ ip: sanOverride.ip,
3397
+ applicationUri: sanOverride.applicationUri,
3398
+ revocation: {
3399
+ crlDistributionUrl: ca.crlDistributionUrl,
3400
+ ocspResponderUrl: ca.ocspResponderUrl,
3401
+ caIssuersUrl: ca.caIssuersUrl
3402
+ }
3403
+ });
3404
+ await fs10.promises.writeFile(certificate, cert);
3405
+ this.#record(db, serialNumber, cert, notAfter);
3406
+ }
3407
+ async #regenerateCrlLocked(ca, db) {
3408
+ const { signingKey, signingAlgorithm, issuerPublicKey, issuerName } = await this.#issuerContext(ca);
3409
+ const crlNumber = db.nextCrlNumber();
3410
+ const entries = db.readIndex().filter((r) => r.status === "revoked").map((r) => ({
3411
+ serialNumber: r.serial,
3412
+ revocationDate: r.revocationDate ? new Date(r.revocationDate) : /* @__PURE__ */ new Date(),
3413
+ reason: r.reason ? REASON_TO_X509_CRL_REASON[r.reason] : void 0
3414
+ }));
3415
+ const { crl } = await createCrl({
3416
+ issuerName,
3417
+ issuerPublicKey,
3418
+ signingKey,
3419
+ signingAlgorithm,
3420
+ crlNumber: BigInt(`0x${crlNumber}`),
3421
+ entries
3422
+ });
3423
+ await fs10.promises.writeFile(ca.revocationList, crl);
3424
+ const der = new x509.X509Crl(crl).rawData;
3425
+ await fs10.promises.writeFile(ca.revocationListDER, Buffer.from(der));
3426
+ }
3427
+ async regenerateCrl(ca) {
3428
+ const db = new CaDatabase(ca.rootDir);
3429
+ await this.#regenerateCrlLocked(ca, db);
3430
+ }
3431
+ async revoke(ca, certificate, reason) {
3432
+ const certPem = await fs10.promises.readFile(certificate, "utf-8");
3433
+ const cert = new x509.X509Certificate(certPem);
3434
+ const db = new CaDatabase(ca.rootDir);
3435
+ db.markRevoked(cert.serialNumber, /* @__PURE__ */ new Date(), reason);
3436
+ await this.#regenerateCrlLocked(ca, db);
3437
+ }
3438
+ };
3439
+ }
3440
+ });
2849
3441
 
2850
- # default_md = sha1
3442
+ // packages/node-opcua-pki/lib/ca/backends/openssl_ca_backend.ts
3443
+ import fs11 from "fs";
3444
+ import path8 from "path";
3445
+ import { Subject as Subject5 } from "node-opcua-crypto";
3446
+ function caAltName(ca) {
3447
+ return `URI:urn:${ca.subject.commonName || "NodeOPCUA-CA"}`;
3448
+ }
3449
+ function caConfigEnvOverrides(ca, altName = caAltName(ca)) {
3450
+ const aiaLegs = [];
3451
+ if (ca.ocspResponderUrl) {
3452
+ aiaLegs.push(`OCSP;URI:${ca.ocspResponderUrl}`);
3453
+ }
3454
+ if (ca.caIssuersUrl) {
3455
+ aiaLegs.push(`caIssuers;URI:${ca.caIssuersUrl}`);
3456
+ }
3457
+ return {
3458
+ ALTNAME: altName,
3459
+ CDP_URL: ca.crlDistributionUrl ?? "",
3460
+ AIA_VALUE: aiaLegs.join(",")
3461
+ };
3462
+ }
3463
+ var n4, OpenSslCaBackend;
3464
+ var init_openssl_ca_backend = __esm({
3465
+ "packages/node-opcua-pki/lib/ca/backends/openssl_ca_backend.ts"() {
3466
+ "use strict";
3467
+ init_esm_shims();
3468
+ init_toolbox();
3469
+ init_with_openssl();
3470
+ n4 = makePath;
3471
+ OpenSslCaBackend = class {
3472
+ /** This backend is the `openssl` executable, so it has to be there. */
3473
+ /**
3474
+ * `-passin env:` argv and env for an openssl call that has to load this
3475
+ * CA's key. Built here rather than on the CA: the passphrase is the
3476
+ * CA's business, but expressing it as an openssl argument is this
3477
+ * backend's, and the core has no reason to know the flag exists.
3478
+ */
3479
+ async #passin(ca) {
3480
+ return passinArg(await ca._privateKeyPassphrase());
3481
+ }
3482
+ /** The openssl CLI loads its key from a file, so it cannot call out to an HSM. */
3483
+ supportsExternalSigner = false;
3484
+ async preflight() {
3485
+ await ensure_openssl_installed();
3486
+ }
3487
+ /**
3488
+ * Render `conf/caconfig.cnf` once with explicit overrides, run `fn`
3489
+ * with the rendered path, and always remove the temp file. Structural
3490
+ * guarantee that (a) every render carries the three required env
3491
+ * values and (b) no rendered `<name>.<pid>-<n>.tmp` file is ever left
3492
+ * behind — each was previously a per-method discipline.
3493
+ */
3494
+ async #withConfig(ca, altName, fn) {
3495
+ const caRootDir = path8.resolve(ca.rootDir);
3496
+ const options = { cwd: caRootDir };
3497
+ const configFile = generateStaticConfig("conf/caconfig.cnf", options, caConfigEnvOverrides(ca, altName));
3498
+ try {
3499
+ return await fn(configFile, options);
3500
+ } finally {
3501
+ await cleanupStaticConfig(configFile, options);
3502
+ }
3503
+ }
3504
+ async #generateCaCsrWith(ca, configFile, options, privateKeyFile, csrFile) {
3505
+ const passin = await this.#passin(ca);
3506
+ displayTitle("Generate a certificate request for the CA key");
3507
+ await execute_openssl(
3508
+ [
3509
+ "req",
3510
+ "-new",
3511
+ "-sha256",
3512
+ "-extensions",
3513
+ "v3_ca_req",
3514
+ "-config",
3515
+ n4(configFile),
3516
+ "-key",
3517
+ n4(privateKeyFile),
3518
+ "-out",
3519
+ n4(csrFile),
3520
+ "-subj",
3521
+ ca.subject.toString(),
3522
+ ...passin.args
3523
+ ],
3524
+ { ...options, env: passin.env }
3525
+ );
3526
+ }
3527
+ /**
3528
+ * `openssl ca -gencrl` signs the CRL with the CA key it finds through the
3529
+ * config file (`private_key = $dir/private/cakey.pem`), so it needs the
3530
+ * CA passphrase like every other `openssl ca` invocation.
3531
+ */
3532
+ async #regenerateCrlWith(ca, configFile, options) {
3533
+ const passin = await this.#passin(ca);
3534
+ displaySubtitle("regenerate CRL (Certificate Revocation List)");
3535
+ await execute_openssl(["ca", "-gencrl", "-config", n4(configFile), "-out", "crl/revocation_list.crl", ...passin.args], {
3536
+ ...options,
3537
+ env: passin.env
3538
+ });
3539
+ await execute_openssl(
3540
+ ["crl", "-in", "crl/revocation_list.crl", "-out", "crl/revocation_list.der", "-outform", "der"],
3541
+ options
3542
+ );
3543
+ displaySubtitle("Display (Certificate Revocation List)");
3544
+ await execute_openssl(["crl", "-in", n4(ca.revocationList), "-text", "-noout"], options);
3545
+ }
3546
+ async generateCaCsr(ca, _caRootDir, privateKeyFile, csrFile) {
3547
+ await this.#withConfig(
3548
+ ca,
3549
+ caAltName(ca),
3550
+ (configFile, options) => this.#generateCaCsrWith(ca, configFile, options, privateKeyFile, csrFile)
3551
+ );
3552
+ }
3553
+ async bootstrap(ca) {
3554
+ const caRootDir = path8.resolve(ca.rootDir);
3555
+ const privateKeyFilename = path8.join(caRootDir, "private/cakey.pem");
3556
+ const csrFilename = path8.join(caRootDir, "private/cakey.csr");
3557
+ await this.#withConfig(ca, caAltName(ca), async (configFile, options) => {
3558
+ await this.#generateCaCsrWith(ca, configFile, options, privateKeyFilename, csrFilename);
3559
+ const issuerCA = ca._issuerCA;
3560
+ if (issuerCA) {
3561
+ displayTitle("Generate CA Certificate (signed by issuer CA)");
3562
+ const issuerCert = path8.resolve(issuerCA.caCertificate);
3563
+ const issuerKey = path8.resolve(issuerCA.rootDir, "private/cakey.pem");
3564
+ const issuerSerial = path8.resolve(issuerCA.rootDir, "serial");
3565
+ const issuerPassin = await this.#passin(issuerCA);
3566
+ const signWithIssuer = async () => {
3567
+ await execute_openssl(
3568
+ [
3569
+ "x509",
3570
+ "-sha256",
3571
+ "-req",
3572
+ "-days",
3573
+ "3650",
3574
+ "-extensions",
3575
+ "v3_ca",
3576
+ "-extfile",
3577
+ n4(configFile),
3578
+ "-in",
3579
+ "private/cakey.csr",
3580
+ "-CA",
3581
+ n4(issuerCert),
3582
+ "-CAkey",
3583
+ n4(issuerKey),
3584
+ "-CAserial",
3585
+ n4(issuerSerial),
3586
+ "-out",
3587
+ "public/cacert.pem",
3588
+ ...issuerPassin.args
3589
+ ],
3590
+ { ...options, env: issuerPassin.env }
3591
+ );
3592
+ };
3593
+ if (path8.resolve(issuerCA.rootDir) === caRootDir) {
3594
+ await signWithIssuer();
3595
+ } else {
3596
+ await issuerCA._withCaDirectoryLock(signWithIssuer);
3597
+ }
3598
+ } else {
3599
+ displayTitle("Generate CA Certificate (self-signed)");
3600
+ const passin = await this.#passin(ca);
3601
+ await execute_openssl(
3602
+ [
3603
+ "x509",
3604
+ "-sha256",
3605
+ "-req",
3606
+ "-days",
3607
+ "3650",
3608
+ "-extensions",
3609
+ "v3_ca",
3610
+ "-extfile",
3611
+ n4(configFile),
3612
+ "-in",
3613
+ "private/cakey.csr",
3614
+ "-signkey",
3615
+ n4(privateKeyFilename),
3616
+ "-out",
3617
+ "public/cacert.pem",
3618
+ ...passin.args
3619
+ ],
3620
+ { ...options, env: passin.env }
3621
+ );
3622
+ }
3623
+ displaySubtitle("generate initial CRL (Certificate Revocation List)");
3624
+ await this.#regenerateCrlWith(ca, configFile, options);
3625
+ });
3626
+ displayTitle("Create Certificate Authority (CA) ---> DONE");
3627
+ }
3628
+ async regenerateCrl(ca) {
3629
+ await this.#withConfig(ca, caAltName(ca), (configFile, options) => this.#regenerateCrlWith(ca, configFile, options));
3630
+ }
3631
+ async signSubordinateCsr(ca, csrFile, certFile, validityDays) {
3632
+ await this.#withConfig(ca, caAltName(ca), async (configFile, options) => {
3633
+ const caRootDir = options.cwd;
3634
+ const passin = await this.#passin(ca);
3635
+ await execute_openssl(
3636
+ [
3637
+ "x509",
3638
+ "-sha256",
3639
+ "-req",
3640
+ "-days",
3641
+ String(validityDays),
3642
+ "-extensions",
3643
+ "v3_ca",
3644
+ "-extfile",
3645
+ n4(configFile),
3646
+ "-in",
3647
+ n4(csrFile),
3648
+ "-CA",
3649
+ n4(ca.caCertificate),
3650
+ "-CAkey",
3651
+ n4(path8.join(caRootDir, "private/cakey.pem")),
3652
+ "-CAserial",
3653
+ n4(path8.join(caRootDir, "serial")),
3654
+ "-out",
3655
+ n4(certFile),
3656
+ ...passin.args
3657
+ ],
3658
+ { ...options, env: passin.env }
3659
+ );
3660
+ });
3661
+ }
3662
+ async signEndEntityCsr(ca, certificate, csr, params1, sanOverride) {
3663
+ await this.#withConfig(ca, buildSubjectAltNameString(sanOverride), async (configFile, options) => {
3664
+ displaySubtitle("- then we ask the authority to sign the certificate signing request");
3665
+ const passin = await this.#passin(ca);
3666
+ await execute_openssl(
3667
+ [
3668
+ "ca",
3669
+ "-config",
3670
+ configFile,
3671
+ "-startdate",
3672
+ x509Date(params1.startDate),
3673
+ "-enddate",
3674
+ x509Date(params1.endDate),
3675
+ "-batch",
3676
+ "-out",
3677
+ n4(certificate),
3678
+ "-in",
3679
+ n4(csr),
3680
+ ...passin.args
3681
+ ],
3682
+ { ...options, env: passin.env }
3683
+ );
3684
+ displaySubtitle("- dump the certificate for a check");
3685
+ await execute_openssl(["x509", "-in", n4(certificate), "-dates", "-fingerprint", "-purpose", "-noout"], options);
3686
+ });
3687
+ }
3688
+ async revoke(ca, certificate, reason) {
3689
+ setEnv("RANDFILE", path8.join(ca.rootDir, "random.rnd"));
3690
+ await this.#withConfig(ca, caAltName(ca), async (configFile, options) => {
3691
+ displaySubtitle("Revoke certificate");
3692
+ const passin = await this.#passin(ca);
3693
+ await execute_openssl_no_failure(
3694
+ ["ca", "-verbose", "-config", n4(configFile), "-revoke", certificate, "-crl_reason", reason, ...passin.args],
3695
+ { ...options, env: passin.env }
3696
+ );
3697
+ await this.#regenerateCrlWith(ca, configFile, options);
3698
+ displaySubtitle("Verify that certificate is revoked");
3699
+ await execute_openssl_no_failure(
3700
+ [
3701
+ "verify",
3702
+ "-verbose",
3703
+ "-CRLfile",
3704
+ n4(ca.revocationList),
3705
+ "-CAfile",
3706
+ n4(ca.caCertificate),
3707
+ "-crl_check",
3708
+ n4(certificate)
3709
+ ],
3710
+ options
3711
+ );
3712
+ displaySubtitle("Produce CRL in DER form ");
3713
+ await execute_openssl(
3714
+ ["crl", "-in", n4(ca.revocationList), "-out", "crl/revocation_list.der", "-outform", "der"],
3715
+ options
3716
+ );
3717
+ displaySubtitle("Produce CRL in PEM form ");
3718
+ await execute_openssl(
3719
+ ["crl", "-in", n4(ca.revocationList), "-out", "crl/revocation_list.pem", "-outform", "pem", "-text"],
3720
+ options
3721
+ );
3722
+ });
3723
+ }
3724
+ async createSelfSignedCertificate(ca, certificateFile, privateKeyFile, params) {
3725
+ const envOverrides = caConfigEnvOverrides(ca, buildSubjectAltNameString(params));
3726
+ const configFile = generateStaticConfig(ca.configFile, { cwd: ca.rootDir }, envOverrides);
3727
+ const options = {
3728
+ cwd: ca.rootDir,
3729
+ openssl_conf: makePath(configFile)
3730
+ };
3731
+ try {
3732
+ const subject = params.subject ? new Subject5(params.subject).toString() : "";
3733
+ const subjectOptions = subject && subject.length > 1 ? ["-subj", subject] : [];
3734
+ const csrFile = `${certificateFile}_csr`;
3735
+ const passin = await this.#passin(ca);
3736
+ displaySubtitle("- the certificate signing request");
3737
+ await execute_openssl(
3738
+ [
3739
+ "req",
3740
+ "-new",
3741
+ "-sha256",
3742
+ ...subjectOptions,
3743
+ "-batch",
3744
+ "-key",
3745
+ n4(privateKeyFile),
3746
+ "-out",
3747
+ n4(csrFile),
3748
+ ...passin.args
3749
+ ],
3750
+ { ...options, env: passin.env }
3751
+ );
3752
+ displaySubtitle("- creating the self-signed certificate");
3753
+ await execute_openssl(
3754
+ [
3755
+ "ca",
3756
+ "-selfsign",
3757
+ "-keyfile",
3758
+ n4(privateKeyFile),
3759
+ "-startdate",
3760
+ x509Date(params.startDate),
3761
+ "-enddate",
3762
+ x509Date(params.endDate),
3763
+ "-batch",
3764
+ "-out",
3765
+ n4(certificateFile),
3766
+ "-in",
3767
+ n4(csrFile),
3768
+ ...passin.args
3769
+ ],
3770
+ { ...options, env: passin.env }
3771
+ );
3772
+ displaySubtitle("- dump the certificate for a check");
3773
+ await execute_openssl(["x509", "-in", n4(certificateFile), "-dates", "-fingerprint", "-purpose", "-noout"], {});
3774
+ displaySubtitle("- verify self-signed certificate");
3775
+ await execute_openssl_no_failure(["verify", "-verbose", "-CAfile", n4(certificateFile), n4(certificateFile)], options);
3776
+ await fs11.promises.unlink(csrFile);
3777
+ } finally {
3778
+ await cleanupStaticConfig(configFile, { cwd: ca.rootDir });
3779
+ }
3780
+ }
3781
+ };
3782
+ }
3783
+ });
3784
+
3785
+ // packages/node-opcua-pki/lib/ca/templates/ca_config_template.cnf.ts
3786
+ var config2, ca_config_template_cnf_default;
3787
+ var init_ca_config_template_cnf = __esm({
3788
+ "packages/node-opcua-pki/lib/ca/templates/ca_config_template.cnf.ts"() {
3789
+ "use strict";
3790
+ init_esm_shims();
3791
+ config2 = `#.........DO NOT MODIFY BY HAND .........................
3792
+ [ ca ]
3793
+ default_ca = CA_default
3794
+ [ CA_default ]
3795
+ dir = "%%ROOT_FOLDER%%" # the main CA folder (quoted: see renderCaConfig)
3796
+ certs = $dir/certs # where to store certificates
3797
+ new_certs_dir = $dir/certs #
3798
+ database = $dir/index.txt # the certificate database
3799
+ serial = $dir/serial # the serial number counter
3800
+ certificate = $dir/public/cacert.pem # The root CA certificate
3801
+ private_key = $dir/private/cakey.pem # the CA private key
3802
+ x509_extensions = usr_cert #
3803
+ default_days = 3650 # default validity : 10 years
3804
+
3805
+ # default_md = sha1
2851
3806
 
2852
3807
  default_md = sha256 # The default digest algorithm
2853
3808
 
@@ -2858,7 +3813,7 @@ policy = policy_match
2858
3813
  # default_enddate = YYMMDDHHMMSSZ
2859
3814
  crl_dir = $dir/crl
2860
3815
  crl_extensions = crl_ext
2861
- crl = $dir/revocation_list.crl # the Revocation list
3816
+ crl = $dir/crl/revocation_list.crl # the Revocation list
2862
3817
  crlnumber = $dir/crlnumber # CRL number file
2863
3818
  default_crl_days = 30
2864
3819
  default_crl_hours = 24
@@ -2968,25 +3923,31 @@ authorityKeyIdentifier = keyid:always,issuer:always
2968
3923
  }
2969
3924
  });
2970
3925
 
2971
- // packages/node-opcua-pki/lib/ca/certificate_authority.ts
2972
- import assert10 from "assert";
2973
- import fs10 from "fs";
3926
+ // packages/node-opcua-pki/lib/ca/core/certificate_authority_core.ts
3927
+ import assert9 from "assert";
3928
+ import fs12 from "fs";
2974
3929
  import os4 from "os";
2975
- import path6 from "path";
3930
+ import path9 from "path";
3931
+ import { withLock as withLock2 } from "@ster5/global-mutex";
2976
3932
  import chalk6 from "chalk";
2977
3933
  import {
2978
- CertificatePurpose as CertificatePurpose2,
3934
+ CertificatePurpose as CertificatePurpose3,
2979
3935
  certificateMatchesPrivateKey,
2980
- convertPEMtoDER,
3936
+ convertPEMtoDER as convertPEMtoDER2,
3937
+ createCertificateSigningRequest as createCertificateSigningRequest3,
3938
+ createPfx,
2981
3939
  exploreCertificate as exploreCertificate2,
2982
3940
  exploreCertificateSigningRequest,
2983
3941
  generatePrivateKeyFile as generatePrivateKeyFile2,
2984
- readCertificatePEM,
3942
+ privateKeyToCryptoKey as privateKeyToCryptoKey2,
3943
+ readCertificatePEM as readCertificatePEM2,
2985
3944
  readCertificateSigningRequest,
2986
- readPrivateKey as readPrivateKey2,
2987
- Subject as Subject4,
3945
+ readPrivateKey as readPrivateKey3,
3946
+ Subject as Subject6,
2988
3947
  toPem as toPem2,
2989
- writePrivateKeyFile as writePrivateKeyFile2
3948
+ verifyCertificateSignature as verifyCertificateSignature2,
3949
+ writePrivateKeyFile as writePrivateKeyFile2,
3950
+ x509 as x5092
2990
3951
  } from "node-opcua-crypto";
2991
3952
  function escapeOpensslConfDoubleQuoted(value) {
2992
3953
  return value.replace(/\\/g, "\\\\").replace(/"/g, '\\"');
@@ -2997,175 +3958,6 @@ function renderCaConfig(caRootDir) {
2997
3958
  function octetStringToIpAddress(a) {
2998
3959
  return parseInt(a.substring(0, 2), 16).toString() + "." + parseInt(a.substring(2, 4), 16).toString() + "." + parseInt(a.substring(4, 6), 16).toString() + "." + parseInt(a.substring(6, 8), 16).toString();
2999
3960
  }
3000
- async function construct_CertificateAuthority(certificateAuthority) {
3001
- const subject = certificateAuthority.subject;
3002
- const caRootDir = path6.resolve(certificateAuthority.rootDir);
3003
- async function make_folders() {
3004
- mkdirRecursiveSync(caRootDir);
3005
- ensurePrivateDirectory(path6.join(caRootDir, "private"));
3006
- mkdirRecursiveSync(path6.join(caRootDir, "public"));
3007
- mkdirRecursiveSync(path6.join(caRootDir, "certs"));
3008
- mkdirRecursiveSync(path6.join(caRootDir, "crl"));
3009
- mkdirRecursiveSync(path6.join(caRootDir, "conf"));
3010
- }
3011
- await make_folders();
3012
- async function construct_default_files() {
3013
- const serial = path6.join(caRootDir, "serial");
3014
- if (!fs10.existsSync(serial)) {
3015
- await fs10.promises.writeFile(serial, "1000");
3016
- }
3017
- const crlNumber = path6.join(caRootDir, "crlnumber");
3018
- if (!fs10.existsSync(crlNumber)) {
3019
- await fs10.promises.writeFile(crlNumber, "1000");
3020
- }
3021
- const indexFile = path6.join(caRootDir, "index.txt");
3022
- if (!fs10.existsSync(indexFile)) {
3023
- await fs10.promises.writeFile(indexFile, "");
3024
- }
3025
- }
3026
- await construct_default_files();
3027
- const caKeyExists = fs10.existsSync(path6.join(caRootDir, "private/cakey.pem"));
3028
- const caCertExists = fs10.existsSync(path6.join(caRootDir, "public/cacert.pem"));
3029
- if (caKeyExists && caCertExists && !config3.forceCA) {
3030
- restrictPrivateFilePermissions(path6.join(caRootDir, "private/cakey.pem"), 384);
3031
- await certificateAuthority._ensurePrivateKeyProtection();
3032
- debugLog("CA private key and certificate already exist ... skipping");
3033
- return;
3034
- }
3035
- if (caKeyExists && !caCertExists) {
3036
- debugLog("CA private key exists but cacert.pem is missing \u2014 rebuilding CA");
3037
- fs10.unlinkSync(path6.join(caRootDir, "private/cakey.pem"));
3038
- const staleCsr = path6.join(caRootDir, "private/cakey.csr");
3039
- if (fs10.existsSync(staleCsr)) {
3040
- fs10.unlinkSync(staleCsr);
3041
- }
3042
- }
3043
- displayTitle("Create Certificate Authority (CA)");
3044
- const indexFileAttr = path6.join(caRootDir, "index.txt.attr");
3045
- if (!fs10.existsSync(indexFileAttr)) {
3046
- await fs10.promises.writeFile(indexFileAttr, "unique_subject = no");
3047
- }
3048
- const caConfigFile = certificateAuthority.configFile;
3049
- if (1) {
3050
- await fs10.promises.writeFile(caConfigFile, renderCaConfig(caRootDir));
3051
- }
3052
- const subjectOpt = ["-subj", subject.toString()];
3053
- const caCommonName = subject.commonName || "NodeOPCUA-CA";
3054
- setEnv("ALTNAME", `URI:urn:${caCommonName}`);
3055
- certificateAuthority._wireRevocationEnvVars();
3056
- const options = { cwd: caRootDir };
3057
- const configFile = generateStaticConfig("conf/caconfig.cnf", options);
3058
- const configOption = ["-config", n5(configFile)];
3059
- const keySize = certificateAuthority.keySize;
3060
- const privateKeyFilename = path6.join(caRootDir, "private/cakey.pem");
3061
- const csrFilename = path6.join(caRootDir, "private/cakey.csr");
3062
- displayTitle(`Generate the CA private Key - ${keySize}`);
3063
- const passin = await certificateAuthority._opensslPassin();
3064
- await generatePrivateKeyFile2(privateKeyFilename, keySize, { passphrase: await certificateAuthority._privateKeyPassphrase() });
3065
- restrictPrivateFilePermissions(privateKeyFilename, 384);
3066
- displayTitle("Generate a certificate request for the CA key");
3067
- await execute_openssl(
3068
- [
3069
- "req",
3070
- "-new",
3071
- "-sha256",
3072
- "-text",
3073
- "-extensions",
3074
- "v3_ca_req",
3075
- ...configOption,
3076
- "-key",
3077
- n5(privateKeyFilename),
3078
- "-out",
3079
- n5(csrFilename),
3080
- ...subjectOpt,
3081
- ...passin.args
3082
- ],
3083
- { ...options, env: passin.env }
3084
- );
3085
- const issuerCA = certificateAuthority._issuerCA;
3086
- if (issuerCA) {
3087
- displayTitle("Generate CA Certificate (signed by issuer CA)");
3088
- const issuerCert = path6.resolve(issuerCA.caCertificate);
3089
- const issuerKey = path6.resolve(issuerCA.rootDir, "private/cakey.pem");
3090
- const issuerSerial = path6.resolve(issuerCA.rootDir, "serial");
3091
- const issuerPassin = await issuerCA._opensslPassin();
3092
- await execute_openssl(
3093
- [
3094
- "x509",
3095
- "-sha256",
3096
- "-req",
3097
- "-days",
3098
- "3650",
3099
- "-text",
3100
- "-extensions",
3101
- "v3_ca",
3102
- "-extfile",
3103
- n5(configFile),
3104
- "-in",
3105
- "private/cakey.csr",
3106
- "-CA",
3107
- n5(issuerCert),
3108
- "-CAkey",
3109
- n5(issuerKey),
3110
- "-CAserial",
3111
- n5(issuerSerial),
3112
- "-out",
3113
- "public/cacert.pem",
3114
- ...issuerPassin.args
3115
- ],
3116
- { ...options, env: issuerPassin.env }
3117
- );
3118
- } else {
3119
- displayTitle("Generate CA Certificate (self-signed)");
3120
- await execute_openssl(
3121
- [
3122
- "x509",
3123
- "-sha256",
3124
- "-req",
3125
- "-days",
3126
- "3650",
3127
- "-text",
3128
- "-extensions",
3129
- "v3_ca",
3130
- "-extfile",
3131
- n5(configFile),
3132
- "-in",
3133
- "private/cakey.csr",
3134
- "-signkey",
3135
- n5(privateKeyFilename),
3136
- "-out",
3137
- "public/cacert.pem",
3138
- ...passin.args
3139
- ],
3140
- { ...options, env: passin.env }
3141
- );
3142
- }
3143
- displaySubtitle("generate initial CRL (Certificate Revocation List)");
3144
- await regenerateCrl(certificateAuthority.revocationList, configOption, options, passin);
3145
- displayTitle("Create Certificate Authority (CA) ---> DONE");
3146
- }
3147
- async function regenerateCrl(revocationList, configOption, options, passin) {
3148
- displaySubtitle("regenerate CRL (Certificate Revocation List)");
3149
- await execute_openssl(["ca", "-gencrl", ...configOption, "-out", "crl/revocation_list.crl", ...passin.args], {
3150
- ...options,
3151
- env: passin.env
3152
- });
3153
- await execute_openssl(["crl", "-in", "crl/revocation_list.crl", "-out", "crl/revocation_list.der", "-outform", "der"], options);
3154
- displaySubtitle("Display (Certificate Revocation List)");
3155
- await execute_openssl(["crl", "-in", n5(revocationList), "-text", "-noout"], options);
3156
- }
3157
- function parseOpenSSLDate(dateStr) {
3158
- const raw = dateStr?.split(",")[0] ?? "";
3159
- if (raw.length < 12) return "";
3160
- const yy = parseInt(raw.substring(0, 2), 10);
3161
- const year = yy >= 70 ? 1900 + yy : 2e3 + yy;
3162
- const month = raw.substring(2, 4);
3163
- const day = raw.substring(4, 6);
3164
- const hour = raw.substring(6, 8);
3165
- const min = raw.substring(8, 10);
3166
- const sec = raw.substring(10, 12);
3167
- return `${year}-${month}-${day}T${hour}:${min}:${sec}Z`;
3168
- }
3169
3961
  function validateRevocationUrl(url, fieldName) {
3170
3962
  if (url === void 0) {
3171
3963
  return void 0;
@@ -3193,27 +3985,25 @@ function validateRevocationUrl(url, fieldName) {
3193
3985
  }
3194
3986
  return url;
3195
3987
  }
3196
- var defaultSubject, configurationFileTemplate, configurationFileSimpleTemplate2, config3, n5, CertificateAuthority;
3197
- var init_certificate_authority = __esm({
3198
- "packages/node-opcua-pki/lib/ca/certificate_authority.ts"() {
3988
+ var defaultSubject, configurationFileTemplate, config3, CA_LOCK_MAX_WAIT_MS, CA_LOCK_RETRY, CertificateAuthorityCore;
3989
+ var init_certificate_authority_core = __esm({
3990
+ "packages/node-opcua-pki/lib/ca/core/certificate_authority_core.ts"() {
3199
3991
  "use strict";
3200
3992
  init_esm_shims();
3201
- init_toolbox_pfx();
3202
3993
  init_toolbox();
3203
- init_with_openssl();
3204
- init_simple_config_template_cnf();
3994
+ init_ca_database();
3205
3995
  init_ca_config_template_cnf();
3206
3996
  defaultSubject = "/C=FR/ST=IDF/L=Paris/O=Local NODE-OPCUA Certificate Authority/CN=NodeOPCUA-CA";
3207
3997
  configurationFileTemplate = ca_config_template_cnf_default;
3208
- configurationFileSimpleTemplate2 = simple_config_template_cnf_default;
3209
3998
  config3 = {
3210
3999
  certificateDir: "INVALID",
3211
4000
  forceCA: false,
3212
4001
  pkiDir: "INVALID"
3213
4002
  };
3214
- n5 = makePath;
3215
- assert10(octetStringToIpAddress("c07b9179") === "192.123.145.121");
3216
- CertificateAuthority = class {
4003
+ CA_LOCK_MAX_WAIT_MS = 5 * 6e4;
4004
+ CA_LOCK_RETRY = { minTimeout: 20, maxTimeout: 250 };
4005
+ assert9(octetStringToIpAddress("c07b9179") === "192.123.145.121");
4006
+ CertificateAuthorityCore = class {
3217
4007
  /** RSA key size used when generating the CA private key. */
3218
4008
  keySize;
3219
4009
  /** Root filesystem path of the CA directory structure. */
@@ -3230,14 +4020,41 @@ var init_certificate_authority = __esm({
3230
4020
  /** resolved once (see `privateKeyPassphrase`); `#passphraseResolved` distinguishes "none" from "not yet" */
3231
4021
  #resolvedPassphrase;
3232
4022
  #passphraseResolved = false;
4023
+ /** Signing backend: `openssl` shells out to the CLI, `native` signs in-process. */
4024
+ #backend;
4025
+ /** External signing key (HSM/KMS), when one was supplied instead of a key file. */
4026
+ #signer;
4027
+ /** Read access to `index.txt` / `certs/<SERIAL>.pem`. */
4028
+ #db;
3233
4029
  constructor(options) {
3234
- assert10(Object.prototype.hasOwnProperty.call(options, "location"));
3235
- assert10(Object.prototype.hasOwnProperty.call(options, "keySize"));
4030
+ assert9(Object.prototype.hasOwnProperty.call(options, "location"));
4031
+ assert9(Object.prototype.hasOwnProperty.call(options, "keySize"));
3236
4032
  this.location = options.location;
3237
4033
  this.keySize = options.keySize || 2048;
3238
- this.subject = new Subject4(options.subject || defaultSubject);
4034
+ this.subject = new Subject6(options.subject || defaultSubject);
3239
4035
  this._issuerCA = options.issuerCA;
3240
4036
  this.#privateKeyPassphrase = options.privateKeyPassphrase;
4037
+ this.#signer = options.signer;
4038
+ if (options.signer) {
4039
+ const algorithm = options.signer.algorithm;
4040
+ if (algorithm.name !== "RSASSA-PKCS1-v1_5" && algorithm.name !== "ECDSA") {
4041
+ throw new Error(
4042
+ `CertificateAuthority: signer algorithm ${algorithm.name} is not supported - use RSASSA-PKCS1-v1_5 or ECDSA.`
4043
+ );
4044
+ }
4045
+ if (algorithm.name === "ECDSA" && !algorithm.namedCurve) {
4046
+ throw new Error(
4047
+ "CertificateAuthority: an ECDSA signer must declare its namedCurve (P-256, P-384 or P-521) - importing the signer's public key needs the curve, and an SPKI import cannot infer it."
4048
+ );
4049
+ }
4050
+ }
4051
+ if (options.signer && !options.backend.supportsExternalSigner) {
4052
+ throw new Error(
4053
+ "CertificateAuthority: this backend cannot sign with an external signer - it loads its key from a file. Use a backend that supports one, such as NativeCaBackend."
4054
+ );
4055
+ }
4056
+ this.#backend = options.backend;
4057
+ this.#db = new CaDatabase(this.location);
3241
4058
  if (options.crlDistributionUrl !== void 0) {
3242
4059
  this.setCrlDistributionUrl(options.crlDistributionUrl);
3243
4060
  }
@@ -3301,42 +4118,17 @@ var init_certificate_authority = __esm({
3301
4118
  setCaIssuersUrl(url) {
3302
4119
  this._caIssuersUrl = validateRevocationUrl(url, "caIssuersUrl");
3303
4120
  }
3304
- /**
3305
- * @internal
3306
- * Populate the OpenSSL config substitution env vars (`CDP_URL` and
3307
- * `AIA_VALUE`) from the configured URLs, or unset them so the
3308
- * matching `{{#KEY}}...{{/KEY}}` blocks in the templates are
3309
- * stripped. MUST be called before every `generateStaticConfig`
3310
- * invocation that signs a certificate.
3311
- */
3312
- _wireRevocationEnvVars() {
3313
- unsetEnv("CDP_URL");
3314
- unsetEnv("AIA_VALUE");
3315
- if (this._crlDistributionUrl) {
3316
- setEnv("CDP_URL", this._crlDistributionUrl);
3317
- }
3318
- const aiaLegs = [];
3319
- if (this._ocspResponderUrl) {
3320
- aiaLegs.push(`OCSP;URI:${this._ocspResponderUrl}`);
3321
- }
3322
- if (this._caIssuersUrl) {
3323
- aiaLegs.push(`caIssuers;URI:${this._caIssuersUrl}`);
3324
- }
3325
- if (aiaLegs.length > 0) {
3326
- setEnv("AIA_VALUE", aiaLegs.join(","));
3327
- }
3328
- }
3329
4121
  /** Absolute path to the CA root directory (alias for {@link location}). */
3330
4122
  get rootDir() {
3331
4123
  return this.location;
3332
4124
  }
3333
4125
  /** Path to the OpenSSL configuration file (`conf/caconfig.cnf`). */
3334
4126
  get configFile() {
3335
- return path6.normalize(path6.join(this.rootDir, "./conf/caconfig.cnf"));
4127
+ return path9.normalize(path9.join(this.rootDir, "./conf/caconfig.cnf"));
3336
4128
  }
3337
4129
  /** Path to the CA private key (`private/cakey.pem`); may be passphrase-encrypted, see {@link getPrivateKey}. */
3338
4130
  get privateKey() {
3339
- return path6.join(path6.resolve(this.rootDir), "private/cakey.pem");
4131
+ return path9.join(path9.resolve(this.rootDir), "private/cakey.pem");
3340
4132
  }
3341
4133
  /**
3342
4134
  * The CA private key, decrypted with the configured `privateKeyPassphrase`
@@ -3344,7 +4136,51 @@ var init_certificate_authority = __esm({
3344
4136
  * on an encrypted key with no or the wrong passphrase.
3345
4137
  */
3346
4138
  async getPrivateKey() {
3347
- return readPrivateKey2(this.privateKey, await this._privateKeyPassphrase());
4139
+ this.#assertKeyIsOnDisk("getPrivateKey");
4140
+ return readPrivateKey3(this.privateKey, await this._privateKeyPassphrase());
4141
+ }
4142
+ /**
4143
+ * True when this CA signs with an external {@link CaSigner} (HSM/KMS)
4144
+ * rather than with `private/cakey.pem`. There is then no private key
4145
+ * file on disk, and never was one.
4146
+ */
4147
+ get hasExternalSigner() {
4148
+ return this.#signer !== void 0;
4149
+ }
4150
+ /**
4151
+ * Does `cert` certify the key this CA signs with? With the key on disk
4152
+ * that is a private-key match; with a signer there is no private key to
4153
+ * match, so compare the certified public key against the signer's own -
4154
+ * the same question, asked the only way an HSM allows.
4155
+ */
4156
+ async #certificateMatchesOurKey(certPem, certDer) {
4157
+ if (this.#signer) {
4158
+ const ours = Buffer.from(await this.#signer.getPublicKey());
4159
+ const certified = Buffer.from(new x5092.X509Certificate(certPem).publicKey.rawData);
4160
+ return ours.equals(certified);
4161
+ }
4162
+ return certificateMatchesPrivateKey(certDer, await this.getPrivateKey());
4163
+ }
4164
+ /** Reject the key-file-only operations up front, rather than failing on a missing file later. */
4165
+ #assertKeyIsOnDisk(what) {
4166
+ if (this.#signer) {
4167
+ throw new Error(
4168
+ `CertificateAuthority.${what} is not available on a signer-backed CA: the private key lives in the signer (HSM/KMS) and is never written to disk.`
4169
+ );
4170
+ }
4171
+ }
4172
+ /**
4173
+ * @internal The key every signing operation goes through. An injected
4174
+ * {@link CaSigner} is returned as-is; otherwise the on-disk key is read
4175
+ * (and decrypted) and handed over as a plain `CryptoKey`. Both are
4176
+ * accepted by the `node-opcua-crypto` signing primitives, so callers
4177
+ * never branch on which one they got.
4178
+ */
4179
+ async _getSigningKey() {
4180
+ if (this.#signer) {
4181
+ return this.#signer;
4182
+ }
4183
+ return privateKeyToCryptoKey2(await this.getPrivateKey());
3348
4184
  }
3349
4185
  /**
3350
4186
  * Enable, disable, or rotate the passphrase protecting `private/cakey.pem`
@@ -3353,18 +4189,19 @@ var init_certificate_authority = __esm({
3353
4189
  * passphrase to continue using it.
3354
4190
  */
3355
4191
  async reencryptPrivateKey(oldPassphrase, newPassphrase) {
4192
+ this.#assertKeyIsOnDisk("reencryptPrivateKey");
3356
4193
  const oldPass = await resolvePrivateKeyPassphrase(oldPassphrase);
3357
4194
  const newPass = await resolvePrivateKeyPassphrase(newPassphrase);
3358
- const key = readPrivateKey2(this.privateKey, oldPass);
4195
+ const key = readPrivateKey3(this.privateKey, oldPass);
3359
4196
  await this.#rewritePrivateKeyFile(key, newPass);
3360
4197
  }
3361
4198
  async #rewritePrivateKeyFile(privateKey, passphrase) {
3362
4199
  const tmpFilename = `${this.privateKey}.${process.pid}-${Date.now()}.tmp`;
3363
4200
  try {
3364
4201
  await writePrivateKeyFile2(tmpFilename, privateKey, { passphrase });
3365
- await fs10.promises.rename(tmpFilename, this.privateKey);
4202
+ await fs12.promises.rename(tmpFilename, this.privateKey);
3366
4203
  } finally {
3367
- await fs10.promises.rm(tmpFilename, { force: true });
4204
+ await fs12.promises.rm(tmpFilename, { force: true });
3368
4205
  }
3369
4206
  }
3370
4207
  /** @internal resolve the configured passphrase, at most once per instance */
@@ -3375,10 +4212,6 @@ var init_certificate_authority = __esm({
3375
4212
  }
3376
4213
  return this.#resolvedPassphrase;
3377
4214
  }
3378
- /** @internal `-passin env:` argv + env for every openssl call that loads this CA's key (always emitted, empty when none) */
3379
- async _opensslPassin() {
3380
- return passinArg(await this._privateKeyPassphrase());
3381
- }
3382
4215
  /**
3383
4216
  * @internal On an existing key: encrypt it in place if a passphrase is
3384
4217
  * configured and it is still plaintext (secure by default: the option
@@ -3387,16 +4220,58 @@ var init_certificate_authority = __esm({
3387
4220
  * first signing operation.
3388
4221
  */
3389
4222
  async _ensurePrivateKeyProtection() {
3390
- if (!fs10.existsSync(this.privateKey)) {
4223
+ if (this.#signer || !fs12.existsSync(this.privateKey)) {
3391
4224
  return;
3392
4225
  }
3393
4226
  if (this.#privateKeyPassphrase !== void 0 && !isEncryptedPrivateKeyFile(this.privateKey)) {
3394
4227
  warningLog("CertificateAuthority: private key is plaintext but a passphrase is configured; encrypting it in place");
3395
- const plaintextKey = readPrivateKey2(this.privateKey);
4228
+ const plaintextKey = readPrivateKey3(this.privateKey);
3396
4229
  await this.#rewritePrivateKeyFile(plaintextKey, await this._privateKeyPassphrase());
3397
4230
  }
3398
4231
  await this.getPrivateKey();
3399
4232
  }
4233
+ /**
4234
+ * Acquire a file-based lock on this CA's directory for the duration of
4235
+ * `action` — serializes every operation that mutates the certificate
4236
+ * database (`index.txt`, `serial`, `crlnumber`, `certs/`,
4237
+ * `crl/revocation_list.*`) or the CA's own key/certificate, so
4238
+ * concurrent calls on one `CertificateAuthority` (or two instances
4239
+ * pointed at the same directory, in this or another process) cannot
4240
+ * interleave. Mirrors `CertificateManager.withLock2`.
4241
+ *
4242
+ * Only ever call this from a top-level public operation — nested calls
4243
+ * on the same instance would deadlock, since the underlying file lock
4244
+ * is not reentrant.
4245
+ *
4246
+ * The wait is bounded: a waiter gives up (throws) after
4247
+ * `CA_LOCK_MAX_WAIT_MS`. Without a bound, a holder whose openssl child
4248
+ * hangs would block every CA operation in every process forever and
4249
+ * silently — the lock library's keepalive keeps refreshing the lock
4250
+ * file's mtime as long as the holder process is alive, so stale-lock
4251
+ * recovery never fires for a live-but-stuck holder. A dead holder's
4252
+ * lock goes stale (default 2 minutes) and is taken over well within
4253
+ * this bound.
4254
+ */
4255
+ async #withCaLock(action) {
4256
+ const lockFileName = path9.join(this.rootDir, ".ca.lock");
4257
+ return withLock2(
4258
+ {
4259
+ fileToLock: lockFileName,
4260
+ retries: { forever: true, maxRetryTime: CA_LOCK_MAX_WAIT_MS, ...CA_LOCK_RETRY }
4261
+ },
4262
+ action
4263
+ );
4264
+ }
4265
+ /**
4266
+ * @internal Run `action` under this CA's directory lock. For the one
4267
+ * legitimate cross-instance use: a subordinate CA's bootstrap signs its
4268
+ * certificate with `openssl x509 -CAserial`, which read-increment-writes
4269
+ * THIS issuer's `serial` file, and must therefore hold this issuer's
4270
+ * lock (ordering is always subordinate -> issuer, so no cycle).
4271
+ */
4272
+ async _withCaDirectoryLock(action) {
4273
+ return this.#withCaLock(action);
4274
+ }
3400
4275
  /** Path to the CA certificate in PEM format (`public/cacert.pem`). */
3401
4276
  get caCertificate() {
3402
4277
  return makePath(this.rootDir, "./public/cacert.pem");
@@ -3444,8 +4319,8 @@ var init_certificate_authority = __esm({
3444
4319
  * (call {@link initialize} first).
3445
4320
  */
3446
4321
  getCACertificateDER() {
3447
- const pem = readCertificatePEM(this.caCertificate);
3448
- return convertPEMtoDER(pem);
4322
+ const pem = readCertificatePEM2(this.caCertificate);
4323
+ return convertPEMtoDER2(pem);
3449
4324
  }
3450
4325
  /**
3451
4326
  * Return the CA certificate as a PEM-encoded string.
@@ -3454,7 +4329,7 @@ var init_certificate_authority = __esm({
3454
4329
  * (call {@link initialize} first).
3455
4330
  */
3456
4331
  getCACertificatePEM() {
3457
- const raw = readCertificatePEM(this.caCertificate);
4332
+ const raw = readCertificatePEM2(this.caCertificate);
3458
4333
  const beginMarker = "-----BEGIN CERTIFICATE-----";
3459
4334
  const idx = raw.indexOf(beginMarker);
3460
4335
  if (idx > 0) {
@@ -3470,10 +4345,10 @@ var init_certificate_authority = __esm({
3470
4345
  */
3471
4346
  getCRLDER() {
3472
4347
  const crlPath = this.revocationListDER;
3473
- if (!fs10.existsSync(crlPath)) {
4348
+ if (!fs12.existsSync(crlPath)) {
3474
4349
  return Buffer.alloc(0);
3475
4350
  }
3476
- return fs10.readFileSync(crlPath);
4351
+ return fs12.readFileSync(crlPath);
3477
4352
  }
3478
4353
  /**
3479
4354
  * Return the current Certificate Revocation List as a
@@ -3483,10 +4358,10 @@ var init_certificate_authority = __esm({
3483
4358
  */
3484
4359
  getCRLPEM() {
3485
4360
  const crlPath = this.revocationList;
3486
- if (!fs10.existsSync(crlPath)) {
4361
+ if (!fs12.existsSync(crlPath)) {
3487
4362
  return "";
3488
4363
  }
3489
- const raw = fs10.readFileSync(crlPath, "utf-8");
4364
+ const raw = fs12.readFileSync(crlPath, "utf-8");
3490
4365
  const beginMarker = "-----BEGIN X509 CRL-----";
3491
4366
  const idx = raw.indexOf(beginMarker);
3492
4367
  if (idx > 0) {
@@ -3505,14 +4380,14 @@ var init_certificate_authority = __esm({
3505
4380
  * expiry date, and (for revoked certs) the revocation date.
3506
4381
  */
3507
4382
  getIssuedCertificates() {
3508
- return this._parseIndexTxt();
4383
+ return this.#db.readIndex();
3509
4384
  }
3510
4385
  /**
3511
4386
  * Return the total number of certificates recorded in
3512
4387
  * `index.txt`.
3513
4388
  */
3514
4389
  getIssuedCertificateCount() {
3515
- return this._parseIndexTxt().length;
4390
+ return this.#db.readIndex().length;
3516
4391
  }
3517
4392
  /**
3518
4393
  * Return the status of a certificate by its serial number.
@@ -3522,9 +4397,7 @@ var init_certificate_authority = __esm({
3522
4397
  * `undefined` if not found
3523
4398
  */
3524
4399
  getCertificateStatus(serial) {
3525
- const upper = serial.toUpperCase();
3526
- const record = this._parseIndexTxt().find((r) => r.serial.toUpperCase() === upper);
3527
- return record?.status;
4400
+ return this.#db.findBySerial(serial)?.status;
3528
4401
  }
3529
4402
  /**
3530
4403
  * Read a specific issued certificate by serial number and
@@ -3537,82 +4410,13 @@ var init_certificate_authority = __esm({
3537
4410
  * @returns the DER buffer, or `undefined` if not found
3538
4411
  */
3539
4412
  getCertificateBySerial(serial) {
3540
- const upper = serial.toUpperCase();
3541
- const certFile = path6.join(this.rootDir, "certs", `${upper}.pem`);
3542
- if (!fs10.existsSync(certFile)) {
3543
- return void 0;
3544
- }
3545
- const pem = readCertificatePEM(certFile);
3546
- return convertPEMtoDER(pem);
4413
+ return this.#db.getCertificateBySerial(serial);
3547
4414
  }
3548
4415
  /**
3549
4416
  * Path to the OpenSSL certificate database file.
3550
4417
  */
3551
4418
  get indexFile() {
3552
- return path6.join(this.rootDir, "index.txt");
3553
- }
3554
- /**
3555
- * Parse the OpenSSL `index.txt` certificate database.
3556
- *
3557
- * Each line has tab-separated fields:
3558
- * ```
3559
- * status expiry [revocationDate] serial unknown subject
3560
- * ```
3561
- *
3562
- * - status: `V` (valid), `R` (revoked), `E` (expired)
3563
- * - expiry: `YYMMDDHHmmssZ`
3564
- * - revocationDate: present only for revoked certs
3565
- * - serial: hex string
3566
- * - unknown: always `"unknown"`
3567
- * - subject: X.500 slash-delimited string
3568
- */
3569
- _parseIndexTxt() {
3570
- const indexPath = this.indexFile;
3571
- if (!fs10.existsSync(indexPath)) {
3572
- return [];
3573
- }
3574
- const content = fs10.readFileSync(indexPath, "utf-8");
3575
- const lines = content.split("\n").filter((l) => l.trim().length > 0);
3576
- const records = [];
3577
- for (const line of lines) {
3578
- const fields = line.split(" ");
3579
- if (fields.length < 4) continue;
3580
- const statusChar = fields[0];
3581
- const expiryStr = fields[1];
3582
- let serial;
3583
- let subject;
3584
- let revocationDate;
3585
- if (statusChar === "R") {
3586
- revocationDate = fields[2];
3587
- serial = fields[3];
3588
- subject = fields.length >= 6 ? fields[5] : "";
3589
- } else {
3590
- serial = fields[3];
3591
- subject = fields.length >= 6 ? fields[5] : "";
3592
- }
3593
- let status;
3594
- switch (statusChar) {
3595
- case "V":
3596
- status = "valid";
3597
- break;
3598
- case "R":
3599
- status = "revoked";
3600
- break;
3601
- case "E":
3602
- status = "expired";
3603
- break;
3604
- default:
3605
- continue;
3606
- }
3607
- records.push({
3608
- serial,
3609
- status,
3610
- subject,
3611
- expiryDate: parseOpenSSLDate(expiryStr),
3612
- revocationDate: revocationDate ? parseOpenSSLDate(revocationDate) : void 0
3613
- });
3614
- }
3615
- return records;
4419
+ return this.#db.indexFile;
3616
4420
  }
3617
4421
  // ---------------------------------------------------------------
3618
4422
  // Buffer-based CA operations (US-058)
@@ -3634,12 +4438,12 @@ var init_certificate_authority = __esm({
3634
4438
  * @returns the signed certificate as a DER-encoded buffer
3635
4439
  */
3636
4440
  async signCertificateRequestFromDER(csrDer, options) {
3637
- const tmpDir = await fs10.promises.mkdtemp(path6.join(os4.tmpdir(), "pki-sign-"));
4441
+ const tmpDir = await fs12.promises.mkdtemp(path9.join(os4.tmpdir(), "pki-sign-"));
3638
4442
  try {
3639
- const csrFile = path6.join(tmpDir, "request.csr");
3640
- const certFile = path6.join(tmpDir, "certificate.pem");
4443
+ const csrFile = path9.join(tmpDir, "request.csr");
4444
+ const certFile = path9.join(tmpDir, "certificate.pem");
3641
4445
  const csrPem = toPem2(csrDer, "CERTIFICATE REQUEST");
3642
- await fs10.promises.writeFile(csrFile, csrPem, "utf-8");
4446
+ await fs12.promises.writeFile(csrFile, csrPem, "utf-8");
3643
4447
  const signingParams = {};
3644
4448
  if (options?.validityMs !== void 0) signingParams.validityMs = options.validityMs;
3645
4449
  else signingParams.validity = options?.validity ?? 365;
@@ -3649,10 +4453,10 @@ var init_certificate_authority = __esm({
3649
4453
  if (options?.applicationUri) signingParams.applicationUri = options.applicationUri;
3650
4454
  if (options?.subject) signingParams.subject = options.subject;
3651
4455
  await this.signCertificateRequest(certFile, csrFile, signingParams);
3652
- const certPem = readCertificatePEM(certFile);
3653
- return convertPEMtoDER(certPem);
4456
+ const certPem = readCertificatePEM2(certFile);
4457
+ return convertPEMtoDER2(certPem);
3654
4458
  } finally {
3655
- await fs10.promises.rm(tmpDir, {
4459
+ await fs12.promises.rm(tmpDir, {
3656
4460
  recursive: true,
3657
4461
  force: true
3658
4462
  });
@@ -3702,27 +4506,35 @@ var init_certificate_authority = __esm({
3702
4506
  * @returns `{ certificateDer, privateKey }` — certificate as DER,
3703
4507
  * private key as a branded `PrivateKey` buffer
3704
4508
  */
4509
+ /**
4510
+ * An ephemeral key and a CSR for it, written into `tmpDir`. Both
4511
+ * `generateKeyPairAndSign*` methods need exactly this, and neither
4512
+ * needs a subprocess for it: the key comes from node's crypto and the
4513
+ * request is built and self-signed in process, so no `openssl.cnf` has
4514
+ * to be rendered either.
4515
+ */
4516
+ async #createEphemeralKeyAndCsr(tmpDir, keySize, options) {
4517
+ const privateKeyFile = path9.join(tmpDir, "private_key.pem");
4518
+ await generatePrivateKeyFile2(privateKeyFile, keySize);
4519
+ const { csr } = await createCertificateSigningRequest3({
4520
+ privateKey: await privateKeyToCryptoKey2(readPrivateKey3(privateKeyFile)),
4521
+ subject: options.subject ? new Subject6(options.subject).toString() : void 0,
4522
+ applicationUri: options.applicationUri,
4523
+ dns: options.dns ?? [],
4524
+ ip: options.ip ?? [],
4525
+ purpose: CertificatePurpose3.ForApplication
4526
+ });
4527
+ const csrFile = path9.join(tmpDir, "request.csr");
4528
+ await fs12.promises.writeFile(csrFile, csr);
4529
+ return { privateKeyFile, csrFile };
4530
+ }
3705
4531
  async generateKeyPairAndSignDER(options) {
3706
4532
  const keySize = options.keySize ?? 2048;
3707
4533
  const startDate = options.startDate ?? /* @__PURE__ */ new Date();
3708
- const tmpDir = await fs10.promises.mkdtemp(path6.join(os4.tmpdir(), "pki-keygen-"));
4534
+ const tmpDir = await fs12.promises.mkdtemp(path9.join(os4.tmpdir(), "pki-keygen-"));
3709
4535
  try {
3710
- const privateKeyFile = path6.join(tmpDir, "private_key.pem");
3711
- await generatePrivateKeyFile2(privateKeyFile, keySize);
3712
- const configFile = path6.join(tmpDir, "openssl.cnf");
3713
- await fs10.promises.writeFile(configFile, configurationFileSimpleTemplate2, "utf-8");
3714
- const csrFile = path6.join(tmpDir, "request.csr");
3715
- await createCertificateSigningRequestWithOpenSSL(csrFile, {
3716
- rootDir: tmpDir,
3717
- configFile,
3718
- privateKey: privateKeyFile,
3719
- applicationUri: options.applicationUri,
3720
- subject: options.subject,
3721
- dns: options.dns ?? [],
3722
- ip: options.ip ?? [],
3723
- purpose: CertificatePurpose2.ForApplication
3724
- });
3725
- const certFile = path6.join(tmpDir, "certificate.pem");
4536
+ const { privateKeyFile, csrFile } = await this.#createEphemeralKeyAndCsr(tmpDir, keySize, options);
4537
+ const certFile = path9.join(tmpDir, "certificate.pem");
3726
4538
  const signingParams = {
3727
4539
  applicationUri: options.applicationUri,
3728
4540
  dns: options.dns,
@@ -3732,12 +4544,12 @@ var init_certificate_authority = __esm({
3732
4544
  if (options.validityMs !== void 0) signingParams.validityMs = options.validityMs;
3733
4545
  else signingParams.validity = options.validity ?? 365;
3734
4546
  await this.signCertificateRequest(certFile, csrFile, signingParams);
3735
- const certPem = readCertificatePEM(certFile);
3736
- const certificateDer = convertPEMtoDER(certPem);
3737
- const privateKey = readPrivateKey2(privateKeyFile);
4547
+ const certPem = readCertificatePEM2(certFile);
4548
+ const certificateDer = convertPEMtoDER2(certPem);
4549
+ const privateKey = readPrivateKey3(privateKeyFile);
3738
4550
  return { certificateDer, privateKey };
3739
4551
  } finally {
3740
- await fs10.promises.rm(tmpDir, {
4552
+ await fs12.promises.rm(tmpDir, {
3741
4553
  recursive: true,
3742
4554
  force: true
3743
4555
  });
@@ -3758,24 +4570,10 @@ var init_certificate_authority = __esm({
3758
4570
  const keySize = options.keySize ?? 2048;
3759
4571
  const startDate = options.startDate ?? /* @__PURE__ */ new Date();
3760
4572
  const passphrase = options.passphrase ?? "";
3761
- const tmpDir = await fs10.promises.mkdtemp(path6.join(os4.tmpdir(), "pki-keygen-pfx-"));
4573
+ const tmpDir = await fs12.promises.mkdtemp(path9.join(os4.tmpdir(), "pki-keygen-pfx-"));
3762
4574
  try {
3763
- const privateKeyFile = path6.join(tmpDir, "private_key.pem");
3764
- await generatePrivateKeyFile2(privateKeyFile, keySize);
3765
- const configFile = path6.join(tmpDir, "openssl.cnf");
3766
- await fs10.promises.writeFile(configFile, configurationFileSimpleTemplate2, "utf-8");
3767
- const csrFile = path6.join(tmpDir, "request.csr");
3768
- await createCertificateSigningRequestWithOpenSSL(csrFile, {
3769
- rootDir: tmpDir,
3770
- configFile,
3771
- privateKey: privateKeyFile,
3772
- applicationUri: options.applicationUri,
3773
- subject: options.subject,
3774
- dns: options.dns ?? [],
3775
- ip: options.ip ?? [],
3776
- purpose: CertificatePurpose2.ForApplication
3777
- });
3778
- const certFile = path6.join(tmpDir, "certificate.pem");
4575
+ const { privateKeyFile, csrFile } = await this.#createEphemeralKeyAndCsr(tmpDir, keySize, options);
4576
+ const certFile = path9.join(tmpDir, "certificate.pem");
3779
4577
  const signingParams = {
3780
4578
  applicationUri: options.applicationUri,
3781
4579
  dns: options.dns,
@@ -3785,17 +4583,20 @@ var init_certificate_authority = __esm({
3785
4583
  if (options.validityMs !== void 0) signingParams.validityMs = options.validityMs;
3786
4584
  else signingParams.validity = options.validity ?? 365;
3787
4585
  await this.signCertificateRequest(certFile, csrFile, signingParams);
3788
- const pfxFile = path6.join(tmpDir, "bundle.pfx");
3789
- await createPFX({
3790
- certificateFile: certFile,
3791
- privateKeyFile,
3792
- outputFile: pfxFile,
3793
- passphrase,
3794
- caCertificateFiles: [this.caCertificate]
4586
+ const blocks = (await fs12.promises.readFile(certFile, "utf-8")).match(
4587
+ /-----BEGIN CERTIFICATE-----[\s\S]*?-----END CERTIFICATE-----/g
4588
+ );
4589
+ if (!blocks || blocks.length === 0) {
4590
+ throw new Error(`generateKeyPairAndSignPFX: no certificate was produced in ${certFile}`);
4591
+ }
4592
+ return await createPfx({
4593
+ certificate: convertPEMtoDER2(blocks[0]),
4594
+ certificateChain: blocks.slice(1).map(convertPEMtoDER2),
4595
+ privateKey: readPrivateKey3(privateKeyFile),
4596
+ password: passphrase
3795
4597
  });
3796
- return await fs10.promises.readFile(pfxFile);
3797
4598
  } finally {
3798
- await fs10.promises.rm(tmpDir, {
4599
+ await fs12.promises.rm(tmpDir, {
3799
4600
  recursive: true,
3800
4601
  force: true
3801
4602
  });
@@ -3816,8 +4617,8 @@ var init_certificate_authority = __esm({
3816
4617
  async revokeCertificateDER(certDer, reason) {
3817
4618
  const info = exploreCertificate2(certDer);
3818
4619
  const serial = info.tbsCertificate.serialNumber.replace(/:/g, "").toUpperCase();
3819
- const storedCertFile = path6.join(this.rootDir, "certs", `${serial}.pem`);
3820
- if (!fs10.existsSync(storedCertFile)) {
4620
+ const storedCertFile = path9.join(this.rootDir, "certs", `${serial}.pem`);
4621
+ if (!fs12.existsSync(storedCertFile)) {
3821
4622
  throw new Error(`Cannot revoke: no stored certificate found for serial ${serial} at ${storedCertFile}`);
3822
4623
  }
3823
4624
  await this.revokeCertificate(storedCertFile, {
@@ -3830,7 +4631,69 @@ var init_certificate_authority = __esm({
3830
4631
  * already exist.
3831
4632
  */
3832
4633
  async initialize() {
3833
- await construct_CertificateAuthority(this);
4634
+ mkdirRecursiveSync(path9.resolve(this.rootDir));
4635
+ await this.#withCaLock(() => this.#bootstrap());
4636
+ }
4637
+ /**
4638
+ * @internal Shared (backend-agnostic) part of `initialize()`: directory
4639
+ * layout, default database files, the "already initialized" / "partial
4640
+ * init" checks, and the openssl config file — then delegates CSR
4641
+ * generation, CA-certificate signing, and the initial CRL to
4642
+ * {@link CaBackend.bootstrap}. Must be called under {@link #withCaLock}.
4643
+ */
4644
+ async #bootstrap() {
4645
+ const caRootDir = path9.resolve(this.rootDir);
4646
+ mkdirRecursiveSync(caRootDir);
4647
+ ensurePrivateDirectory(path9.join(caRootDir, "private"));
4648
+ mkdirRecursiveSync(path9.join(caRootDir, "public"));
4649
+ mkdirRecursiveSync(path9.join(caRootDir, "certs"));
4650
+ mkdirRecursiveSync(path9.join(caRootDir, "crl"));
4651
+ mkdirRecursiveSync(path9.join(caRootDir, "conf"));
4652
+ const serial = path9.join(caRootDir, "serial");
4653
+ if (!fs12.existsSync(serial)) {
4654
+ await fs12.promises.writeFile(serial, "1000");
4655
+ }
4656
+ const crlNumber = path9.join(caRootDir, "crlnumber");
4657
+ if (!fs12.existsSync(crlNumber)) {
4658
+ await fs12.promises.writeFile(crlNumber, "1000");
4659
+ }
4660
+ const indexFile = path9.join(caRootDir, "index.txt");
4661
+ if (!fs12.existsSync(indexFile)) {
4662
+ await fs12.promises.writeFile(indexFile, "");
4663
+ }
4664
+ const signerBacked = this.hasExternalSigner;
4665
+ const caKeyExists = signerBacked || fs12.existsSync(path9.join(caRootDir, "private/cakey.pem"));
4666
+ const caCertExists = fs12.existsSync(path9.join(caRootDir, "public/cacert.pem"));
4667
+ if (caKeyExists && caCertExists && !config3.forceCA) {
4668
+ if (!signerBacked) {
4669
+ restrictPrivateFilePermissions(path9.join(caRootDir, "private/cakey.pem"), 384);
4670
+ await this._ensurePrivateKeyProtection();
4671
+ }
4672
+ debugLog("CA private key and certificate already exist ... skipping");
4673
+ return;
4674
+ }
4675
+ if (!signerBacked && caKeyExists && !caCertExists) {
4676
+ debugLog("CA private key exists but cacert.pem is missing \u2014 rebuilding CA");
4677
+ fs12.unlinkSync(path9.join(caRootDir, "private/cakey.pem"));
4678
+ const staleCsr = path9.join(caRootDir, "private/cakey.csr");
4679
+ if (fs12.existsSync(staleCsr)) {
4680
+ fs12.unlinkSync(staleCsr);
4681
+ }
4682
+ }
4683
+ displayTitle("Create Certificate Authority (CA)");
4684
+ const indexFileAttr = path9.join(caRootDir, "index.txt.attr");
4685
+ if (!fs12.existsSync(indexFileAttr)) {
4686
+ await fs12.promises.writeFile(indexFileAttr, "unique_subject = no");
4687
+ }
4688
+ const caConfigFile = this.configFile;
4689
+ await fs12.promises.writeFile(caConfigFile, renderCaConfig(caRootDir));
4690
+ if (!signerBacked) {
4691
+ const privateKeyFilename = path9.join(caRootDir, "private/cakey.pem");
4692
+ displayTitle(`Generate the CA private Key - ${this.keySize}`);
4693
+ await generatePrivateKeyFile2(privateKeyFilename, this.keySize, { passphrase: await this._privateKeyPassphrase() });
4694
+ restrictPrivateFilePermissions(privateKeyFilename, 384);
4695
+ }
4696
+ await this.#backend.bootstrap(this);
3834
4697
  }
3835
4698
  /**
3836
4699
  * Initialize the CA directory structure and generate the
@@ -3852,21 +4715,25 @@ var init_certificate_authority = __esm({
3852
4715
  * @returns an {@link InitializeCSRResult} describing the CA state
3853
4716
  */
3854
4717
  async initializeCSR() {
3855
- const caRootDir = path6.resolve(this.rootDir);
4718
+ const caRootDir = path9.resolve(this.rootDir);
3856
4719
  mkdirRecursiveSync(caRootDir);
4720
+ return this.#withCaLock(() => this.#initializeCSRLocked(caRootDir));
4721
+ }
4722
+ async #initializeCSRLocked(caRootDir) {
3857
4723
  for (const dir of ["public", "certs", "crl", "conf"]) {
3858
- mkdirRecursiveSync(path6.join(caRootDir, dir));
4724
+ mkdirRecursiveSync(path9.join(caRootDir, dir));
3859
4725
  }
3860
- ensurePrivateDirectory(path6.join(caRootDir, "private"));
4726
+ ensurePrivateDirectory(path9.join(caRootDir, "private"));
3861
4727
  const caCertFile = this.caCertificate;
3862
- const privateKeyFile = path6.join(caRootDir, "private/cakey.pem");
3863
- const csrFile = path6.join(caRootDir, "private/cakey.csr");
3864
- if (fs10.existsSync(privateKeyFile)) {
4728
+ const privateKeyFile = path9.join(caRootDir, "private/cakey.pem");
4729
+ const csrFile = path9.join(caRootDir, "private/cakey.csr");
4730
+ const keyAvailable = this.hasExternalSigner || fs12.existsSync(privateKeyFile);
4731
+ if (!this.hasExternalSigner && fs12.existsSync(privateKeyFile)) {
3865
4732
  restrictPrivateFilePermissions(privateKeyFile, 384);
3866
4733
  await this._ensurePrivateKeyProtection();
3867
4734
  }
3868
- if (fs10.existsSync(caCertFile)) {
3869
- const certDer = convertPEMtoDER(readCertificatePEM(caCertFile));
4735
+ if (fs12.existsSync(caCertFile)) {
4736
+ const certDer = convertPEMtoDER2(readCertificatePEM2(caCertFile));
3870
4737
  const certInfo = exploreCertificate2(certDer);
3871
4738
  const notAfter = certInfo.tbsCertificate.validity.notAfter;
3872
4739
  if (notAfter.getTime() < Date.now()) {
@@ -3877,29 +4744,29 @@ var init_certificate_authority = __esm({
3877
4744
  debugLog("CA certificate already exists and is valid \u2014 ready");
3878
4745
  return { status: "ready" };
3879
4746
  }
3880
- if (fs10.existsSync(privateKeyFile) && fs10.existsSync(csrFile)) {
4747
+ if (keyAvailable && fs12.existsSync(csrFile)) {
3881
4748
  debugLog("CA key + CSR already exist \u2014 pending external signing");
3882
4749
  return { status: "pending", csrPath: csrFile };
3883
4750
  }
3884
- const serial = path6.join(caRootDir, "serial");
3885
- if (!fs10.existsSync(serial)) {
3886
- await fs10.promises.writeFile(serial, "1000");
4751
+ const serial = path9.join(caRootDir, "serial");
4752
+ if (!fs12.existsSync(serial)) {
4753
+ await fs12.promises.writeFile(serial, "1000");
3887
4754
  }
3888
- const crlNumber = path6.join(caRootDir, "crlnumber");
3889
- if (!fs10.existsSync(crlNumber)) {
3890
- await fs10.promises.writeFile(crlNumber, "1000");
4755
+ const crlNumber = path9.join(caRootDir, "crlnumber");
4756
+ if (!fs12.existsSync(crlNumber)) {
4757
+ await fs12.promises.writeFile(crlNumber, "1000");
3891
4758
  }
3892
- const indexFile = path6.join(caRootDir, "index.txt");
3893
- if (!fs10.existsSync(indexFile)) {
3894
- await fs10.promises.writeFile(indexFile, "");
4759
+ const indexFile = path9.join(caRootDir, "index.txt");
4760
+ if (!fs12.existsSync(indexFile)) {
4761
+ await fs12.promises.writeFile(indexFile, "");
3895
4762
  }
3896
- const indexFileAttr = path6.join(caRootDir, "index.txt.attr");
3897
- if (!fs10.existsSync(indexFileAttr)) {
3898
- await fs10.promises.writeFile(indexFileAttr, "unique_subject = no");
4763
+ const indexFileAttr = path9.join(caRootDir, "index.txt.attr");
4764
+ if (!fs12.existsSync(indexFileAttr)) {
4765
+ await fs12.promises.writeFile(indexFileAttr, "unique_subject = no");
3899
4766
  }
3900
4767
  const caConfigFile = this.configFile;
3901
- await fs10.promises.writeFile(caConfigFile, renderCaConfig(caRootDir));
3902
- if (!fs10.existsSync(privateKeyFile)) {
4768
+ await fs12.promises.writeFile(caConfigFile, renderCaConfig(caRootDir));
4769
+ if (!keyAvailable) {
3903
4770
  await generatePrivateKeyFile2(privateKeyFile, this.keySize, { passphrase: await this._privateKeyPassphrase() });
3904
4771
  restrictPrivateFilePermissions(privateKeyFile, 384);
3905
4772
  }
@@ -3920,53 +4787,34 @@ var init_certificate_authority = __esm({
3920
4787
  * renewal is needed, `"ready"` if the cert is still valid
3921
4788
  */
3922
4789
  async renewCSR(thresholdDays = 30) {
3923
- const caRootDir = path6.resolve(this.rootDir);
4790
+ const caRootDir = path9.resolve(this.rootDir);
3924
4791
  const caCertFile = this.caCertificate;
3925
- const privateKeyFile = path6.join(caRootDir, "private/cakey.pem");
3926
- const csrFile = path6.join(caRootDir, "private/cakey.csr");
3927
- if (!fs10.existsSync(caCertFile)) {
4792
+ const privateKeyFile = path9.join(caRootDir, "private/cakey.pem");
4793
+ const csrFile = path9.join(caRootDir, "private/cakey.csr");
4794
+ if (!fs12.existsSync(caCertFile)) {
3928
4795
  return this.initializeCSR();
3929
4796
  }
3930
- const certDer = convertPEMtoDER(readCertificatePEM(caCertFile));
4797
+ const certDer = convertPEMtoDER2(readCertificatePEM2(caCertFile));
3931
4798
  const certInfo = exploreCertificate2(certDer);
3932
4799
  const notAfter = certInfo.tbsCertificate.validity.notAfter;
3933
4800
  const thresholdMs = thresholdDays * 24 * 60 * 60 * 1e3;
3934
4801
  if (notAfter.getTime() - Date.now() < thresholdMs) {
3935
4802
  debugLog(`CA certificate expires within ${thresholdDays} days \u2014 generating renewal CSR`);
3936
- await this._generateCSR(caRootDir, privateKeyFile, csrFile);
4803
+ await this.#withCaLock(() => this._generateCSR(caRootDir, privateKeyFile, csrFile));
3937
4804
  return { status: "expired", csrPath: csrFile, expiryDate: notAfter };
3938
4805
  }
3939
4806
  return { status: "ready" };
3940
4807
  }
3941
4808
  /**
3942
4809
  * Generate a CSR using the existing private key.
4810
+ * Must be called under {@link #withCaLock} — the lock is taken by the
4811
+ * public callers (`initializeCSR`, `renewCSR`), not here, so that
4812
+ * `initializeCSR` can hold one lock across key generation AND CSR
4813
+ * generation without the non-reentrant file lock deadlocking.
3943
4814
  * @internal
3944
4815
  */
3945
4816
  async _generateCSR(caRootDir, privateKeyFile, csrFile) {
3946
- processAltNames({});
3947
- const options = { cwd: caRootDir };
3948
- const configFile = generateStaticConfig("conf/caconfig.cnf", options);
3949
- const passin = await this._opensslPassin();
3950
- await execute_openssl(
3951
- [
3952
- "req",
3953
- "-new",
3954
- "-sha256",
3955
- "-text",
3956
- "-extensions",
3957
- "v3_ca_req",
3958
- "-config",
3959
- n5(configFile),
3960
- "-key",
3961
- n5(privateKeyFile),
3962
- "-out",
3963
- n5(csrFile),
3964
- "-subj",
3965
- this.subject.toString(),
3966
- ...passin.args
3967
- ],
3968
- { ...options, env: passin.env }
3969
- );
4817
+ await this.#backend.generateCaCsr(this, caRootDir, privateKeyFile, csrFile);
3970
4818
  }
3971
4819
  /**
3972
4820
  * Install an externally-signed CA certificate and generate
@@ -3985,43 +4833,41 @@ var init_certificate_authority = __esm({
3985
4833
  * `status: "success"` or `status: "error"` and a `reason`
3986
4834
  */
3987
4835
  async installCACertificate(signedCertFile) {
3988
- const caRootDir = path6.resolve(this.rootDir);
3989
- const caCertFile = this.caCertificate;
3990
- const fullPem = await fs10.promises.readFile(signedCertFile, "utf8");
3991
- const pemBlocks = fullPem.match(/-----BEGIN CERTIFICATE-----[\s\S]*?-----END CERTIFICATE-----/g);
3992
- if (!pemBlocks || pemBlocks.length === 0) {
3993
- return {
3994
- status: "error",
3995
- reason: "no_certificate_found",
3996
- message: "The provided file does not contain any PEM-encoded certificate."
3997
- };
3998
- }
3999
- const certDer = convertPEMtoDER(pemBlocks[0]);
4000
- const privateKey = await this.getPrivateKey();
4001
- if (!certificateMatchesPrivateKey(certDer, privateKey)) {
4002
- return {
4003
- status: "error",
4004
- reason: "certificate_key_mismatch",
4005
- message: "The provided certificate does not match the CA private key. Ensure the certificate was signed from the CSR generated by initializeCSR()."
4006
- };
4007
- }
4008
- await fs10.promises.writeFile(caCertFile, `${pemBlocks[0]}
4836
+ return this.#withCaLock(async () => {
4837
+ const caCertFile = this.caCertificate;
4838
+ const fullPem = await fs12.promises.readFile(signedCertFile, "utf8");
4839
+ const pemBlocks = fullPem.match(/-----BEGIN CERTIFICATE-----[\s\S]*?-----END CERTIFICATE-----/g);
4840
+ if (!pemBlocks || pemBlocks.length === 0) {
4841
+ return {
4842
+ status: "error",
4843
+ reason: "no_certificate_found",
4844
+ message: "The provided file does not contain any PEM-encoded certificate."
4845
+ };
4846
+ }
4847
+ const certDer = convertPEMtoDER2(pemBlocks[0]);
4848
+ if (!await this.#certificateMatchesOurKey(pemBlocks[0], certDer)) {
4849
+ return {
4850
+ status: "error",
4851
+ reason: "certificate_key_mismatch",
4852
+ message: "The provided certificate does not match the CA private key. Ensure the certificate was signed from the CSR generated by initializeCSR()."
4853
+ };
4854
+ }
4855
+ await fs12.promises.writeFile(caCertFile, `${pemBlocks[0]}
4009
4856
  `);
4010
- const issuerChainFile = this.issuerCertificateChain;
4011
- if (pemBlocks.length > 1) {
4012
- const issuerPem = `${pemBlocks.slice(1).join("\n")}
4857
+ const issuerChainFile = this.issuerCertificateChain;
4858
+ if (pemBlocks.length > 1) {
4859
+ const issuerPem = `${pemBlocks.slice(1).join("\n")}
4013
4860
  `;
4014
- await fs10.promises.writeFile(issuerChainFile, issuerPem);
4015
- debugLog(`Stored ${pemBlocks.length - 1} issuer certificate(s) in issuer_chain.pem`);
4016
- } else {
4017
- if (fs10.existsSync(issuerChainFile)) {
4018
- await fs10.promises.unlink(issuerChainFile);
4861
+ await fs12.promises.writeFile(issuerChainFile, issuerPem);
4862
+ debugLog(`Stored ${pemBlocks.length - 1} issuer certificate(s) in issuer_chain.pem`);
4863
+ } else {
4864
+ if (fs12.existsSync(issuerChainFile)) {
4865
+ await fs12.promises.unlink(issuerChainFile);
4866
+ }
4019
4867
  }
4020
- }
4021
- const options = { cwd: caRootDir };
4022
- const configFile = generateStaticConfig("conf/caconfig.cnf", options);
4023
- await regenerateCrl(this.revocationList, ["-config", n5(configFile)], options, await this._opensslPassin());
4024
- return { status: "success" };
4868
+ await this.#backend.regenerateCrl(this);
4869
+ return { status: "success" };
4870
+ });
4025
4871
  }
4026
4872
  /**
4027
4873
  * Sign a CSR with CA extensions (`v3_ca`), producing a
@@ -4037,39 +4883,11 @@ var init_certificate_authority = __esm({
4037
4883
  * @param params - signing parameters
4038
4884
  */
4039
4885
  async signCACertificateRequest(certFile, csrFile, params) {
4040
- const caRootDir = path6.resolve(this.rootDir);
4041
- const options = { cwd: caRootDir };
4042
- this._wireRevocationEnvVars();
4043
- const configFile = generateStaticConfig("conf/caconfig.cnf", options);
4044
- const validity = params.validity ?? 3650;
4045
- const passin = await this._opensslPassin();
4046
- await execute_openssl(
4047
- [
4048
- "x509",
4049
- "-sha256",
4050
- "-req",
4051
- "-days",
4052
- String(validity),
4053
- "-text",
4054
- "-extensions",
4055
- "v3_ca",
4056
- "-extfile",
4057
- n5(configFile),
4058
- "-in",
4059
- n5(csrFile),
4060
- "-CA",
4061
- n5(this.caCertificate),
4062
- "-CAkey",
4063
- n5(path6.join(caRootDir, "private/cakey.pem")),
4064
- "-CAserial",
4065
- n5(path6.join(caRootDir, "serial")),
4066
- "-out",
4067
- n5(certFile),
4068
- ...passin.args
4069
- ],
4070
- { ...options, env: passin.env }
4071
- );
4072
- await this.constructCertificateChain(certFile);
4886
+ await this.#withCaLock(async () => {
4887
+ const validity = params.validity ?? 3650;
4888
+ await this.#backend.signSubordinateCsr(this, csrFile, certFile, validity);
4889
+ await this.constructCertificateChain(certFile);
4890
+ });
4073
4891
  }
4074
4892
  /**
4075
4893
  * Rebuild the combined CA certificate + CRL file.
@@ -4080,13 +4898,13 @@ var init_certificate_authority = __esm({
4080
4898
  */
4081
4899
  async constructCACertificateWithCRL() {
4082
4900
  const cacertWithCRL = this.caCertificateWithCrl;
4083
- if (fs10.existsSync(this.revocationList)) {
4084
- await fs10.promises.writeFile(
4901
+ if (fs12.existsSync(this.revocationList)) {
4902
+ await fs12.promises.writeFile(
4085
4903
  cacertWithCRL,
4086
- fs10.readFileSync(this.caCertificate, "utf8") + fs10.readFileSync(this.revocationList, "utf8")
4904
+ fs12.readFileSync(this.caCertificate, "utf8") + fs12.readFileSync(this.revocationList, "utf8")
4087
4905
  );
4088
4906
  } else {
4089
- await fs10.promises.writeFile(cacertWithCRL, fs10.readFileSync(this.caCertificate));
4907
+ await fs12.promises.writeFile(cacertWithCRL, fs12.readFileSync(this.caCertificate));
4090
4908
  }
4091
4909
  }
4092
4910
  /**
@@ -4096,15 +4914,15 @@ var init_certificate_authority = __esm({
4096
4914
  * @param certificate - path to the certificate file to extend
4097
4915
  */
4098
4916
  async constructCertificateChain(certificate) {
4099
- assert10(fs10.existsSync(certificate));
4100
- assert10(fs10.existsSync(this.caCertificate));
4917
+ assert9(fs12.existsSync(certificate));
4918
+ assert9(fs12.existsSync(this.caCertificate));
4101
4919
  debugLog(chalk6.yellow(" certificate file :"), chalk6.cyan(certificate));
4102
- let chain = await fs10.promises.readFile(certificate, "utf8");
4103
- chain += await fs10.promises.readFile(this.caCertificate, "utf8");
4104
- if (fs10.existsSync(this.issuerCertificateChain)) {
4105
- chain += await fs10.promises.readFile(this.issuerCertificateChain, "utf8");
4920
+ let chain = await fs12.promises.readFile(certificate, "utf8");
4921
+ chain += await fs12.promises.readFile(this.caCertificate, "utf8");
4922
+ if (fs12.existsSync(this.issuerCertificateChain)) {
4923
+ chain += await fs12.promises.readFile(this.issuerCertificateChain, "utf8");
4106
4924
  }
4107
- await fs10.promises.writeFile(certificate, chain);
4925
+ await fs12.promises.writeFile(certificate, chain);
4108
4926
  }
4109
4927
  /**
4110
4928
  * Create a self-signed certificate using OpenSSL.
@@ -4114,52 +4932,30 @@ var init_certificate_authority = __esm({
4114
4932
  * @param params - certificate parameters (subject, validity, SANs)
4115
4933
  */
4116
4934
  async createSelfSignedCertificate(certificateFile, privateKey, params) {
4117
- assert10(typeof privateKey === "string");
4118
- assert10(fs10.existsSync(privateKey));
4935
+ assert9(typeof privateKey === "string");
4936
+ assert9(fs12.existsSync(privateKey));
4119
4937
  if (!certificateFileExist(certificateFile)) {
4120
4938
  return;
4121
4939
  }
4122
4940
  adjustDate(params);
4123
4941
  adjustApplicationUri(params);
4124
- processAltNames(params);
4125
- const csrFile = `${certificateFile}_csr`;
4126
- assert10(csrFile);
4127
- const configFile = generateStaticConfig(this.configFile, { cwd: this.rootDir });
4128
- const options = {
4129
- cwd: this.rootDir,
4130
- openssl_conf: makePath(configFile)
4131
- };
4132
- const subject = params.subject ? new Subject4(params.subject).toString() : "";
4133
- const subjectOptions = subject && subject.length > 1 ? ["-subj", subject] : [];
4134
- displaySubtitle("- the certificate signing request");
4135
- await execute_openssl(
4136
- ["req", "-new", "-sha256", "-text", ...subjectOptions, "-batch", "-key", n5(privateKey), "-out", n5(csrFile)],
4137
- options
4138
- );
4139
- displaySubtitle("- creating the self-signed certificate");
4140
- await execute_openssl(
4141
- [
4142
- "ca",
4143
- "-selfsign",
4144
- "-keyfile",
4145
- n5(privateKey),
4146
- "-startdate",
4147
- x509Date(params.startDate),
4148
- "-enddate",
4149
- x509Date(params.endDate),
4150
- "-batch",
4151
- "-out",
4152
- n5(certificateFile),
4153
- "-in",
4154
- n5(csrFile)
4155
- ],
4156
- options
4157
- );
4158
- displaySubtitle("- dump the certificate for a check");
4159
- await execute_openssl(["x509", "-in", n5(certificateFile), "-dates", "-fingerprint", "-purpose", "-noout"], {});
4160
- displaySubtitle("- verify self-signed certificate");
4161
- await execute_openssl_no_failure(["verify", "-verbose", "-CAfile", n5(certificateFile), n5(certificateFile)], options);
4162
- await fs10.promises.unlink(csrFile);
4942
+ params.dns = params.dns || [];
4943
+ params.ip = params.ip || [];
4944
+ await this.#withCaLock(async () => {
4945
+ await this.#backend.createSelfSignedCertificate(this, certificateFile, privateKey, params);
4946
+ });
4947
+ }
4948
+ /**
4949
+ * Regenerate `crl/revocation_list.{crl,der}` from the current database
4950
+ * state, without changing any certificate's status. Normally
4951
+ * unnecessary `revokeCertificate` already regenerates the CRL as
4952
+ * part of revoking — but useful to force a refresh (e.g. after
4953
+ * switching a CA's `backend` between `"openssl"` and `"native"`).
4954
+ */
4955
+ async regenerateCrl() {
4956
+ await this.#withCaLock(async () => {
4957
+ await this.#backend.regenerateCrl(this);
4958
+ });
4163
4959
  }
4164
4960
  /**
4165
4961
  * Revoke a certificate and regenerate the CRL.
@@ -4180,49 +4976,12 @@ var init_certificate_authority = __esm({
4180
4976
  "certificateHold",
4181
4977
  "removeFromCRL"
4182
4978
  ];
4183
- const configFile = generateStaticConfig("conf/caconfig.cnf", { cwd: this.rootDir });
4184
- const options = {
4185
- cwd: this.rootDir,
4186
- openssl_conf: makePath(configFile)
4187
- };
4188
- setEnv("ALTNAME", "");
4189
- const randomFile = path6.join(this.rootDir, "random.rnd");
4190
- setEnv("RANDFILE", randomFile);
4191
- const configOption = ["-config", n5(configFile)];
4192
4979
  const reason = params.reason || "keyCompromise";
4193
- assert10(crlReasons.indexOf(reason) >= 0);
4980
+ assert9(crlReasons.indexOf(reason) >= 0);
4194
4981
  displayTitle(`Revoking certificate ${certificate}`);
4195
- displaySubtitle("Revoke certificate");
4196
- const passin = await this._opensslPassin();
4197
- await execute_openssl_no_failure(
4198
- ["ca", "-verbose", ...configOption, "-revoke", certificate, "-crl_reason", reason, ...passin.args],
4199
- { ...options, env: passin.env }
4200
- );
4201
- await regenerateCrl(this.revocationList, configOption, options, passin);
4202
- displaySubtitle("Verify that certificate is revoked");
4203
- await execute_openssl_no_failure(
4204
- [
4205
- "verify",
4206
- "-verbose",
4207
- "-CRLfile",
4208
- n5(this.revocationList),
4209
- "-CAfile",
4210
- n5(this.caCertificate),
4211
- "-crl_check",
4212
- n5(certificate)
4213
- ],
4214
- options
4215
- );
4216
- displaySubtitle("Produce CRL in DER form ");
4217
- await execute_openssl(
4218
- ["crl", "-in", n5(this.revocationList), "-out", "crl/revocation_list.der", "-outform", "der"],
4219
- options
4220
- );
4221
- displaySubtitle("Produce CRL in PEM form ");
4222
- await execute_openssl(
4223
- ["crl", "-in", n5(this.revocationList), "-out", "crl/revocation_list.pem", "-outform", "pem", "-text"],
4224
- options
4225
- );
4982
+ await this.#withCaLock(async () => {
4983
+ await this.#backend.revoke(this, certificate, reason);
4984
+ });
4226
4985
  }
4227
4986
  /**
4228
4987
  * Sign a Certificate Signing Request (CSR) with this CA.
@@ -4237,15 +4996,13 @@ var init_certificate_authority = __esm({
4237
4996
  * @returns the path to the signed certificate
4238
4997
  */
4239
4998
  async signCertificateRequest(certificate, certificateSigningRequestFilename, params1) {
4240
- await ensure_openssl_installed();
4241
- assert10(fs10.existsSync(certificateSigningRequestFilename));
4999
+ await this.#backend.preflight();
5000
+ assert9(fs12.existsSync(certificateSigningRequestFilename));
4242
5001
  if (!certificateFileExist(certificate)) {
4243
5002
  return "";
4244
5003
  }
4245
5004
  adjustDate(params1);
4246
5005
  adjustApplicationUri(params1);
4247
- processAltNames(params1);
4248
- const options = { cwd: this.rootDir };
4249
5006
  const csr = await readCertificateSigningRequest(certificateSigningRequestFilename);
4250
5007
  const csrInfo = exploreCertificateSigningRequest(csr);
4251
5008
  const applicationUri = csrInfo.extensionRequest.subjectAltName.uniformResourceIdentifier ? csrInfo.extensionRequest.subjectAltName.uniformResourceIdentifier[0] : void 0;
@@ -4255,73 +5012,122 @@ var init_certificate_authority = __esm({
4255
5012
  const dns2 = csrInfo.extensionRequest.subjectAltName.dNSName || [];
4256
5013
  let ip = csrInfo.extensionRequest.subjectAltName.iPAddress || [];
4257
5014
  ip = ip.map(octetStringToIpAddress);
4258
- const params = {
4259
- applicationUri,
4260
- dns: dns2,
4261
- ip
4262
- };
4263
- processAltNames(params);
4264
- this._wireRevocationEnvVars();
4265
- const configFile = generateStaticConfig("conf/caconfig.cnf", options);
4266
- displaySubtitle("- then we ask the authority to sign the certificate signing request");
4267
- const passin = await this._opensslPassin();
4268
- await execute_openssl(
4269
- [
4270
- "ca",
4271
- "-config",
4272
- configFile,
4273
- "-startdate",
4274
- x509Date(params1.startDate),
4275
- "-enddate",
4276
- x509Date(params1.endDate),
4277
- "-batch",
4278
- "-out",
4279
- n5(certificate),
4280
- "-in",
4281
- n5(certificateSigningRequestFilename),
4282
- ...passin.args
4283
- ],
4284
- { ...options, env: passin.env }
4285
- );
4286
- displaySubtitle("- dump the certificate for a check");
4287
- await execute_openssl(["x509", "-in", n5(certificate), "-dates", "-fingerprint", "-purpose", "-noout"], options);
4288
- displaySubtitle("- construct CA certificate with CRL");
4289
- await this.constructCACertificateWithCRL();
4290
- displaySubtitle("- construct certificate chain");
4291
- await this.constructCertificateChain(certificate);
4292
- displaySubtitle("- verify certificate against the root CA");
4293
- await this.verifyCertificate(certificate);
4294
- return certificate;
5015
+ const sanOverride = { applicationUri, dns: dns2, ip };
5016
+ return this.#withCaLock(async () => {
5017
+ await this.#backend.signEndEntityCsr(this, certificate, certificateSigningRequestFilename, params1, sanOverride);
5018
+ displaySubtitle("- construct CA certificate with CRL");
5019
+ await this.constructCACertificateWithCRL();
5020
+ displaySubtitle("- construct certificate chain");
5021
+ await this.constructCertificateChain(certificate);
5022
+ displaySubtitle("- verify certificate against the root CA");
5023
+ await this.verifyCertificate(certificate);
5024
+ return certificate;
5025
+ });
4295
5026
  }
4296
5027
  /**
4297
- * Verify a certificate against this CA.
5028
+ * Check that `certificate` really was signed by this CA, and throw if it
5029
+ * was not.
5030
+ *
5031
+ * This used to do nothing: `openssl verify` crashes on Windows, so the
5032
+ * check was left as a placeholder. It no longer needs a subprocess -
5033
+ * `verifyCertificateSignature` does it in pure JS, the same way
5034
+ * {@link CertificateManager} already validates a chain - so the check
5035
+ * that `signCertificateRequest` always claimed to perform now actually
5036
+ * happens on every issuance, whichever backend did the signing.
4298
5037
  *
4299
- * @param certificate - path to the certificate file to verify
5038
+ * Only the leading certificate is examined: the file may be a chain,
5039
+ * and the rest of it is this CA's own certificate and its issuers.
5040
+ *
5041
+ * @param certificate - path to the certificate (or chain) to verify
4300
5042
  */
4301
5043
  async verifyCertificate(certificate) {
4302
- const isImplemented = false;
4303
- if (isImplemented) {
4304
- const options = { cwd: this.rootDir };
4305
- const configFile = generateStaticConfig("conf/caconfig.cnf", options);
4306
- setEnv("OPENSSL_CONF", makePath(configFile));
4307
- await execute_openssl_no_failure(
4308
- ["verify", "-verbose", "-CAfile", n5(this.caCertificateWithCrl), n5(certificate)],
4309
- options
5044
+ const pem = await fs12.promises.readFile(certificate, "utf-8");
5045
+ const blocks = pem.match(/-----BEGIN CERTIFICATE-----[\s\S]*?-----END CERTIFICATE-----/g);
5046
+ if (!blocks || blocks.length === 0) {
5047
+ throw new Error(`verifyCertificate: ${certificate} contains no PEM-encoded certificate`);
5048
+ }
5049
+ const caCertificateDer = convertPEMtoDER2(readCertificatePEM2(this.caCertificate));
5050
+ if (!verifyCertificateSignature2(convertPEMtoDER2(blocks[0]), caCertificateDer)) {
5051
+ throw new Error(
5052
+ `verifyCertificate: ${certificate} was not signed by this certificate authority (${this.caCertificate})`
5053
+ );
5054
+ }
5055
+ }
5056
+ };
5057
+ }
5058
+ });
5059
+
5060
+ // packages/node-opcua-pki/lib/ca/certificate_authority.ts
5061
+ var CertificateAuthority;
5062
+ var init_certificate_authority = __esm({
5063
+ "packages/node-opcua-pki/lib/ca/certificate_authority.ts"() {
5064
+ "use strict";
5065
+ init_esm_shims();
5066
+ init_with_openssl();
5067
+ init_native_ca_backend();
5068
+ init_openssl_ca_backend();
5069
+ init_certificate_authority_core();
5070
+ init_certificate_authority_core();
5071
+ CertificateAuthority = class extends CertificateAuthorityCore {
5072
+ constructor(options) {
5073
+ if (options.signer && options.backend === "openssl") {
5074
+ throw new Error(
5075
+ "CertificateAuthority: backend 'openssl' cannot be combined with 'signer' - the openssl CLI loads its key from a file and cannot call an external signer. Drop the backend option (a signer implies 'native') or drop the signer."
4310
5076
  );
4311
5077
  }
5078
+ const backend = options.signer || options.backend === "native" ? new NativeCaBackend() : new OpenSslCaBackend();
5079
+ super({ ...options, backend });
5080
+ }
5081
+ /**
5082
+ * @internal `-passin env:` argv + env for an openssl call that loads
5083
+ * this CA's key (always emitted, empty when none).
5084
+ *
5085
+ * The openssl backend builds its own now; this remains so that external
5086
+ * code calling it keeps working, and lives here rather than on the core
5087
+ * because the flag means nothing to a backend that spawns nothing.
5088
+ */
5089
+ async _opensslPassin() {
5090
+ return passinArg(await this._privateKeyPassphrase());
5091
+ }
5092
+ /**
5093
+ * @internal
5094
+ * Legacy shim: publish the `CDP_URL` / `AIA_VALUE` config substitution
5095
+ * values to the shared env registry, or unset them so the matching
5096
+ * `{{#KEY}}...{{/KEY}}` blocks are stripped. Nothing in this package
5097
+ * reads the registry any more - every openssl config render receives
5098
+ * these values explicitly, from the same {@link caConfigEnvOverrides}
5099
+ * builder this delegates to, so the two cannot drift - kept only for
5100
+ * external code that renders openssl config templates against the
5101
+ * registry directly.
5102
+ *
5103
+ * It lives on this class rather than the core because it is meaningful
5104
+ * only to the openssl backend.
5105
+ */
5106
+ _wireRevocationEnvVars() {
5107
+ const overrides = caConfigEnvOverrides(this);
5108
+ if (overrides.CDP_URL) {
5109
+ setEnv("CDP_URL", overrides.CDP_URL);
5110
+ } else {
5111
+ unsetEnv("CDP_URL");
5112
+ }
5113
+ if (overrides.AIA_VALUE) {
5114
+ setEnv("AIA_VALUE", overrides.AIA_VALUE);
5115
+ } else {
5116
+ unsetEnv("AIA_VALUE");
5117
+ }
4312
5118
  }
4313
5119
  };
4314
5120
  }
4315
5121
  });
4316
5122
 
4317
5123
  // packages/node-opcua-pki/lib/ca/crypto_create_CA.ts
4318
- import assert11 from "assert";
4319
- import fs11 from "fs";
5124
+ import assert10 from "assert";
5125
+ import fs13 from "fs";
4320
5126
  import { createRequire } from "module";
4321
5127
  import os5 from "os";
4322
- import path7 from "path";
5128
+ import path10 from "path";
4323
5129
  import chalk7 from "chalk";
4324
- import { CertificatePurpose as CertificatePurpose3, generatePrivateKeyFile as generatePrivateKeyFile3, Subject as Subject5 } from "node-opcua-crypto";
5130
+ import { CertificatePurpose as CertificatePurpose4, generatePrivateKeyFile as generatePrivateKeyFile3, Subject as Subject7 } from "node-opcua-crypto";
4325
5131
  import commandLineArgs from "command-line-args";
4326
5132
  import commandLineUsage from "command-line-usage";
4327
5133
  function get_offset_date(date, nbDays) {
@@ -4329,9 +5135,9 @@ function get_offset_date(date, nbDays) {
4329
5135
  d.setDate(d.getDate() + nbDays);
4330
5136
  return d;
4331
5137
  }
4332
- async function construct_CertificateAuthority2(subject) {
4333
- assert11(typeof gLocalConfig.CAFolder === "string", "expecting a CAFolder in config");
4334
- assert11(typeof gLocalConfig.keySize === "number", "expecting a keySize in config");
5138
+ async function construct_CertificateAuthority(subject) {
5139
+ assert10(typeof gLocalConfig.CAFolder === "string", "expecting a CAFolder in config");
5140
+ assert10(typeof gLocalConfig.keySize === "number", "expecting a keySize in config");
4335
5141
  if (!g_certificateAuthority) {
4336
5142
  g_certificateAuthority = new CertificateAuthority({
4337
5143
  keySize: gLocalConfig.keySize,
@@ -4342,7 +5148,7 @@ async function construct_CertificateAuthority2(subject) {
4342
5148
  }
4343
5149
  }
4344
5150
  async function construct_CertificateManager() {
4345
- assert11(typeof gLocalConfig.PKIFolder === "string", "expecting a PKIFolder in config");
5151
+ assert10(typeof gLocalConfig.PKIFolder === "string", "expecting a PKIFolder in config");
4346
5152
  if (!certificateManager) {
4347
5153
  certificateManager = new CertificateManager({
4348
5154
  keySize: gLocalConfig.keySize,
@@ -4353,35 +5159,35 @@ async function construct_CertificateManager() {
4353
5159
  }
4354
5160
  function default_template_content() {
4355
5161
  if (process.pkg?.entrypoint) {
4356
- const a = fs11.readFileSync(path7.join(__dirname, "../../bin/pki_config.example.js"), "utf8");
5162
+ const a = fs13.readFileSync(path10.join(__dirname, "../../bin/pki_config.example.js"), "utf8");
4357
5163
  return a;
4358
5164
  }
4359
5165
  function find_default_config_template() {
4360
5166
  const rootFolder = find_module_root_folder();
4361
5167
  const configName = "pki_config.example.js";
4362
- let default_config_template2 = path7.join(rootFolder, "bin", configName);
4363
- if (!fs11.existsSync(default_config_template2)) {
4364
- default_config_template2 = path7.join(__dirname, "..", configName);
4365
- if (!fs11.existsSync(default_config_template2)) {
4366
- default_config_template2 = path7.join(__dirname, `../bin/${configName}`);
5168
+ let default_config_template2 = path10.join(rootFolder, "bin", configName);
5169
+ if (!fs13.existsSync(default_config_template2)) {
5170
+ default_config_template2 = path10.join(__dirname, "..", configName);
5171
+ if (!fs13.existsSync(default_config_template2)) {
5172
+ default_config_template2 = path10.join(__dirname, `../bin/${configName}`);
4367
5173
  }
4368
5174
  }
4369
5175
  return default_config_template2;
4370
5176
  }
4371
5177
  const default_config_template = find_default_config_template();
4372
- assert11(fs11.existsSync(default_config_template));
4373
- const default_config_template_content = fs11.readFileSync(default_config_template, "utf8");
5178
+ assert10(fs13.existsSync(default_config_template));
5179
+ const default_config_template_content = fs13.readFileSync(default_config_template, "utf8");
4374
5180
  return default_config_template_content;
4375
5181
  }
4376
5182
  function find_module_root_folder() {
4377
- let rootFolder = path7.join(__dirname);
5183
+ let rootFolder = path10.join(__dirname);
4378
5184
  for (let i = 0; i < 4; i++) {
4379
- if (fs11.existsSync(path7.join(rootFolder, "package.json"))) {
5185
+ if (fs13.existsSync(path10.join(rootFolder, "package.json"))) {
4380
5186
  return rootFolder;
4381
5187
  }
4382
- rootFolder = path7.join(rootFolder, "..");
5188
+ rootFolder = path10.join(rootFolder, "..");
4383
5189
  }
4384
- assert11(fs11.existsSync(path7.join(rootFolder, "package.json")), "root folder must have a package.json file");
5190
+ assert10(fs13.existsSync(path10.join(rootFolder, "package.json")), "root folder must have a package.json file");
4385
5191
  return rootFolder;
4386
5192
  }
4387
5193
  async function readConfiguration(argv) {
@@ -4406,41 +5212,41 @@ async function readConfiguration(argv) {
4406
5212
  return str;
4407
5213
  }
4408
5214
  function prepare(file) {
4409
- const tmp = path7.resolve(performSubstitution(file));
5215
+ const tmp = path10.resolve(performSubstitution(file));
4410
5216
  return makePath(tmp);
4411
5217
  }
4412
5218
  certificateDir = argv.root;
4413
- assert11(typeof certificateDir === "string");
5219
+ assert10(typeof certificateDir === "string");
4414
5220
  certificateDir = prepare(certificateDir);
4415
5221
  mkdirRecursiveSync(certificateDir);
4416
- assert11(fs11.existsSync(certificateDir));
4417
- const default_config = path7.join(certificateDir, "config.js");
4418
- if (!fs11.existsSync(default_config)) {
5222
+ assert10(fs13.existsSync(certificateDir));
5223
+ const default_config = path10.join(certificateDir, "config.js");
5224
+ if (!fs13.existsSync(default_config)) {
4419
5225
  debugLog(chalk7.yellow(" Creating default g_config file "), chalk7.cyan(default_config));
4420
5226
  const default_config_template_content = default_template_content();
4421
- fs11.writeFileSync(default_config, default_config_template_content);
5227
+ fs13.writeFileSync(default_config, default_config_template_content);
4422
5228
  } else {
4423
5229
  debugLog(chalk7.yellow(" using g_config file "), chalk7.cyan(default_config));
4424
5230
  }
4425
- if (!fs11.existsSync(default_config)) {
5231
+ if (!fs13.existsSync(default_config)) {
4426
5232
  debugLog(chalk7.redBright(" cannot find config file ", default_config));
4427
5233
  }
4428
- const defaultRandomFile = path7.join(path7.dirname(default_config), "random.rnd");
5234
+ const defaultRandomFile = path10.join(path10.dirname(default_config), "random.rnd");
4429
5235
  setEnv("RANDFILE", defaultRandomFile);
4430
5236
  const _require = createRequire(__filename);
4431
5237
  gLocalConfig = _require(default_config);
4432
- gLocalConfig.subject = new Subject5(gLocalConfig.subject || "");
5238
+ gLocalConfig.subject = new Subject7(gLocalConfig.subject || "");
4433
5239
  if (argv.subject) {
4434
- gLocalConfig.subject = new Subject5(argv.subject);
5240
+ gLocalConfig.subject = new Subject7(argv.subject);
4435
5241
  }
4436
5242
  if (!gLocalConfig.subject.commonName) {
4437
5243
  throw new Error("subject must have a Common Name");
4438
5244
  }
4439
5245
  gLocalConfig.certificateDir = certificateDir;
4440
- let CAFolder = argv.CAFolder || path7.join(certificateDir, "CA");
5246
+ let CAFolder = argv.CAFolder || path10.join(certificateDir, "CA");
4441
5247
  CAFolder = prepare(CAFolder);
4442
5248
  gLocalConfig.CAFolder = CAFolder;
4443
- gLocalConfig.PKIFolder = path7.join(gLocalConfig.certificateDir, "PKI");
5249
+ gLocalConfig.PKIFolder = path10.join(gLocalConfig.certificateDir, "PKI");
4444
5250
  if (argv.PKIFolder) {
4445
5251
  gLocalConfig.PKIFolder = prepare(argv.PKIFolder);
4446
5252
  }
@@ -4478,7 +5284,7 @@ async function readConfiguration(argv) {
4478
5284
  }
4479
5285
  }
4480
5286
  async function createDefaultCertificate(base_name, prefix, key_length, applicationUri, dev) {
4481
- assert11(key_length === 1024 || key_length === 2048 || key_length === 3072 || key_length === 4096);
5287
+ assert10(key_length === 1024 || key_length === 2048 || key_length === 3072 || key_length === 4096);
4482
5288
  const private_key_file = makePath(base_name, `${prefix}key_${key_length}.pem`);
4483
5289
  const public_key_file = makePath(base_name, `${prefix}public_key_${key_length}.pub`);
4484
5290
  const certificate_file = makePath(base_name, `${prefix}cert_${key_length}.pem`);
@@ -4498,7 +5304,7 @@ async function createDefaultCertificate(base_name, prefix, key_length, applicati
4498
5304
  }
4499
5305
  const ip = [];
4500
5306
  async function createCertificateIfNotExist(certificate, private_key, applicationUri2, startDate, validity) {
4501
- if (fs11.existsSync(certificate)) {
5307
+ if (fs13.existsSync(certificate)) {
4502
5308
  warningLog(chalk7.yellow(" certificate"), chalk7.cyan(certificate), chalk7.yellow(" already exists => skipping"));
4503
5309
  return "";
4504
5310
  } else {
@@ -4517,7 +5323,7 @@ async function createDefaultCertificate(base_name, prefix, key_length, applicati
4517
5323
  configFile,
4518
5324
  dns: dns3,
4519
5325
  ip: ip2,
4520
- purpose: CertificatePurpose3.ForApplication
5326
+ purpose: CertificatePurpose4.ForApplication
4521
5327
  };
4522
5328
  await createCertificateSigningRequestWithOpenSSL(certificateSigningRequestFile, params);
4523
5329
  return await g_certificateAuthority.signCertificateRequest(certificate, certificateSigningRequestFile, {
@@ -4541,7 +5347,7 @@ async function createDefaultCertificate(base_name, prefix, key_length, applicati
4541
5347
  await g_certificateAuthority.revokeCertificate(certificate, {});
4542
5348
  }
4543
5349
  async function createPrivateKeyIfNotExist(privateKey, keyLength) {
4544
- if (fs11.existsSync(privateKey)) {
5350
+ if (fs13.existsSync(privateKey)) {
4545
5351
  warningLog(chalk7.yellow(" privateKey"), chalk7.cyan(privateKey), chalk7.yellow(" already exists => skipping"));
4546
5352
  return;
4547
5353
  } else {
@@ -4555,14 +5361,14 @@ async function createDefaultCertificate(base_name, prefix, key_length, applicati
4555
5361
  displaySubtitle(` create Certificate ${certificate_file}`);
4556
5362
  await createCertificateIfNotExist(certificate_file, private_key_file, applicationUri, yesterday, 365);
4557
5363
  displaySubtitle(` create self signed Certificate ${self_signed_certificate_file}`);
4558
- if (fs11.existsSync(self_signed_certificate_file)) {
5364
+ if (fs13.existsSync(self_signed_certificate_file)) {
4559
5365
  return;
4560
5366
  }
4561
5367
  await createSelfSignedCertificate2(self_signed_certificate_file, private_key_file, applicationUri, yesterday, 365);
4562
5368
  if (dev) {
4563
5369
  await createCertificateIfNotExist(certificate_file_outofdate, private_key_file, applicationUri, two_years_ago, 365);
4564
5370
  await createCertificateIfNotExist(certificate_file_not_active_yet, private_key_file, applicationUri, next_year, 365);
4565
- if (!fs11.existsSync(certificate_revoked)) {
5371
+ if (!fs13.existsSync(certificate_revoked)) {
4566
5372
  const certificate = await createCertificateIfNotExist(
4567
5373
  certificate_revoked,
4568
5374
  private_key_file,
@@ -4584,9 +5390,9 @@ async function wrap(func) {
4584
5390
  }
4585
5391
  }
4586
5392
  async function create_default_certificates(dev) {
4587
- assert11(gLocalConfig);
5393
+ assert10(gLocalConfig);
4588
5394
  const base_name = gLocalConfig.certificateDir || "";
4589
- assert11(fs11.existsSync(base_name));
5395
+ assert10(fs13.existsSync(base_name));
4590
5396
  let clientURN;
4591
5397
  let serverURN;
4592
5398
  let discoveryServerURN;
@@ -4617,7 +5423,7 @@ async function create_default_certificates(dev) {
4617
5423
  });
4618
5424
  }
4619
5425
  async function createDefaultCertificates(dev) {
4620
- await construct_CertificateAuthority2("");
5426
+ await construct_CertificateAuthority("");
4621
5427
  await construct_CertificateManager();
4622
5428
  await create_default_certificates(dev);
4623
5429
  }
@@ -4683,7 +5489,7 @@ ${epilog}`
4683
5489
  }
4684
5490
  if (command === "version") {
4685
5491
  const rootFolder = find_module_root_folder();
4686
- const pkg = JSON.parse(fs11.readFileSync(path7.join(rootFolder, "package.json"), "utf-8"));
5492
+ const pkg = JSON.parse(fs13.readFileSync(path10.join(rootFolder, "package.json"), "utf-8"));
4687
5493
  console.log(pkg.version);
4688
5494
  return;
4689
5495
  }
@@ -4708,12 +5514,12 @@ ${epilog}`
4708
5514
  await readConfiguration(local_argv);
4709
5515
  if (local_argv.clean) {
4710
5516
  displayTitle("Cleaning old certificates");
4711
- assert11(gLocalConfig);
5517
+ assert10(gLocalConfig);
4712
5518
  const certificateDir = gLocalConfig.certificateDir || "";
4713
- const files = await fs11.promises.readdir(certificateDir);
5519
+ const files = await fs13.promises.readdir(certificateDir);
4714
5520
  for (const file of files) {
4715
5521
  if (file.includes(".pem") || file.includes(".pub")) {
4716
- await fs11.promises.unlink(path7.join(certificateDir, file));
5522
+ await fs13.promises.unlink(path10.join(certificateDir, file));
4717
5523
  }
4718
5524
  }
4719
5525
  mkdirRecursiveSync(certificateDir);
@@ -4734,7 +5540,7 @@ ${epilog}`
4734
5540
  await wrap(async () => {
4735
5541
  await ensure_openssl_installed();
4736
5542
  await readConfiguration(local_argv);
4737
- await construct_CertificateAuthority2(local_argv.subject);
5543
+ await construct_CertificateAuthority(local_argv.subject);
4738
5544
  });
4739
5545
  return;
4740
5546
  }
@@ -4803,7 +5609,7 @@ ${epilog}`
4803
5609
  await readConfiguration(local_argv2);
4804
5610
  await construct_CertificateManager();
4805
5611
  displaySubtitle(` create self signed Certificate ${gLocalConfig.outputFile}`);
4806
- let subject = local_argv2.subject && local_argv2.subject.length > 1 ? new Subject5(local_argv2.subject) : gLocalConfig.subject || "";
5612
+ let subject = local_argv2.subject && local_argv2.subject.length > 1 ? new Subject7(local_argv2.subject) : gLocalConfig.subject || "";
4807
5613
  subject = JSON.parse(JSON.stringify(subject));
4808
5614
  const params = {
4809
5615
  applicationUri: gLocalConfig.applicationUri || "",
@@ -4819,8 +5625,8 @@ ${epilog}`
4819
5625
  async function command_full_certificate(local_argv2) {
4820
5626
  await readConfiguration(local_argv2);
4821
5627
  await construct_CertificateManager();
4822
- await construct_CertificateAuthority2("");
4823
- assert11(fs11.existsSync(gLocalConfig.CAFolder || ""), " CA folder must exist");
5628
+ await construct_CertificateAuthority("");
5629
+ assert10(fs13.existsSync(gLocalConfig.CAFolder || ""), " CA folder must exist");
4824
5630
  gLocalConfig.privateKey = void 0;
4825
5631
  gLocalConfig.subject = local_argv2.subject && local_argv2.subject.length > 1 ? local_argv2.subject : gLocalConfig.subject;
4826
5632
  const csr_file = await certificateManager.createCertificateRequest(
@@ -4831,7 +5637,7 @@ ${epilog}`
4831
5637
  }
4832
5638
  warningLog(" csr_file = ", csr_file);
4833
5639
  const certificate = csr_file.replace(".csr", ".pem");
4834
- if (fs11.existsSync(certificate)) {
5640
+ if (fs13.existsSync(certificate)) {
4835
5641
  throw new Error(` File ${certificate} already exist`);
4836
5642
  }
4837
5643
  await g_certificateAuthority.signCertificateRequest(
@@ -4839,8 +5645,8 @@ ${epilog}`
4839
5645
  csr_file,
4840
5646
  gLocalConfig
4841
5647
  );
4842
- assert11(typeof gLocalConfig.outputFile === "string");
4843
- fs11.writeFileSync(gLocalConfig.outputFile || "", fs11.readFileSync(certificate, "ascii"));
5648
+ assert10(typeof gLocalConfig.outputFile === "string");
5649
+ fs13.writeFileSync(gLocalConfig.outputFile || "", fs13.readFileSync(certificate, "ascii"));
4844
5650
  }
4845
5651
  await wrap(async () => await command_certificate(local_argv));
4846
5652
  return;
@@ -4859,13 +5665,13 @@ ${epilog}`
4859
5665
  await g_certificateAuthority.revokeCertificate(certificate, {});
4860
5666
  }
4861
5667
  await wrap(async () => {
4862
- const certificate = path7.resolve(local_argv.certificateFile);
5668
+ const certificate = path10.resolve(local_argv.certificateFile);
4863
5669
  warningLog(chalk7.yellow(" Certificate to revoke : "), chalk7.cyan(certificate));
4864
- if (!fs11.existsSync(certificate)) {
5670
+ if (!fs13.existsSync(certificate)) {
4865
5671
  throw new Error(`cannot find certificate to revoke ${certificate}`);
4866
5672
  }
4867
5673
  await readConfiguration(local_argv);
4868
- await construct_CertificateAuthority2("");
5674
+ await construct_CertificateAuthority("");
4869
5675
  await revoke_certificate(certificate);
4870
5676
  warningLog("done ... ");
4871
5677
  warningLog(" crl = ", g_certificateAuthority.revocationList);
@@ -4908,11 +5714,11 @@ ${epilog}`
4908
5714
  if (local_argv.help) return showHelp("csr", "create a certificate signing request", optionsDef);
4909
5715
  await wrap(async () => {
4910
5716
  await readConfiguration(local_argv);
4911
- if (!fs11.existsSync(gLocalConfig.PKIFolder || "")) {
5717
+ if (!fs13.existsSync(gLocalConfig.PKIFolder || "")) {
4912
5718
  warningLog("PKI folder must exist");
4913
5719
  }
4914
5720
  await construct_CertificateManager();
4915
- if (!gLocalConfig.outputFile || fs11.existsSync(gLocalConfig.outputFile)) {
5721
+ if (!gLocalConfig.outputFile || fs13.existsSync(gLocalConfig.outputFile)) {
4916
5722
  throw new Error(` File ${gLocalConfig.outputFile} already exist`);
4917
5723
  }
4918
5724
  gLocalConfig.privateKey = void 0;
@@ -4927,8 +5733,8 @@ ${epilog}`
4927
5733
  warningLog("please specify a output file");
4928
5734
  return;
4929
5735
  }
4930
- const csr = await fs11.promises.readFile(internal_csr_file, "utf-8");
4931
- fs11.writeFileSync(gLocalConfig.outputFile || "", csr, "utf-8");
5736
+ const csr = await fs13.promises.readFile(internal_csr_file, "utf-8");
5737
+ fs13.writeFileSync(gLocalConfig.outputFile || "", csr, "utf-8");
4932
5738
  warningLog("Subject = ", gLocalConfig.subject);
4933
5739
  warningLog("applicationUri = ", gLocalConfig.applicationUri);
4934
5740
  warningLog("altNames = ", gLocalConfig.altNames);
@@ -4956,16 +5762,16 @@ ${epilog}`
4956
5762
  return showHelp("sign", "validate a certificate signing request and generate a certificate", optionsDef);
4957
5763
  await wrap(async () => {
4958
5764
  await readConfiguration(local_argv);
4959
- if (!fs11.existsSync(gLocalConfig.CAFolder || "")) {
5765
+ if (!fs13.existsSync(gLocalConfig.CAFolder || "")) {
4960
5766
  throw new Error(`CA folder must exist:${gLocalConfig.CAFolder}`);
4961
5767
  }
4962
- await construct_CertificateAuthority2("");
4963
- const csr_file = path7.resolve(local_argv.csr || "");
4964
- if (!fs11.existsSync(csr_file)) {
5768
+ await construct_CertificateAuthority("");
5769
+ const csr_file = path10.resolve(local_argv.csr || "");
5770
+ if (!fs13.existsSync(csr_file)) {
4965
5771
  throw new Error(`Certificate signing request doesn't exist: ${csr_file}`);
4966
5772
  }
4967
- const certificate = path7.resolve(local_argv.output || csr_file.replace(".csr", ".pem"));
4968
- if (fs11.existsSync(certificate)) {
5773
+ const certificate = path10.resolve(local_argv.output || csr_file.replace(".csr", ".pem"));
5774
+ if (fs13.existsSync(certificate)) {
4969
5775
  throw new Error(` File ${certificate} already exist`);
4970
5776
  }
4971
5777
  await g_certificateAuthority.signCertificateRequest(
@@ -4973,8 +5779,8 @@ ${epilog}`
4973
5779
  csr_file,
4974
5780
  gLocalConfig
4975
5781
  );
4976
- assert11(typeof gLocalConfig.outputFile === "string");
4977
- fs11.writeFileSync(gLocalConfig.outputFile || "", fs11.readFileSync(certificate, "ascii"));
5782
+ assert10(typeof gLocalConfig.outputFile === "string");
5783
+ fs13.writeFileSync(gLocalConfig.outputFile || "", fs13.readFileSync(certificate, "ascii"));
4978
5784
  });
4979
5785
  return;
4980
5786
  }