node-opcua-pki 6.20.0 → 6.21.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
@@ -519,7 +519,31 @@ var init_certificate_manager = __esm({
519
519
  // even if the consumer forgets to call dispose().
520
520
  static #activeInstances = /* @__PURE__ */ new Set();
521
521
  static #cleanupInstalled = false;
522
- static #installProcessCleanup() {
522
+ static #exitHandler;
523
+ /**
524
+ * Install a best-effort `exit` hook that closes any watcher
525
+ * still open when the process terminates.
526
+ *
527
+ * **This library never terminates the host process.** No
528
+ * SIGINT/SIGTERM handler is installed: deciding how (and
529
+ * whether) to shut down on a signal is the application's
530
+ * responsibility, and a listener registered here would both
531
+ * pre-empt the application's own graceful shutdown and
532
+ * silently suppress Node's default signal behaviour.
533
+ *
534
+ * Nothing here is load-bearing for process exit. The native
535
+ * `fs.watch` handles are `unref()`'d when the watchers are
536
+ * created (see `#readCertificates`), so an undisposed
537
+ * CertificateManager never keeps the event loop alive. This
538
+ * hook is only tidiness on the way out.
539
+ *
540
+ * `exit` rather than `beforeExit`: `beforeExit` fires when
541
+ * the loop merely drains and the loop can subsequently be
542
+ * resurrected, which would leave a still-in-use instance
543
+ * marked Disposed. `exit` is terminal, synchronous-only and
544
+ * cannot alter the exit code.
545
+ */
546
+ static #installExitCleanup() {
523
547
  if (_CertificateManager.#cleanupInstalled) return;
524
548
  _CertificateManager.#cleanupInstalled = true;
525
549
  const closeDanglingWatchers = () => {
@@ -535,13 +559,22 @@ var init_certificate_manager = __esm({
535
559
  }
536
560
  _CertificateManager.#activeInstances.clear();
537
561
  };
538
- process.on("beforeExit", closeDanglingWatchers);
539
- for (const signal of ["SIGINT", "SIGTERM"]) {
540
- process.once(signal, () => {
541
- closeDanglingWatchers();
542
- process.exit();
543
- });
562
+ _CertificateManager.#exitHandler = closeDanglingWatchers;
563
+ process.on("exit", closeDanglingWatchers);
564
+ }
565
+ /**
566
+ * Remove the `exit` hook once the last instance is disposed,
567
+ * so a library that is initialized and disposed repeatedly
568
+ * does not accumulate process listeners. A later
569
+ * `initialize()` re-arms it.
570
+ */
571
+ static #uninstallExitCleanupIfIdle() {
572
+ if (_CertificateManager.#activeInstances.size > 0) return;
573
+ if (_CertificateManager.#exitHandler) {
574
+ process.removeListener("exit", _CertificateManager.#exitHandler);
575
+ _CertificateManager.#exitHandler = void 0;
544
576
  }
577
+ _CertificateManager.#cleanupInstalled = false;
545
578
  }
546
579
  /**
547
580
  * Dispose **all** active CertificateManager instances,
@@ -966,23 +999,19 @@ var init_certificate_manager = __esm({
966
999
  return "BadCertificateUntrusted" /* BadCertificateUntrusted */;
967
1000
  }
968
1001
  }
969
- const _c2 = chain[1] ? exploreCertificateInfo(chain[1]) : "non";
970
- debugLog("chain[1] info=", _c2);
971
- const certificateInfo = exploreCertificateInfo(chain[0]);
1002
+ const { validity } = exploreCertificate(chain[0]).tbsCertificate;
972
1003
  const now = /* @__PURE__ */ new Date();
973
1004
  let isTimeInvalid = false;
974
- if (certificateInfo.notBefore.getTime() > now.getTime()) {
1005
+ if (validity.notBefore.getTime() > now.getTime()) {
975
1006
  debugLog(
976
- `${chalk3.red("certificate is invalid : certificate is not active yet !")} not before date =${certificateInfo.notBefore}`
1007
+ `${chalk3.red("certificate is invalid : certificate is not active yet !")} not before date =${validity.notBefore}`
977
1008
  );
978
1009
  if (!options.acceptPendingCertificate) {
979
1010
  isTimeInvalid = true;
980
1011
  }
981
1012
  }
982
- if (certificateInfo.notAfter.getTime() <= now.getTime()) {
983
- debugLog(
984
- `${chalk3.red("certificate is invalid : certificate has expired !")} not after date =${certificateInfo.notAfter}`
985
- );
1013
+ if (validity.notAfter.getTime() <= now.getTime()) {
1014
+ debugLog(`${chalk3.red("certificate is invalid : certificate has expired !")} not after date =${validity.notAfter}`);
986
1015
  if (!options.acceptOutdatedCertificate) {
987
1016
  isTimeInvalid = true;
988
1017
  }
@@ -1021,7 +1050,7 @@ var init_certificate_manager = __esm({
1021
1050
  const chain = coerceCertificateChain(certificate);
1022
1051
  for (const element of chain) {
1023
1052
  try {
1024
- exploreCertificateInfo(element);
1053
+ exploreCertificate(element);
1025
1054
  } catch (_err) {
1026
1055
  return "BadCertificateInvalid" /* BadCertificateInvalid */;
1027
1056
  }
@@ -1074,7 +1103,7 @@ var init_certificate_manager = __esm({
1074
1103
  this.#initializingPromise = void 0;
1075
1104
  this.state = 2 /* Initialized */;
1076
1105
  _CertificateManager.#activeInstances.add(this);
1077
- _CertificateManager.#installProcessCleanup();
1106
+ _CertificateManager.#installExitCleanup();
1078
1107
  }
1079
1108
  async #initialize() {
1080
1109
  const pkiDir = this.#location;
@@ -1161,6 +1190,7 @@ var init_certificate_manager = __esm({
1161
1190
  this.#cachedPrivateKey = void 0;
1162
1191
  this.#privateKeyPromise = void 0;
1163
1192
  _CertificateManager.#activeInstances.delete(this);
1193
+ _CertificateManager.#uninstallExitCleanupIfIdle();
1164
1194
  }
1165
1195
  }
1166
1196
  /**
@@ -1509,17 +1539,12 @@ var init_certificate_manager = __esm({
1509
1539
  return "BadSecurityChecksFailed" /* BadSecurityChecksFailed */;
1510
1540
  }
1511
1541
  if (!opts.acceptExpiredCertificate) {
1512
- let certDetails;
1513
- try {
1514
- certDetails = exploreCertificateInfo(currentCert);
1515
- } catch (_err) {
1516
- return "BadCertificateInvalid" /* BadCertificateInvalid */;
1517
- }
1542
+ const { validity } = currentInfo.tbsCertificate;
1518
1543
  const now = /* @__PURE__ */ new Date();
1519
- if (certDetails.notBefore.getTime() > now.getTime()) {
1544
+ if (validity.notBefore.getTime() > now.getTime()) {
1520
1545
  return "BadCertificateTimeInvalid" /* BadCertificateTimeInvalid */;
1521
1546
  }
1522
- if (certDetails.notAfter.getTime() <= now.getTime()) {
1547
+ if (validity.notAfter.getTime() <= now.getTime()) {
1523
1548
  return depth === 1 ? "BadCertificateTimeInvalid" /* BadCertificateTimeInvalid */ : "BadCertificateIssuerTimeInvalid" /* BadCertificateIssuerTimeInvalid */;
1524
1549
  }
1525
1550
  }
@@ -1869,6 +1894,7 @@ var init_certificate_manager = __esm({
1869
1894
  const chokidarOptions = {
1870
1895
  usePolling,
1871
1896
  ...usePolling ? { interval: pollingInterval } : {},
1897
+ depth: 0,
1872
1898
  persistent: false
1873
1899
  };
1874
1900
  const allCapturedHandles = [];
@@ -2145,21 +2171,17 @@ function getEnvironmentVarNames() {
2145
2171
  return { key: varName, pattern: `\\$ENV\\:\\:${varName}` };
2146
2172
  });
2147
2173
  }
2174
+ function buildSubjectAltNameString(params) {
2175
+ return [
2176
+ `URI:${params.applicationUri}`,
2177
+ ...(params.dns ?? []).map((d) => `DNS:${d}`),
2178
+ ...(params.ip ?? []).map((d) => `IP:${d}`)
2179
+ ].join(", ");
2180
+ }
2148
2181
  function processAltNames(params) {
2149
2182
  params.dns = params.dns || [];
2150
2183
  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);
2184
+ setEnv("ALTNAME", buildSubjectAltNameString(params));
2163
2185
  }
2164
2186
  var SAFE_ENV_PASSTHROUGH, exportedEnvVars;
2165
2187
  var init_env = __esm({
@@ -2511,9 +2533,6 @@ import chalk5 from "chalk";
2511
2533
  function passinArg(passphrase = "") {
2512
2534
  return { args: ["-passin", `env:${PASSIN_ENV_VAR}`], env: { [PASSIN_ENV_VAR]: passphrase } };
2513
2535
  }
2514
- function passoutArg(passphrase = "") {
2515
- return { args: ["-passout", `env:${PASSOUT_ENV_VAR}`], env: { [PASSOUT_ENV_VAR]: passphrase } };
2516
- }
2517
2536
  function renderForDisplay(file, args) {
2518
2537
  return [file, ...args].map((a) => a === "" || /[\s"'`$\\]/.test(a) ? JSON.stringify(a) : a).join(" ");
2519
2538
  }
@@ -2534,7 +2553,7 @@ async function execute2(file, args, options) {
2534
2553
  stdio: ["ignore", "pipe", "pipe"]
2535
2554
  });
2536
2555
  const fail = (message) => {
2537
- if (!options.hideErrorMessage) {
2556
+ if (!options.hideErrorMessage && !g_config.silent) {
2538
2557
  const fence = "###########################################";
2539
2558
  console.error(chalk5.bgWhiteBright.redBright(`${fence} OPENSSL ERROR ${fence}`));
2540
2559
  console.error(chalk5.bgWhiteBright.redBright(`CWD = ${options.cwd}`));
@@ -2628,7 +2647,7 @@ async function execute_openssl(args, options) {
2628
2647
  await ensure_openssl_installed();
2629
2648
  return await execute2(opensslPath, args, options);
2630
2649
  }
2631
- var opensslPath, n, PASSIN_ENV_VAR, PASSOUT_ENV_VAR;
2650
+ var opensslPath, n, PASSIN_ENV_VAR;
2632
2651
  var init_execute_openssl = __esm({
2633
2652
  "packages/node-opcua-pki/lib/toolbox/with_openssl/execute_openssl.ts"() {
2634
2653
  "use strict";
@@ -2640,7 +2659,6 @@ var init_execute_openssl = __esm({
2640
2659
  init_install_prerequisite();
2641
2660
  n = makePath;
2642
2661
  PASSIN_ENV_VAR = "NODE_OPCUA_PKI_OPENSSL_PASSIN";
2643
- PASSOUT_ENV_VAR = "NODE_OPCUA_PKI_OPENSSL_PASSOUT";
2644
2662
  }
2645
2663
  });
2646
2664
 
@@ -2656,19 +2674,27 @@ function openssl_require2DigitYearInDate() {
2656
2674
  }
2657
2675
  return g_config.opensslVersion.match(/OpenSSL 0\.9/);
2658
2676
  }
2659
- function stripConditionalBlocks(template) {
2677
+ function stripConditionalBlocks(template, envOverrides) {
2660
2678
  return template.replace(/\{\{#([A-Z_][A-Z0-9_]*)\}\}([\s\S]*?)\{\{\/\1\}\}\r?\n?/g, (_match, key, content) => {
2661
- const keep = hasEnv(key) && getEnv(key) !== "";
2679
+ const keep = envOverrides && Object.prototype.hasOwnProperty.call(envOverrides, key) ? envOverrides[key] !== "" : hasEnv(key) && getEnv(key) !== "";
2662
2680
  return keep ? content : "";
2663
2681
  });
2664
2682
  }
2665
- function generateStaticConfig(configPath, options) {
2683
+ function generateStaticConfig(configPath, options, envOverrides) {
2666
2684
  const prePath = options?.cwd || "";
2667
2685
  const originalFilename = !path4.isAbsolute(configPath) ? path4.join(prePath, configPath) : configPath;
2668
2686
  let staticConfig = fs7.readFileSync(originalFilename, { encoding: "utf8" });
2669
- staticConfig = stripConditionalBlocks(staticConfig);
2687
+ staticConfig = stripConditionalBlocks(staticConfig, envOverrides);
2670
2688
  for (const envVar of getEnvironmentVarNames()) {
2671
- staticConfig = staticConfig.replace(new RegExp(envVar.pattern, "gi"), getEnv(envVar.key));
2689
+ if (envOverrides && Object.prototype.hasOwnProperty.call(envOverrides, envVar.key)) {
2690
+ continue;
2691
+ }
2692
+ staticConfig = staticConfig.replace(new RegExp(envVar.pattern, "gi"), () => getEnv(envVar.key));
2693
+ }
2694
+ if (envOverrides) {
2695
+ for (const [key, value] of Object.entries(envOverrides)) {
2696
+ staticConfig = staticConfig.replace(new RegExp(`\\$ENV\\:\\:${key}`, "gi"), () => value);
2697
+ }
2672
2698
  }
2673
2699
  const staticConfigPath = `${configPath}.${process.pid}-${_counter++}.tmp`;
2674
2700
  const temporaryConfigPath = !path4.isAbsolute(configPath) ? path4.join(prePath, staticConfigPath) : staticConfigPath;
@@ -2679,6 +2705,9 @@ function generateStaticConfig(configPath, options) {
2679
2705
  return temporaryConfigPath;
2680
2706
  }
2681
2707
  }
2708
+ async function cleanupStaticConfig(configFile, options) {
2709
+ await fs7.promises.rm(path4.resolve(options?.cwd ?? "", configFile), { force: true });
2710
+ }
2682
2711
  async function getPublicKeyFromPrivateKey(privateKeyFilename, publicKeyFilename, passphrase) {
2683
2712
  assert7(fs7.existsSync(privateKeyFilename));
2684
2713
  const passin = passinArg(passphrase);
@@ -2747,26 +2776,30 @@ async function createCertificateSigningRequestWithOpenSSL(certificateSigningRequ
2747
2776
  assert8(typeof certificateSigningRequestFilename === "string");
2748
2777
  processAltNames(params);
2749
2778
  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
- );
2779
+ try {
2780
+ const options = { cwd: params.rootDir, openssl_conf: path5.relative(params.rootDir, configFile) };
2781
+ const subject = params.subject ? new Subject3(params.subject).toString() : void 0;
2782
+ displaySubtitle("- Creating a Certificate Signing Request with openssl");
2783
+ await execute_openssl(
2784
+ [
2785
+ "req",
2786
+ "-new",
2787
+ "-sha256",
2788
+ "-batch",
2789
+ "-text",
2790
+ "-config",
2791
+ n3(configFile),
2792
+ "-key",
2793
+ n3(params.privateKey),
2794
+ ...subject ? ["-subj", subject] : [],
2795
+ "-out",
2796
+ n3(certificateSigningRequestFilename)
2797
+ ],
2798
+ options
2799
+ );
2800
+ } finally {
2801
+ await cleanupStaticConfig(configFile, { cwd: params.rootDir });
2802
+ }
2770
2803
  }
2771
2804
  var n3;
2772
2805
  var init_create_certificate_signing_request2 = __esm({
@@ -2797,363 +2830,10 @@ var init_with_openssl = __esm({
2797
2830
  }
2798
2831
  });
2799
2832
 
2800
- // packages/node-opcua-pki/lib/pki/toolbox_pfx.ts
2801
- import assert9 from "assert";
2833
+ // packages/node-opcua-pki/lib/ca/core/ca_database.ts
2802
2834
  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
- }
2813
- }
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 } });
2818
- }
2819
- var n4;
2820
- var init_toolbox_pfx = __esm({
2821
- "packages/node-opcua-pki/lib/pki/toolbox_pfx.ts"() {
2822
- "use strict";
2823
- init_esm_shims();
2824
- init_common2();
2825
- init_execute_openssl();
2826
- n4 = makePath;
2827
- }
2828
- });
2829
-
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"() {
2834
- "use strict";
2835
- 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
2849
-
2850
- # default_md = sha1
2851
-
2852
- default_md = sha256 # The default digest algorithm
2853
-
2854
- preserve = no
2855
- policy = policy_match
2856
- # randfile = $dir/random.rnd
2857
- # default_startdate = YYMMDDHHMMSSZ
2858
- # default_enddate = YYMMDDHHMMSSZ
2859
- crl_dir = $dir/crl
2860
- crl_extensions = crl_ext
2861
- crl = $dir/revocation_list.crl # the Revocation list
2862
- crlnumber = $dir/crlnumber # CRL number file
2863
- default_crl_days = 30
2864
- default_crl_hours = 24
2865
- #msie_hack
2866
-
2867
- [ policy_match ]
2868
- countryName = optional
2869
- stateOrProvinceName = optional
2870
- localityName = optional
2871
- organizationName = optional
2872
- organizationalUnitName = optional
2873
- commonName = optional
2874
- emailAddress = optional
2875
-
2876
- [ req ]
2877
- default_bits = 4096 # Size of keys
2878
- default_keyfile = key.pem # name of generated keys
2879
- distinguished_name = req_distinguished_name
2880
- attributes = req_attributes
2881
- x509_extensions = v3_ca
2882
- #input_password
2883
- #output_password
2884
- string_mask = nombstr # permitted characters
2885
- req_extensions = v3_req
2886
-
2887
- [ req_distinguished_name ]
2888
-
2889
- #0 countryName = Country Name (2 letter code)
2890
- # countryName_default = FR
2891
- # countryName_min = 2
2892
- # countryName_max = 2
2893
- # stateOrProvinceName = State or Province Name (full name)
2894
- # stateOrProvinceName_default = Ile de France
2895
- # localityName = Locality Name (city, district)
2896
- # localityName_default = Paris
2897
- organizationName = Organization Name (company)
2898
- organizationName_default = NodeOPCUA
2899
- # organizationalUnitName = Organizational Unit Name (department, division)
2900
- # organizationalUnitName_default = R&D
2901
- commonName = Common Name (hostname, FQDN, IP, or your name)
2902
- commonName_max = 256
2903
- commonName_default = NodeOPCUA
2904
- # emailAddress = Email Address
2905
- # emailAddress_max = 40
2906
- # emailAddress_default = node-opcua (at) node-opcua (dot) com
2907
-
2908
- [ req_attributes ]
2909
- #challengePassword = A challenge password
2910
- #challengePassword_min = 4
2911
- #challengePassword_max = 20
2912
- #unstructuredName = An optional company name
2913
- [ usr_cert ]
2914
- basicConstraints = critical, CA:FALSE
2915
- subjectKeyIdentifier = hash
2916
- authorityKeyIdentifier = keyid,issuer:always
2917
- #authorityKeyIdentifier = keyid
2918
- subjectAltName = $ENV::ALTNAME
2919
- # issuerAltName = issuer:copy
2920
- nsComment = ''OpenSSL Generated Certificate''
2921
- #nsCertType = client, email, objsign for ''everything including object signing''
2922
- #nsCaRevocationUrl = http://www.domain.dom/ca-crl.pem
2923
- #nsBaseUrl =
2924
- #nsRenewalUrl =
2925
- #nsCaPolicyUrl =
2926
- #nsSslServerName =
2927
- keyUsage = critical, digitalSignature, nonRepudiation, keyEncipherment, dataEncipherment, keyAgreement
2928
- extendedKeyUsage = critical,serverAuth ,clientAuth
2929
- {{#CDP_URL}}crlDistributionPoints = URI:$ENV::CDP_URL
2930
- {{/CDP_URL}}{{#AIA_VALUE}}authorityInfoAccess = $ENV::AIA_VALUE
2931
- {{/AIA_VALUE}}
2932
- [ v3_req ]
2933
- basicConstraints = critical, CA:FALSE
2934
- keyUsage = nonRepudiation, digitalSignature, keyEncipherment, dataEncipherment, keyAgreement
2935
- extendedKeyUsage = critical,serverAuth ,clientAuth
2936
- subjectAltName = $ENV::ALTNAME
2937
- nsComment = "CA Generated by Node-OPCUA Certificate utility using openssl"
2938
- [ v3_ca_req ]
2939
- subjectKeyIdentifier = hash
2940
- basicConstraints = CA:TRUE
2941
- keyUsage = critical, cRLSign, keyCertSign
2942
- subjectAltName = $ENV::ALTNAME
2943
- nsComment = "CA CSR generated by Node-OPCUA Certificate utility using openssl"
2944
- [ v3_ca ]
2945
- subjectKeyIdentifier = hash
2946
- authorityKeyIdentifier = keyid:always,issuer:always
2947
- basicConstraints = CA:TRUE
2948
- keyUsage = critical, cRLSign, keyCertSign
2949
- subjectAltName = $ENV::ALTNAME
2950
- nsComment = "CA Certificate generated by Node-OPCUA Certificate utility using openssl"
2951
- #nsCertType = sslCA, emailCA
2952
- #issuerAltName = issuer:copy
2953
- #obj = DER:02:03
2954
- {{#CDP_URL}}crlDistributionPoints = URI:$ENV::CDP_URL
2955
- {{/CDP_URL}}{{#AIA_VALUE}}authorityInfoAccess = $ENV::AIA_VALUE
2956
- {{/AIA_VALUE}}[ v3_selfsigned]
2957
- basicConstraints = critical, CA:FALSE
2958
- keyUsage = nonRepudiation, digitalSignature, keyEncipherment, dataEncipherment, keyAgreement
2959
- extendedKeyUsage = critical,serverAuth ,clientAuth
2960
- nsComment = "Self-signed certificate, generated by NodeOPCUA"
2961
- subjectAltName = $ENV::ALTNAME
2962
-
2963
- [ crl_ext ]
2964
- #issuerAltName = issuer:copy
2965
- authorityKeyIdentifier = keyid:always,issuer:always
2966
- #authorityInfoAccess = @issuer_info`;
2967
- ca_config_template_cnf_default = config2;
2968
- }
2969
- });
2970
-
2971
- // packages/node-opcua-pki/lib/ca/certificate_authority.ts
2972
- import assert10 from "assert";
2973
- import fs10 from "fs";
2974
- import os4 from "os";
2975
2835
  import path6 from "path";
2976
- import chalk6 from "chalk";
2977
- import {
2978
- CertificatePurpose as CertificatePurpose2,
2979
- certificateMatchesPrivateKey,
2980
- convertPEMtoDER,
2981
- exploreCertificate as exploreCertificate2,
2982
- exploreCertificateSigningRequest,
2983
- generatePrivateKeyFile as generatePrivateKeyFile2,
2984
- readCertificatePEM,
2985
- readCertificateSigningRequest,
2986
- readPrivateKey as readPrivateKey2,
2987
- Subject as Subject4,
2988
- toPem as toPem2,
2989
- writePrivateKeyFile as writePrivateKeyFile2
2990
- } from "node-opcua-crypto";
2991
- function escapeOpensslConfDoubleQuoted(value) {
2992
- return value.replace(/\\/g, "\\\\").replace(/"/g, '\\"');
2993
- }
2994
- function renderCaConfig(caRootDir) {
2995
- return configurationFileTemplate.replace(/%%ROOT_FOLDER%%/, escapeOpensslConfDoubleQuoted(makePath(caRootDir)));
2996
- }
2997
- function octetStringToIpAddress(a) {
2998
- 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
- }
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
- }
2836
+ import { convertPEMtoDER, readCertificatePEM } from "node-opcua-crypto";
3157
2837
  function parseOpenSSLDate(dateStr) {
3158
2838
  const raw = dateStr?.split(",")[0] ?? "";
3159
2839
  if (raw.length < 12) return "";
@@ -3166,24 +2846,1032 @@ function parseOpenSSLDate(dateStr) {
3166
2846
  const sec = raw.substring(10, 12);
3167
2847
  return `${year}-${month}-${day}T${hour}:${min}:${sec}Z`;
3168
2848
  }
3169
- function validateRevocationUrl(url, fieldName) {
3170
- if (url === void 0) {
3171
- return void 0;
3172
- }
3173
- if (url === "") {
3174
- throw new Error(`${fieldName} must not be empty \u2014 pass undefined to disable the extension`);
3175
- }
3176
- let parsed;
3177
- try {
3178
- parsed = new URL(url);
3179
- } catch {
3180
- throw new Error(`${fieldName} is not a valid URL: ${url}`);
3181
- }
3182
- if (parsed.protocol !== "http:" && parsed.protocol !== "https:") {
3183
- throw new Error(`${fieldName} must use http: or https: (got ${parsed.protocol} in ${url})`);
2849
+ function formatOpenSSLDate(date) {
2850
+ const yy = String(date.getUTCFullYear() % 100).padStart(2, "0");
2851
+ const mm = String(date.getUTCMonth() + 1).padStart(2, "0");
2852
+ const dd = String(date.getUTCDate()).padStart(2, "0");
2853
+ const hh = String(date.getUTCHours()).padStart(2, "0");
2854
+ const mi = String(date.getUTCMinutes()).padStart(2, "0");
2855
+ const ss = String(date.getUTCSeconds()).padStart(2, "0");
2856
+ return `${yy}${mm}${dd}${hh}${mi}${ss}Z`;
2857
+ }
2858
+ function parseRevocationReason(dateStr) {
2859
+ const parts = dateStr?.split(",");
2860
+ return parts && parts.length > 1 ? parts[1] : void 0;
2861
+ }
2862
+ function evenLengthHex(value) {
2863
+ const hex = value.toString(16).toUpperCase();
2864
+ return hex.length % 2 === 0 ? hex : `0${hex}`;
2865
+ }
2866
+ function escapeIndexField(value) {
2867
+ return value.replace(/[\x00-\x1f\x7f\\]/g, (c) => `\\x${c.charCodeAt(0).toString(16).toUpperCase().padStart(2, "0")}`);
2868
+ }
2869
+ function assertHexSerial(serial) {
2870
+ if (!/^[0-9A-Fa-f]+$/.test(serial)) {
2871
+ throw new Error(`Invalid certificate serial number: ${JSON.stringify(serial)}`);
3184
2872
  }
3185
- if (!parsed.pathname || parsed.pathname === "/") {
3186
- throw new Error(`${fieldName} must include a path component (got ${url})`);
2873
+ return serial.toUpperCase();
2874
+ }
2875
+ var CaDatabase;
2876
+ var init_ca_database = __esm({
2877
+ "packages/node-opcua-pki/lib/ca/core/ca_database.ts"() {
2878
+ "use strict";
2879
+ init_esm_shims();
2880
+ CaDatabase = class {
2881
+ #rootDir;
2882
+ constructor(rootDir) {
2883
+ this.#rootDir = rootDir;
2884
+ }
2885
+ /** Path to the OpenSSL certificate database file (`index.txt`). */
2886
+ get indexFile() {
2887
+ return path6.join(this.#rootDir, "index.txt");
2888
+ }
2889
+ /**
2890
+ * Parse the OpenSSL `index.txt` certificate database.
2891
+ *
2892
+ * Each line has tab-separated fields:
2893
+ * ```
2894
+ * status expiry [revocationDate] serial unknown subject
2895
+ * ```
2896
+ *
2897
+ * - status: `V` (valid), `R` (revoked), `E` (expired)
2898
+ * - expiry: `YYMMDDHHmmssZ`
2899
+ * - revocationDate: present only for revoked certs
2900
+ * - serial: hex string
2901
+ * - unknown: always `"unknown"`
2902
+ * - subject: X.500 slash-delimited string
2903
+ */
2904
+ readIndex() {
2905
+ const indexPath = this.indexFile;
2906
+ if (!fs9.existsSync(indexPath)) {
2907
+ return [];
2908
+ }
2909
+ const content = fs9.readFileSync(indexPath, "utf-8");
2910
+ const lines = content.split("\n").filter((l) => l.trim().length > 0);
2911
+ const records = [];
2912
+ for (const line of lines) {
2913
+ const fields = line.split(" ");
2914
+ if (fields.length < 4) continue;
2915
+ const statusChar = fields[0];
2916
+ const expiryStr = fields[1];
2917
+ let serial;
2918
+ let subject;
2919
+ let revocationDate;
2920
+ if (statusChar === "R") {
2921
+ revocationDate = fields[2];
2922
+ serial = fields[3];
2923
+ subject = fields.length >= 6 ? fields[5] : "";
2924
+ } else {
2925
+ serial = fields[3];
2926
+ subject = fields.length >= 6 ? fields[5] : "";
2927
+ }
2928
+ let status;
2929
+ switch (statusChar) {
2930
+ case "V":
2931
+ status = "valid";
2932
+ break;
2933
+ case "R":
2934
+ status = "revoked";
2935
+ break;
2936
+ case "E":
2937
+ status = "expired";
2938
+ break;
2939
+ default:
2940
+ continue;
2941
+ }
2942
+ records.push({
2943
+ serial,
2944
+ status,
2945
+ subject,
2946
+ expiryDate: parseOpenSSLDate(expiryStr),
2947
+ revocationDate: revocationDate ? parseOpenSSLDate(revocationDate) : void 0,
2948
+ reason: revocationDate ? parseRevocationReason(revocationDate) : void 0
2949
+ });
2950
+ }
2951
+ return records;
2952
+ }
2953
+ /** Look up one record by serial number (case-insensitive). */
2954
+ findBySerial(serial) {
2955
+ const upper = serial.toUpperCase();
2956
+ return this.readIndex().find((r) => r.serial.toUpperCase() === upper);
2957
+ }
2958
+ /**
2959
+ * Read a specific issued certificate by serial number.
2960
+ *
2961
+ * OpenSSL stores signed certificates in the `certs/` directory using
2962
+ * the naming convention `<SERIAL>.pem`.
2963
+ *
2964
+ * @param serial - hex-encoded serial number (e.g. `"1000"`)
2965
+ * @returns the DER buffer, or `undefined` if not found
2966
+ */
2967
+ getCertificateBySerial(serial) {
2968
+ const upper = serial.toUpperCase();
2969
+ const certFile = path6.join(this.#rootDir, "certs", `${upper}.pem`);
2970
+ if (!fs9.existsSync(certFile)) {
2971
+ return void 0;
2972
+ }
2973
+ const pem = readCertificatePEM(certFile);
2974
+ return convertPEMtoDER(pem);
2975
+ }
2976
+ /**
2977
+ * Read-increment-write a hex counter file (`serial`/`crlnumber`),
2978
+ * returning the value to hand out — matching `openssl ca`'s own
2979
+ * behavior: the file holds the *next* value to assign, and is bumped
2980
+ * to the following one immediately after being read. A `.old` backup
2981
+ * of the pre-bump content is kept, as `openssl` itself does.
2982
+ */
2983
+ #nextHexCounter(fileName) {
2984
+ const counterFile = path6.join(this.#rootDir, fileName);
2985
+ const current = BigInt(`0x${fs9.readFileSync(counterFile, "utf-8").trim()}`);
2986
+ fs9.copyFileSync(counterFile, `${counterFile}.old`);
2987
+ fs9.writeFileSync(counterFile, evenLengthHex(current + 1n));
2988
+ return evenLengthHex(current);
2989
+ }
2990
+ /** Hand out the next certificate serial number, bumping the `serial` file. */
2991
+ nextSerial() {
2992
+ return this.#nextHexCounter("serial");
2993
+ }
2994
+ /** Hand out the next CRL number, bumping the `crlnumber` file. */
2995
+ nextCrlNumber() {
2996
+ return this.#nextHexCounter("crlnumber");
2997
+ }
2998
+ /** Append a newly-issued certificate's `V` (valid) row to `index.txt`. */
2999
+ appendIssued(record) {
3000
+ const serial = assertHexSerial(record.serial);
3001
+ const subject = escapeIndexField(record.subject);
3002
+ const line = `V ${formatOpenSSLDate(record.expiryDate)} ${serial} unknown ${subject}
3003
+ `;
3004
+ fs9.appendFileSync(this.indexFile, line);
3005
+ }
3006
+ /**
3007
+ * Rewrite a certificate's `index.txt` row from `V` to `R` (revoked).
3008
+ * Throws if the serial is not found, or is already revoked — the same
3009
+ * "already revoked" case `openssl ca -revoke` itself rejects.
3010
+ */
3011
+ markRevoked(serial, revocationDate, reason) {
3012
+ const upper = assertHexSerial(serial);
3013
+ if (!/^[A-Za-z]+$/.test(reason)) {
3014
+ throw new Error(`Invalid CRL reason: ${JSON.stringify(reason)}`);
3015
+ }
3016
+ const lines = fs9.readFileSync(this.indexFile, "utf-8").split("\n").filter((l) => l.trim().length > 0);
3017
+ let found = false;
3018
+ const rewritten = lines.map((line) => {
3019
+ const fields = line.split(" ");
3020
+ if (fields.length < 4 || fields[3]?.toUpperCase() !== upper) {
3021
+ return line;
3022
+ }
3023
+ if (fields[0] === "R") {
3024
+ throw new Error(`Certificate ${upper} is already revoked`);
3025
+ }
3026
+ found = true;
3027
+ const subject = fields.length >= 6 ? fields[5] : "";
3028
+ return `R ${fields[1]} ${formatOpenSSLDate(revocationDate)},${reason} ${upper} unknown ${subject}`;
3029
+ });
3030
+ if (!found) {
3031
+ throw new Error(`Certificate ${upper} not found in the certificate database`);
3032
+ }
3033
+ fs9.writeFileSync(this.indexFile, `${rewritten.join("\n")}
3034
+ `);
3035
+ }
3036
+ /** Store a signed certificate's PEM under `certs/<SERIAL>.pem`, as `openssl ca` does. */
3037
+ storeCertificate(serial, pem) {
3038
+ const certFile = path6.join(this.#rootDir, "certs", `${assertHexSerial(serial)}.pem`);
3039
+ fs9.writeFileSync(certFile, pem);
3040
+ }
3041
+ };
3042
+ }
3043
+ });
3044
+
3045
+ // packages/node-opcua-pki/lib/ca/backends/native_ca_backend.ts
3046
+ import fs10 from "fs";
3047
+ import path7 from "path";
3048
+ import {
3049
+ CertificatePurpose as CertificatePurpose2,
3050
+ createCertificateFromCsr,
3051
+ createCertificateSigningRequest as createCertificateSigningRequest2,
3052
+ createCrl,
3053
+ isCaSigner,
3054
+ privateKeyToCryptoKey,
3055
+ readPrivateKey as readPrivateKey2,
3056
+ Subject as Subject4,
3057
+ x509
3058
+ } from "node-opcua-crypto";
3059
+ function signingAlgorithmOf(key) {
3060
+ return isCaSigner(key) ? key.algorithm : RSA_SHA256;
3061
+ }
3062
+ function daysFromNow(notBefore, days) {
3063
+ return new Date(notBefore.getTime() + days * 24 * 60 * 60 * 1e3);
3064
+ }
3065
+ function caSanUri(ca) {
3066
+ return `urn:${ca.subject.commonName || "NodeOPCUA-CA"}`;
3067
+ }
3068
+ function sanFromCsr(csrPem) {
3069
+ const request = new x509.Pkcs10CertificateRequest(csrPem);
3070
+ const extension = request.getExtension("2.5.29.17");
3071
+ const dns2 = [];
3072
+ const ip = [];
3073
+ let applicationUri;
3074
+ for (const name of extension?.names.toJSON() ?? []) {
3075
+ if (name.type === "dns") {
3076
+ dns2.push(name.value);
3077
+ } else if (name.type === "ip") {
3078
+ ip.push(name.value);
3079
+ } else if (name.type === "url" && applicationUri === void 0) {
3080
+ applicationUri = name.value;
3081
+ }
3082
+ }
3083
+ return { dns: dns2, ip, applicationUri };
3084
+ }
3085
+ function toOpenSslSubjectString(subjectName) {
3086
+ const parts = [];
3087
+ for (const rdn of subjectName.toJSON()) {
3088
+ for (const [type, values] of Object.entries(rdn)) {
3089
+ for (const value of values) {
3090
+ parts.push(`${type}=${value}`);
3091
+ }
3092
+ }
3093
+ }
3094
+ return `/${parts.join("/")}`;
3095
+ }
3096
+ var CA_CERT_VALIDITY_DAYS, RSA_SHA256, REASON_TO_X509_CRL_REASON, NativeCaBackend;
3097
+ var init_native_ca_backend = __esm({
3098
+ "packages/node-opcua-pki/lib/ca/backends/native_ca_backend.ts"() {
3099
+ "use strict";
3100
+ init_esm_shims();
3101
+ init_toolbox();
3102
+ init_ca_database();
3103
+ CA_CERT_VALIDITY_DAYS = 3650;
3104
+ RSA_SHA256 = { name: "RSASSA-PKCS1-v1_5", hash: { name: "SHA-256" } };
3105
+ REASON_TO_X509_CRL_REASON = {
3106
+ unspecified: x509.X509CrlReason.unspecified,
3107
+ keyCompromise: x509.X509CrlReason.keyCompromise,
3108
+ CACompromise: x509.X509CrlReason.cACompromise,
3109
+ affiliationChanged: x509.X509CrlReason.affiliationChanged,
3110
+ superseded: x509.X509CrlReason.superseded,
3111
+ cessationOfOperation: x509.X509CrlReason.cessationOfOperation,
3112
+ certificateHold: x509.X509CrlReason.certificateHold,
3113
+ removeFromCRL: x509.X509CrlReason.removeFromCRL
3114
+ };
3115
+ NativeCaBackend = class {
3116
+ /** Nothing to check: this backend spawns no process and needs no tool on PATH. */
3117
+ /** Signing goes through `ca._getSigningKey()`, which may be an external signer. */
3118
+ supportsExternalSigner = true;
3119
+ async preflight() {
3120
+ }
3121
+ /**
3122
+ * Write a CSR for the CA's own key, the `[v3_ca_req]` equivalent. The
3123
+ * key comes from the CA rather than from `privateKeyFile`: on a
3124
+ * signer-backed CA that file does not exist.
3125
+ */
3126
+ async generateCaCsr(ca, _caRootDir, _privateKeyFile, csrFile) {
3127
+ displayTitle("Generate a certificate request for the CA key");
3128
+ const signingKey = await ca._getSigningKey();
3129
+ const { csr } = await createCertificateSigningRequest2({
3130
+ privateKey: signingKey,
3131
+ subject: ca.subject.toString(),
3132
+ applicationUri: caSanUri(ca),
3133
+ purpose: CertificatePurpose2.ForCertificateAuthority
3134
+ });
3135
+ await fs10.promises.writeFile(csrFile, csr);
3136
+ }
3137
+ async bootstrap(ca) {
3138
+ const caRootDir = path7.resolve(ca.rootDir);
3139
+ const csrFile = path7.join(caRootDir, "private/cakey.csr");
3140
+ await this.generateCaCsr(ca, caRootDir, path7.join(caRootDir, "private/cakey.pem"), csrFile);
3141
+ const issuerCA = ca._issuerCA;
3142
+ if (issuerCA) {
3143
+ displayTitle("Generate CA Certificate (signed by issuer CA)");
3144
+ const signWithIssuer = () => this.signSubordinateCsr(issuerCA, csrFile, ca.caCertificate, CA_CERT_VALIDITY_DAYS);
3145
+ if (path7.resolve(issuerCA.rootDir) === caRootDir) {
3146
+ await signWithIssuer();
3147
+ } else {
3148
+ await issuerCA._withCaDirectoryLock(signWithIssuer);
3149
+ }
3150
+ } else {
3151
+ displayTitle("Generate CA Certificate (self-signed)");
3152
+ const csrPem = await fs10.promises.readFile(csrFile, "utf-8");
3153
+ const signingKey = await ca._getSigningKey();
3154
+ const request = new x509.Pkcs10CertificateRequest(csrPem);
3155
+ const notBefore = /* @__PURE__ */ new Date();
3156
+ const { cert } = await createCertificateFromCsr({
3157
+ csr: csrPem,
3158
+ // the CSR's own parsed subject, so issuer and subject are
3159
+ // byte-identical on a self-signed root: re-encoding a string
3160
+ // form could differ and break chain building
3161
+ issuerName: request.subjectName,
3162
+ issuerPublicKey: request.publicKey,
3163
+ signingKey,
3164
+ signingAlgorithm: signingAlgorithmOf(signingKey),
3165
+ notBefore,
3166
+ notAfter: daysFromNow(notBefore, CA_CERT_VALIDITY_DAYS),
3167
+ purpose: CertificatePurpose2.ForCertificateAuthority,
3168
+ applicationUri: caSanUri(ca),
3169
+ revocation: {
3170
+ crlDistributionUrl: ca.crlDistributionUrl,
3171
+ ocspResponderUrl: ca.ocspResponderUrl,
3172
+ caIssuersUrl: ca.caIssuersUrl
3173
+ }
3174
+ });
3175
+ await fs10.promises.writeFile(ca.caCertificate, cert);
3176
+ }
3177
+ displaySubtitle("generate initial CRL (Certificate Revocation List)");
3178
+ await this.#regenerateCrlLocked(ca, new CaDatabase(ca.rootDir));
3179
+ displayTitle("Create Certificate Authority (CA) ---> DONE");
3180
+ }
3181
+ /** Sign a subordinate CA's request with this CA's key, the `[v3_ca]` equivalent. */
3182
+ async signSubordinateCsr(ca, csrFile, certFile, validityDays) {
3183
+ const { signingKey, signingAlgorithm, issuerPublicKey, issuerName } = await this.#issuerContext(ca);
3184
+ const csrPem = await fs10.promises.readFile(csrFile, "utf-8");
3185
+ const db = new CaDatabase(ca.rootDir);
3186
+ const serialNumber = db.nextSerial();
3187
+ const notBefore = /* @__PURE__ */ new Date();
3188
+ const notAfter = daysFromNow(notBefore, validityDays);
3189
+ const { cert } = await createCertificateFromCsr({
3190
+ csr: csrPem,
3191
+ issuerName,
3192
+ issuerPublicKey,
3193
+ signingKey,
3194
+ signingAlgorithm,
3195
+ serialNumber,
3196
+ notBefore,
3197
+ notAfter,
3198
+ purpose: CertificatePurpose2.ForCertificateAuthority,
3199
+ // the subordinate's own SAN, not the issuer's
3200
+ ...sanFromCsr(csrPem),
3201
+ revocation: {
3202
+ crlDistributionUrl: ca.crlDistributionUrl,
3203
+ ocspResponderUrl: ca.ocspResponderUrl,
3204
+ caIssuersUrl: ca.caIssuersUrl
3205
+ }
3206
+ });
3207
+ await fs10.promises.writeFile(certFile, cert);
3208
+ this.#record(db, serialNumber, cert, notAfter);
3209
+ }
3210
+ /**
3211
+ * Self-sign a certificate with a caller-supplied key file and record it
3212
+ * in this CA's database: the native equivalent of `openssl req -new`
3213
+ * followed by `openssl ca -selfsign`, which likewise applies the
3214
+ * end-entity profile and updates `index.txt`.
3215
+ */
3216
+ async createSelfSignedCertificate(ca, certificateFile, privateKeyFile, params) {
3217
+ displaySubtitle("- the certificate signing request");
3218
+ const key = await privateKeyToCryptoKey(readPrivateKey2(privateKeyFile, await ca._privateKeyPassphrase()));
3219
+ const { csr } = await createCertificateSigningRequest2({
3220
+ privateKey: key,
3221
+ subject: params.subject ? new Subject4(params.subject).toString() : ca.subject.toString(),
3222
+ dns: params.dns,
3223
+ ip: params.ip,
3224
+ applicationUri: params.applicationUri,
3225
+ purpose: CertificatePurpose2.ForApplication
3226
+ });
3227
+ displaySubtitle("- creating the self-signed certificate");
3228
+ const request = new x509.Pkcs10CertificateRequest(csr);
3229
+ const db = new CaDatabase(ca.rootDir);
3230
+ const serialNumber = db.nextSerial();
3231
+ const notBefore = params.startDate ?? /* @__PURE__ */ new Date();
3232
+ const notAfter = params.endDate ?? daysFromNow(notBefore, params.validity ?? 365);
3233
+ const { cert } = await createCertificateFromCsr({
3234
+ csr,
3235
+ issuerName: request.subjectName,
3236
+ issuerPublicKey: request.publicKey,
3237
+ signingKey: key,
3238
+ signingAlgorithm: RSA_SHA256,
3239
+ serialNumber,
3240
+ notBefore,
3241
+ notAfter,
3242
+ purpose: CertificatePurpose2.ForApplication,
3243
+ ...sanFromCsr(csr)
3244
+ });
3245
+ await fs10.promises.writeFile(certificateFile, cert);
3246
+ this.#record(db, serialNumber, cert, notAfter);
3247
+ }
3248
+ /** `certs/<SERIAL>.pem` plus the `V` row, exactly as `openssl ca` writes them. */
3249
+ #record(db, serialNumber, certPem, notAfter) {
3250
+ db.storeCertificate(serialNumber, certPem);
3251
+ db.appendIssued({
3252
+ serial: serialNumber,
3253
+ expiryDate: notAfter,
3254
+ subject: toOpenSslSubjectString(new x509.X509Certificate(certPem).subjectName)
3255
+ });
3256
+ }
3257
+ /**
3258
+ * The CA's signing key and issuer identity, resolved fresh on every
3259
+ * call and never cached across operations. The identity is read back
3260
+ * from the CA certificate on disk so that the issuer field of anything
3261
+ * this CA signs is byte-identical to that certificate's subject.
3262
+ */
3263
+ async #issuerContext(ca) {
3264
+ const signingKey = await ca._getSigningKey();
3265
+ const caCertPem = await fs10.promises.readFile(ca.caCertificate, "utf-8");
3266
+ const caCert = new x509.X509Certificate(caCertPem);
3267
+ return {
3268
+ signingKey,
3269
+ signingAlgorithm: signingAlgorithmOf(signingKey),
3270
+ issuerPublicKey: caCert.publicKey,
3271
+ issuerName: caCert.subjectName
3272
+ };
3273
+ }
3274
+ async signEndEntityCsr(ca, certificate, csr, params, sanOverride) {
3275
+ const { signingKey, signingAlgorithm, issuerPublicKey, issuerName } = await this.#issuerContext(ca);
3276
+ const db = new CaDatabase(ca.rootDir);
3277
+ const serialNumber = db.nextSerial();
3278
+ const csrPem = await fs10.promises.readFile(csr, "utf-8");
3279
+ const notBefore = params.startDate ?? /* @__PURE__ */ new Date();
3280
+ const notAfter = params.endDate ?? new Date(notBefore.getTime() + (params.validity ?? 365) * 24 * 60 * 60 * 1e3);
3281
+ const { cert } = await createCertificateFromCsr({
3282
+ csr: csrPem,
3283
+ issuerName,
3284
+ issuerPublicKey,
3285
+ signingKey,
3286
+ signingAlgorithm,
3287
+ serialNumber,
3288
+ notBefore,
3289
+ notAfter,
3290
+ purpose: CertificatePurpose2.ForApplication,
3291
+ dns: sanOverride.dns,
3292
+ ip: sanOverride.ip,
3293
+ applicationUri: sanOverride.applicationUri,
3294
+ revocation: {
3295
+ crlDistributionUrl: ca.crlDistributionUrl,
3296
+ ocspResponderUrl: ca.ocspResponderUrl,
3297
+ caIssuersUrl: ca.caIssuersUrl
3298
+ }
3299
+ });
3300
+ await fs10.promises.writeFile(certificate, cert);
3301
+ this.#record(db, serialNumber, cert, notAfter);
3302
+ }
3303
+ async #regenerateCrlLocked(ca, db) {
3304
+ const { signingKey, signingAlgorithm, issuerPublicKey, issuerName } = await this.#issuerContext(ca);
3305
+ const crlNumber = db.nextCrlNumber();
3306
+ const entries = db.readIndex().filter((r) => r.status === "revoked").map((r) => ({
3307
+ serialNumber: r.serial,
3308
+ revocationDate: r.revocationDate ? new Date(r.revocationDate) : /* @__PURE__ */ new Date(),
3309
+ reason: r.reason ? REASON_TO_X509_CRL_REASON[r.reason] : void 0
3310
+ }));
3311
+ const { crl } = await createCrl({
3312
+ issuerName,
3313
+ issuerPublicKey,
3314
+ signingKey,
3315
+ signingAlgorithm,
3316
+ crlNumber: BigInt(`0x${crlNumber}`),
3317
+ entries
3318
+ });
3319
+ await fs10.promises.writeFile(ca.revocationList, crl);
3320
+ const der = new x509.X509Crl(crl).rawData;
3321
+ await fs10.promises.writeFile(ca.revocationListDER, Buffer.from(der));
3322
+ }
3323
+ async regenerateCrl(ca) {
3324
+ const db = new CaDatabase(ca.rootDir);
3325
+ await this.#regenerateCrlLocked(ca, db);
3326
+ }
3327
+ async revoke(ca, certificate, reason) {
3328
+ const certPem = await fs10.promises.readFile(certificate, "utf-8");
3329
+ const cert = new x509.X509Certificate(certPem);
3330
+ const db = new CaDatabase(ca.rootDir);
3331
+ db.markRevoked(cert.serialNumber, /* @__PURE__ */ new Date(), reason);
3332
+ await this.#regenerateCrlLocked(ca, db);
3333
+ }
3334
+ };
3335
+ }
3336
+ });
3337
+
3338
+ // packages/node-opcua-pki/lib/ca/backends/openssl_ca_backend.ts
3339
+ import fs11 from "fs";
3340
+ import path8 from "path";
3341
+ import { Subject as Subject5 } from "node-opcua-crypto";
3342
+ function caAltName(ca) {
3343
+ return `URI:urn:${ca.subject.commonName || "NodeOPCUA-CA"}`;
3344
+ }
3345
+ function caConfigEnvOverrides(ca, altName = caAltName(ca)) {
3346
+ const aiaLegs = [];
3347
+ if (ca.ocspResponderUrl) {
3348
+ aiaLegs.push(`OCSP;URI:${ca.ocspResponderUrl}`);
3349
+ }
3350
+ if (ca.caIssuersUrl) {
3351
+ aiaLegs.push(`caIssuers;URI:${ca.caIssuersUrl}`);
3352
+ }
3353
+ return {
3354
+ ALTNAME: altName,
3355
+ CDP_URL: ca.crlDistributionUrl ?? "",
3356
+ AIA_VALUE: aiaLegs.join(",")
3357
+ };
3358
+ }
3359
+ var n4, OpenSslCaBackend;
3360
+ var init_openssl_ca_backend = __esm({
3361
+ "packages/node-opcua-pki/lib/ca/backends/openssl_ca_backend.ts"() {
3362
+ "use strict";
3363
+ init_esm_shims();
3364
+ init_toolbox();
3365
+ init_with_openssl();
3366
+ n4 = makePath;
3367
+ OpenSslCaBackend = class {
3368
+ /** This backend is the `openssl` executable, so it has to be there. */
3369
+ /**
3370
+ * `-passin env:` argv and env for an openssl call that has to load this
3371
+ * CA's key. Built here rather than on the CA: the passphrase is the
3372
+ * CA's business, but expressing it as an openssl argument is this
3373
+ * backend's, and the core has no reason to know the flag exists.
3374
+ */
3375
+ async #passin(ca) {
3376
+ return passinArg(await ca._privateKeyPassphrase());
3377
+ }
3378
+ /** The openssl CLI loads its key from a file, so it cannot call out to an HSM. */
3379
+ supportsExternalSigner = false;
3380
+ async preflight() {
3381
+ await ensure_openssl_installed();
3382
+ }
3383
+ /**
3384
+ * Render `conf/caconfig.cnf` once with explicit overrides, run `fn`
3385
+ * with the rendered path, and always remove the temp file. Structural
3386
+ * guarantee that (a) every render carries the three required env
3387
+ * values and (b) no rendered `<name>.<pid>-<n>.tmp` file is ever left
3388
+ * behind — each was previously a per-method discipline.
3389
+ */
3390
+ async #withConfig(ca, altName, fn) {
3391
+ const caRootDir = path8.resolve(ca.rootDir);
3392
+ const options = { cwd: caRootDir };
3393
+ const configFile = generateStaticConfig("conf/caconfig.cnf", options, caConfigEnvOverrides(ca, altName));
3394
+ try {
3395
+ return await fn(configFile, options);
3396
+ } finally {
3397
+ await cleanupStaticConfig(configFile, options);
3398
+ }
3399
+ }
3400
+ async #generateCaCsrWith(ca, configFile, options, privateKeyFile, csrFile) {
3401
+ const passin = await this.#passin(ca);
3402
+ displayTitle("Generate a certificate request for the CA key");
3403
+ await execute_openssl(
3404
+ [
3405
+ "req",
3406
+ "-new",
3407
+ "-sha256",
3408
+ "-extensions",
3409
+ "v3_ca_req",
3410
+ "-config",
3411
+ n4(configFile),
3412
+ "-key",
3413
+ n4(privateKeyFile),
3414
+ "-out",
3415
+ n4(csrFile),
3416
+ "-subj",
3417
+ ca.subject.toString(),
3418
+ ...passin.args
3419
+ ],
3420
+ { ...options, env: passin.env }
3421
+ );
3422
+ }
3423
+ /**
3424
+ * `openssl ca -gencrl` signs the CRL with the CA key it finds through the
3425
+ * config file (`private_key = $dir/private/cakey.pem`), so it needs the
3426
+ * CA passphrase like every other `openssl ca` invocation.
3427
+ */
3428
+ async #regenerateCrlWith(ca, configFile, options) {
3429
+ const passin = await this.#passin(ca);
3430
+ displaySubtitle("regenerate CRL (Certificate Revocation List)");
3431
+ await execute_openssl(["ca", "-gencrl", "-config", n4(configFile), "-out", "crl/revocation_list.crl", ...passin.args], {
3432
+ ...options,
3433
+ env: passin.env
3434
+ });
3435
+ await execute_openssl(
3436
+ ["crl", "-in", "crl/revocation_list.crl", "-out", "crl/revocation_list.der", "-outform", "der"],
3437
+ options
3438
+ );
3439
+ displaySubtitle("Display (Certificate Revocation List)");
3440
+ await execute_openssl(["crl", "-in", n4(ca.revocationList), "-text", "-noout"], options);
3441
+ }
3442
+ async generateCaCsr(ca, _caRootDir, privateKeyFile, csrFile) {
3443
+ await this.#withConfig(
3444
+ ca,
3445
+ caAltName(ca),
3446
+ (configFile, options) => this.#generateCaCsrWith(ca, configFile, options, privateKeyFile, csrFile)
3447
+ );
3448
+ }
3449
+ async bootstrap(ca) {
3450
+ const caRootDir = path8.resolve(ca.rootDir);
3451
+ const privateKeyFilename = path8.join(caRootDir, "private/cakey.pem");
3452
+ const csrFilename = path8.join(caRootDir, "private/cakey.csr");
3453
+ await this.#withConfig(ca, caAltName(ca), async (configFile, options) => {
3454
+ await this.#generateCaCsrWith(ca, configFile, options, privateKeyFilename, csrFilename);
3455
+ const issuerCA = ca._issuerCA;
3456
+ if (issuerCA) {
3457
+ displayTitle("Generate CA Certificate (signed by issuer CA)");
3458
+ const issuerCert = path8.resolve(issuerCA.caCertificate);
3459
+ const issuerKey = path8.resolve(issuerCA.rootDir, "private/cakey.pem");
3460
+ const issuerSerial = path8.resolve(issuerCA.rootDir, "serial");
3461
+ const issuerPassin = await this.#passin(issuerCA);
3462
+ const signWithIssuer = async () => {
3463
+ await execute_openssl(
3464
+ [
3465
+ "x509",
3466
+ "-sha256",
3467
+ "-req",
3468
+ "-days",
3469
+ "3650",
3470
+ "-extensions",
3471
+ "v3_ca",
3472
+ "-extfile",
3473
+ n4(configFile),
3474
+ "-in",
3475
+ "private/cakey.csr",
3476
+ "-CA",
3477
+ n4(issuerCert),
3478
+ "-CAkey",
3479
+ n4(issuerKey),
3480
+ "-CAserial",
3481
+ n4(issuerSerial),
3482
+ "-out",
3483
+ "public/cacert.pem",
3484
+ ...issuerPassin.args
3485
+ ],
3486
+ { ...options, env: issuerPassin.env }
3487
+ );
3488
+ };
3489
+ if (path8.resolve(issuerCA.rootDir) === caRootDir) {
3490
+ await signWithIssuer();
3491
+ } else {
3492
+ await issuerCA._withCaDirectoryLock(signWithIssuer);
3493
+ }
3494
+ } else {
3495
+ displayTitle("Generate CA Certificate (self-signed)");
3496
+ const passin = await this.#passin(ca);
3497
+ await execute_openssl(
3498
+ [
3499
+ "x509",
3500
+ "-sha256",
3501
+ "-req",
3502
+ "-days",
3503
+ "3650",
3504
+ "-extensions",
3505
+ "v3_ca",
3506
+ "-extfile",
3507
+ n4(configFile),
3508
+ "-in",
3509
+ "private/cakey.csr",
3510
+ "-signkey",
3511
+ n4(privateKeyFilename),
3512
+ "-out",
3513
+ "public/cacert.pem",
3514
+ ...passin.args
3515
+ ],
3516
+ { ...options, env: passin.env }
3517
+ );
3518
+ }
3519
+ displaySubtitle("generate initial CRL (Certificate Revocation List)");
3520
+ await this.#regenerateCrlWith(ca, configFile, options);
3521
+ });
3522
+ displayTitle("Create Certificate Authority (CA) ---> DONE");
3523
+ }
3524
+ async regenerateCrl(ca) {
3525
+ await this.#withConfig(ca, caAltName(ca), (configFile, options) => this.#regenerateCrlWith(ca, configFile, options));
3526
+ }
3527
+ async signSubordinateCsr(ca, csrFile, certFile, validityDays) {
3528
+ await this.#withConfig(ca, caAltName(ca), async (configFile, options) => {
3529
+ const caRootDir = options.cwd;
3530
+ const passin = await this.#passin(ca);
3531
+ await execute_openssl(
3532
+ [
3533
+ "x509",
3534
+ "-sha256",
3535
+ "-req",
3536
+ "-days",
3537
+ String(validityDays),
3538
+ "-extensions",
3539
+ "v3_ca",
3540
+ "-extfile",
3541
+ n4(configFile),
3542
+ "-in",
3543
+ n4(csrFile),
3544
+ "-CA",
3545
+ n4(ca.caCertificate),
3546
+ "-CAkey",
3547
+ n4(path8.join(caRootDir, "private/cakey.pem")),
3548
+ "-CAserial",
3549
+ n4(path8.join(caRootDir, "serial")),
3550
+ "-out",
3551
+ n4(certFile),
3552
+ ...passin.args
3553
+ ],
3554
+ { ...options, env: passin.env }
3555
+ );
3556
+ });
3557
+ }
3558
+ async signEndEntityCsr(ca, certificate, csr, params1, sanOverride) {
3559
+ await this.#withConfig(ca, buildSubjectAltNameString(sanOverride), async (configFile, options) => {
3560
+ displaySubtitle("- then we ask the authority to sign the certificate signing request");
3561
+ const passin = await this.#passin(ca);
3562
+ await execute_openssl(
3563
+ [
3564
+ "ca",
3565
+ "-config",
3566
+ configFile,
3567
+ "-startdate",
3568
+ x509Date(params1.startDate),
3569
+ "-enddate",
3570
+ x509Date(params1.endDate),
3571
+ "-batch",
3572
+ "-out",
3573
+ n4(certificate),
3574
+ "-in",
3575
+ n4(csr),
3576
+ ...passin.args
3577
+ ],
3578
+ { ...options, env: passin.env }
3579
+ );
3580
+ displaySubtitle("- dump the certificate for a check");
3581
+ await execute_openssl(["x509", "-in", n4(certificate), "-dates", "-fingerprint", "-purpose", "-noout"], options);
3582
+ });
3583
+ }
3584
+ async revoke(ca, certificate, reason) {
3585
+ setEnv("RANDFILE", path8.join(ca.rootDir, "random.rnd"));
3586
+ await this.#withConfig(ca, caAltName(ca), async (configFile, options) => {
3587
+ displaySubtitle("Revoke certificate");
3588
+ const passin = await this.#passin(ca);
3589
+ await execute_openssl_no_failure(
3590
+ ["ca", "-verbose", "-config", n4(configFile), "-revoke", certificate, "-crl_reason", reason, ...passin.args],
3591
+ { ...options, env: passin.env }
3592
+ );
3593
+ await this.#regenerateCrlWith(ca, configFile, options);
3594
+ displaySubtitle("Verify that certificate is revoked");
3595
+ await execute_openssl_no_failure(
3596
+ [
3597
+ "verify",
3598
+ "-verbose",
3599
+ "-CRLfile",
3600
+ n4(ca.revocationList),
3601
+ "-CAfile",
3602
+ n4(ca.caCertificate),
3603
+ "-crl_check",
3604
+ n4(certificate)
3605
+ ],
3606
+ options
3607
+ );
3608
+ displaySubtitle("Produce CRL in DER form ");
3609
+ await execute_openssl(
3610
+ ["crl", "-in", n4(ca.revocationList), "-out", "crl/revocation_list.der", "-outform", "der"],
3611
+ options
3612
+ );
3613
+ displaySubtitle("Produce CRL in PEM form ");
3614
+ await execute_openssl(
3615
+ ["crl", "-in", n4(ca.revocationList), "-out", "crl/revocation_list.pem", "-outform", "pem", "-text"],
3616
+ options
3617
+ );
3618
+ });
3619
+ }
3620
+ async createSelfSignedCertificate(ca, certificateFile, privateKeyFile, params) {
3621
+ const envOverrides = caConfigEnvOverrides(ca, buildSubjectAltNameString(params));
3622
+ const configFile = generateStaticConfig(ca.configFile, { cwd: ca.rootDir }, envOverrides);
3623
+ const options = {
3624
+ cwd: ca.rootDir,
3625
+ openssl_conf: makePath(configFile)
3626
+ };
3627
+ try {
3628
+ const subject = params.subject ? new Subject5(params.subject).toString() : "";
3629
+ const subjectOptions = subject && subject.length > 1 ? ["-subj", subject] : [];
3630
+ const csrFile = `${certificateFile}_csr`;
3631
+ const passin = await this.#passin(ca);
3632
+ displaySubtitle("- the certificate signing request");
3633
+ await execute_openssl(
3634
+ [
3635
+ "req",
3636
+ "-new",
3637
+ "-sha256",
3638
+ ...subjectOptions,
3639
+ "-batch",
3640
+ "-key",
3641
+ n4(privateKeyFile),
3642
+ "-out",
3643
+ n4(csrFile),
3644
+ ...passin.args
3645
+ ],
3646
+ { ...options, env: passin.env }
3647
+ );
3648
+ displaySubtitle("- creating the self-signed certificate");
3649
+ await execute_openssl(
3650
+ [
3651
+ "ca",
3652
+ "-selfsign",
3653
+ "-keyfile",
3654
+ n4(privateKeyFile),
3655
+ "-startdate",
3656
+ x509Date(params.startDate),
3657
+ "-enddate",
3658
+ x509Date(params.endDate),
3659
+ "-batch",
3660
+ "-out",
3661
+ n4(certificateFile),
3662
+ "-in",
3663
+ n4(csrFile),
3664
+ ...passin.args
3665
+ ],
3666
+ { ...options, env: passin.env }
3667
+ );
3668
+ displaySubtitle("- dump the certificate for a check");
3669
+ await execute_openssl(["x509", "-in", n4(certificateFile), "-dates", "-fingerprint", "-purpose", "-noout"], {});
3670
+ displaySubtitle("- verify self-signed certificate");
3671
+ await execute_openssl_no_failure(["verify", "-verbose", "-CAfile", n4(certificateFile), n4(certificateFile)], options);
3672
+ await fs11.promises.unlink(csrFile);
3673
+ } finally {
3674
+ await cleanupStaticConfig(configFile, { cwd: ca.rootDir });
3675
+ }
3676
+ }
3677
+ };
3678
+ }
3679
+ });
3680
+
3681
+ // packages/node-opcua-pki/lib/ca/templates/ca_config_template.cnf.ts
3682
+ var config2, ca_config_template_cnf_default;
3683
+ var init_ca_config_template_cnf = __esm({
3684
+ "packages/node-opcua-pki/lib/ca/templates/ca_config_template.cnf.ts"() {
3685
+ "use strict";
3686
+ init_esm_shims();
3687
+ config2 = `#.........DO NOT MODIFY BY HAND .........................
3688
+ [ ca ]
3689
+ default_ca = CA_default
3690
+ [ CA_default ]
3691
+ dir = "%%ROOT_FOLDER%%" # the main CA folder (quoted: see renderCaConfig)
3692
+ certs = $dir/certs # where to store certificates
3693
+ new_certs_dir = $dir/certs #
3694
+ database = $dir/index.txt # the certificate database
3695
+ serial = $dir/serial # the serial number counter
3696
+ certificate = $dir/public/cacert.pem # The root CA certificate
3697
+ private_key = $dir/private/cakey.pem # the CA private key
3698
+ x509_extensions = usr_cert #
3699
+ default_days = 3650 # default validity : 10 years
3700
+
3701
+ # default_md = sha1
3702
+
3703
+ default_md = sha256 # The default digest algorithm
3704
+
3705
+ preserve = no
3706
+ policy = policy_match
3707
+ # randfile = $dir/random.rnd
3708
+ # default_startdate = YYMMDDHHMMSSZ
3709
+ # default_enddate = YYMMDDHHMMSSZ
3710
+ crl_dir = $dir/crl
3711
+ crl_extensions = crl_ext
3712
+ crl = $dir/crl/revocation_list.crl # the Revocation list
3713
+ crlnumber = $dir/crlnumber # CRL number file
3714
+ default_crl_days = 30
3715
+ default_crl_hours = 24
3716
+ #msie_hack
3717
+
3718
+ [ policy_match ]
3719
+ countryName = optional
3720
+ stateOrProvinceName = optional
3721
+ localityName = optional
3722
+ organizationName = optional
3723
+ organizationalUnitName = optional
3724
+ commonName = optional
3725
+ emailAddress = optional
3726
+
3727
+ [ req ]
3728
+ default_bits = 4096 # Size of keys
3729
+ default_keyfile = key.pem # name of generated keys
3730
+ distinguished_name = req_distinguished_name
3731
+ attributes = req_attributes
3732
+ x509_extensions = v3_ca
3733
+ #input_password
3734
+ #output_password
3735
+ string_mask = nombstr # permitted characters
3736
+ req_extensions = v3_req
3737
+
3738
+ [ req_distinguished_name ]
3739
+
3740
+ #0 countryName = Country Name (2 letter code)
3741
+ # countryName_default = FR
3742
+ # countryName_min = 2
3743
+ # countryName_max = 2
3744
+ # stateOrProvinceName = State or Province Name (full name)
3745
+ # stateOrProvinceName_default = Ile de France
3746
+ # localityName = Locality Name (city, district)
3747
+ # localityName_default = Paris
3748
+ organizationName = Organization Name (company)
3749
+ organizationName_default = NodeOPCUA
3750
+ # organizationalUnitName = Organizational Unit Name (department, division)
3751
+ # organizationalUnitName_default = R&D
3752
+ commonName = Common Name (hostname, FQDN, IP, or your name)
3753
+ commonName_max = 256
3754
+ commonName_default = NodeOPCUA
3755
+ # emailAddress = Email Address
3756
+ # emailAddress_max = 40
3757
+ # emailAddress_default = node-opcua (at) node-opcua (dot) com
3758
+
3759
+ [ req_attributes ]
3760
+ #challengePassword = A challenge password
3761
+ #challengePassword_min = 4
3762
+ #challengePassword_max = 20
3763
+ #unstructuredName = An optional company name
3764
+ [ usr_cert ]
3765
+ basicConstraints = critical, CA:FALSE
3766
+ subjectKeyIdentifier = hash
3767
+ authorityKeyIdentifier = keyid,issuer:always
3768
+ #authorityKeyIdentifier = keyid
3769
+ subjectAltName = $ENV::ALTNAME
3770
+ # issuerAltName = issuer:copy
3771
+ nsComment = ''OpenSSL Generated Certificate''
3772
+ #nsCertType = client, email, objsign for ''everything including object signing''
3773
+ #nsCaRevocationUrl = http://www.domain.dom/ca-crl.pem
3774
+ #nsBaseUrl =
3775
+ #nsRenewalUrl =
3776
+ #nsCaPolicyUrl =
3777
+ #nsSslServerName =
3778
+ keyUsage = critical, digitalSignature, nonRepudiation, keyEncipherment, dataEncipherment, keyAgreement
3779
+ extendedKeyUsage = critical,serverAuth ,clientAuth
3780
+ {{#CDP_URL}}crlDistributionPoints = URI:$ENV::CDP_URL
3781
+ {{/CDP_URL}}{{#AIA_VALUE}}authorityInfoAccess = $ENV::AIA_VALUE
3782
+ {{/AIA_VALUE}}
3783
+ [ v3_req ]
3784
+ basicConstraints = critical, CA:FALSE
3785
+ keyUsage = nonRepudiation, digitalSignature, keyEncipherment, dataEncipherment, keyAgreement
3786
+ extendedKeyUsage = critical,serverAuth ,clientAuth
3787
+ subjectAltName = $ENV::ALTNAME
3788
+ nsComment = "CA Generated by Node-OPCUA Certificate utility using openssl"
3789
+ [ v3_ca_req ]
3790
+ subjectKeyIdentifier = hash
3791
+ basicConstraints = CA:TRUE
3792
+ keyUsage = critical, cRLSign, keyCertSign
3793
+ subjectAltName = $ENV::ALTNAME
3794
+ nsComment = "CA CSR generated by Node-OPCUA Certificate utility using openssl"
3795
+ [ v3_ca ]
3796
+ subjectKeyIdentifier = hash
3797
+ authorityKeyIdentifier = keyid:always,issuer:always
3798
+ basicConstraints = CA:TRUE
3799
+ keyUsage = critical, cRLSign, keyCertSign
3800
+ subjectAltName = $ENV::ALTNAME
3801
+ nsComment = "CA Certificate generated by Node-OPCUA Certificate utility using openssl"
3802
+ #nsCertType = sslCA, emailCA
3803
+ #issuerAltName = issuer:copy
3804
+ #obj = DER:02:03
3805
+ {{#CDP_URL}}crlDistributionPoints = URI:$ENV::CDP_URL
3806
+ {{/CDP_URL}}{{#AIA_VALUE}}authorityInfoAccess = $ENV::AIA_VALUE
3807
+ {{/AIA_VALUE}}[ v3_selfsigned]
3808
+ basicConstraints = critical, CA:FALSE
3809
+ keyUsage = nonRepudiation, digitalSignature, keyEncipherment, dataEncipherment, keyAgreement
3810
+ extendedKeyUsage = critical,serverAuth ,clientAuth
3811
+ nsComment = "Self-signed certificate, generated by NodeOPCUA"
3812
+ subjectAltName = $ENV::ALTNAME
3813
+
3814
+ [ crl_ext ]
3815
+ #issuerAltName = issuer:copy
3816
+ authorityKeyIdentifier = keyid:always,issuer:always
3817
+ #authorityInfoAccess = @issuer_info`;
3818
+ ca_config_template_cnf_default = config2;
3819
+ }
3820
+ });
3821
+
3822
+ // packages/node-opcua-pki/lib/ca/core/certificate_authority_core.ts
3823
+ import assert9 from "assert";
3824
+ import fs12 from "fs";
3825
+ import os4 from "os";
3826
+ import path9 from "path";
3827
+ import { withLock as withLock2 } from "@ster5/global-mutex";
3828
+ import chalk6 from "chalk";
3829
+ import {
3830
+ CertificatePurpose as CertificatePurpose3,
3831
+ certificateMatchesPrivateKey,
3832
+ convertPEMtoDER as convertPEMtoDER2,
3833
+ createCertificateSigningRequest as createCertificateSigningRequest3,
3834
+ createPfx,
3835
+ exploreCertificate as exploreCertificate2,
3836
+ exploreCertificateSigningRequest,
3837
+ generatePrivateKeyFile as generatePrivateKeyFile2,
3838
+ privateKeyToCryptoKey as privateKeyToCryptoKey2,
3839
+ readCertificatePEM as readCertificatePEM2,
3840
+ readCertificateSigningRequest,
3841
+ readPrivateKey as readPrivateKey3,
3842
+ Subject as Subject6,
3843
+ toPem as toPem2,
3844
+ verifyCertificateSignature as verifyCertificateSignature2,
3845
+ writePrivateKeyFile as writePrivateKeyFile2,
3846
+ x509 as x5092
3847
+ } from "node-opcua-crypto";
3848
+ function escapeOpensslConfDoubleQuoted(value) {
3849
+ return value.replace(/\\/g, "\\\\").replace(/"/g, '\\"');
3850
+ }
3851
+ function renderCaConfig(caRootDir) {
3852
+ return configurationFileTemplate.replace(/%%ROOT_FOLDER%%/, escapeOpensslConfDoubleQuoted(makePath(caRootDir)));
3853
+ }
3854
+ function octetStringToIpAddress(a) {
3855
+ 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();
3856
+ }
3857
+ function validateRevocationUrl(url, fieldName) {
3858
+ if (url === void 0) {
3859
+ return void 0;
3860
+ }
3861
+ if (url === "") {
3862
+ throw new Error(`${fieldName} must not be empty \u2014 pass undefined to disable the extension`);
3863
+ }
3864
+ let parsed;
3865
+ try {
3866
+ parsed = new URL(url);
3867
+ } catch {
3868
+ throw new Error(`${fieldName} is not a valid URL: ${url}`);
3869
+ }
3870
+ if (parsed.protocol !== "http:" && parsed.protocol !== "https:") {
3871
+ throw new Error(`${fieldName} must use http: or https: (got ${parsed.protocol} in ${url})`);
3872
+ }
3873
+ if (!parsed.pathname || parsed.pathname === "/") {
3874
+ throw new Error(`${fieldName} must include a path component (got ${url})`);
3187
3875
  }
3188
3876
  const isLoopback = parsed.hostname === "localhost" || parsed.hostname === "::1" || parsed.hostname.startsWith("127.");
3189
3877
  if (isLoopback) {
@@ -3193,27 +3881,25 @@ function validateRevocationUrl(url, fieldName) {
3193
3881
  }
3194
3882
  return url;
3195
3883
  }
3196
- var defaultSubject, configurationFileTemplate, configurationFileSimpleTemplate2, config3, n5, CertificateAuthority;
3197
- var init_certificate_authority = __esm({
3198
- "packages/node-opcua-pki/lib/ca/certificate_authority.ts"() {
3884
+ var defaultSubject, configurationFileTemplate, config3, CA_LOCK_MAX_WAIT_MS, CA_LOCK_RETRY, CertificateAuthorityCore;
3885
+ var init_certificate_authority_core = __esm({
3886
+ "packages/node-opcua-pki/lib/ca/core/certificate_authority_core.ts"() {
3199
3887
  "use strict";
3200
3888
  init_esm_shims();
3201
- init_toolbox_pfx();
3202
3889
  init_toolbox();
3203
- init_with_openssl();
3204
- init_simple_config_template_cnf();
3890
+ init_ca_database();
3205
3891
  init_ca_config_template_cnf();
3206
3892
  defaultSubject = "/C=FR/ST=IDF/L=Paris/O=Local NODE-OPCUA Certificate Authority/CN=NodeOPCUA-CA";
3207
3893
  configurationFileTemplate = ca_config_template_cnf_default;
3208
- configurationFileSimpleTemplate2 = simple_config_template_cnf_default;
3209
3894
  config3 = {
3210
3895
  certificateDir: "INVALID",
3211
3896
  forceCA: false,
3212
3897
  pkiDir: "INVALID"
3213
3898
  };
3214
- n5 = makePath;
3215
- assert10(octetStringToIpAddress("c07b9179") === "192.123.145.121");
3216
- CertificateAuthority = class {
3899
+ CA_LOCK_MAX_WAIT_MS = 5 * 6e4;
3900
+ CA_LOCK_RETRY = { minTimeout: 20, maxTimeout: 250 };
3901
+ assert9(octetStringToIpAddress("c07b9179") === "192.123.145.121");
3902
+ CertificateAuthorityCore = class {
3217
3903
  /** RSA key size used when generating the CA private key. */
3218
3904
  keySize;
3219
3905
  /** Root filesystem path of the CA directory structure. */
@@ -3230,14 +3916,41 @@ var init_certificate_authority = __esm({
3230
3916
  /** resolved once (see `privateKeyPassphrase`); `#passphraseResolved` distinguishes "none" from "not yet" */
3231
3917
  #resolvedPassphrase;
3232
3918
  #passphraseResolved = false;
3919
+ /** Signing backend: `openssl` shells out to the CLI, `native` signs in-process. */
3920
+ #backend;
3921
+ /** External signing key (HSM/KMS), when one was supplied instead of a key file. */
3922
+ #signer;
3923
+ /** Read access to `index.txt` / `certs/<SERIAL>.pem`. */
3924
+ #db;
3233
3925
  constructor(options) {
3234
- assert10(Object.prototype.hasOwnProperty.call(options, "location"));
3235
- assert10(Object.prototype.hasOwnProperty.call(options, "keySize"));
3926
+ assert9(Object.prototype.hasOwnProperty.call(options, "location"));
3927
+ assert9(Object.prototype.hasOwnProperty.call(options, "keySize"));
3236
3928
  this.location = options.location;
3237
3929
  this.keySize = options.keySize || 2048;
3238
- this.subject = new Subject4(options.subject || defaultSubject);
3930
+ this.subject = new Subject6(options.subject || defaultSubject);
3239
3931
  this._issuerCA = options.issuerCA;
3240
3932
  this.#privateKeyPassphrase = options.privateKeyPassphrase;
3933
+ this.#signer = options.signer;
3934
+ if (options.signer) {
3935
+ const algorithm = options.signer.algorithm;
3936
+ if (algorithm.name !== "RSASSA-PKCS1-v1_5" && algorithm.name !== "ECDSA") {
3937
+ throw new Error(
3938
+ `CertificateAuthority: signer algorithm ${algorithm.name} is not supported - use RSASSA-PKCS1-v1_5 or ECDSA.`
3939
+ );
3940
+ }
3941
+ if (algorithm.name === "ECDSA" && !algorithm.namedCurve) {
3942
+ throw new Error(
3943
+ "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."
3944
+ );
3945
+ }
3946
+ }
3947
+ if (options.signer && !options.backend.supportsExternalSigner) {
3948
+ throw new Error(
3949
+ "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."
3950
+ );
3951
+ }
3952
+ this.#backend = options.backend;
3953
+ this.#db = new CaDatabase(this.location);
3241
3954
  if (options.crlDistributionUrl !== void 0) {
3242
3955
  this.setCrlDistributionUrl(options.crlDistributionUrl);
3243
3956
  }
@@ -3296,35 +4009,10 @@ var init_certificate_authority = __esm({
3296
4009
  * the `authorityInfoAccess` extension on every subsequently-issued
3297
4010
  * certificate. Pass `undefined` to disable.
3298
4011
  *
3299
- * @see US-202
3300
- */
3301
- setCaIssuersUrl(url) {
3302
- this._caIssuersUrl = validateRevocationUrl(url, "caIssuersUrl");
3303
- }
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.
4012
+ * @see US-202
3311
4013
  */
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
- }
4014
+ setCaIssuersUrl(url) {
4015
+ this._caIssuersUrl = validateRevocationUrl(url, "caIssuersUrl");
3328
4016
  }
3329
4017
  /** Absolute path to the CA root directory (alias for {@link location}). */
3330
4018
  get rootDir() {
@@ -3332,11 +4020,11 @@ var init_certificate_authority = __esm({
3332
4020
  }
3333
4021
  /** Path to the OpenSSL configuration file (`conf/caconfig.cnf`). */
3334
4022
  get configFile() {
3335
- return path6.normalize(path6.join(this.rootDir, "./conf/caconfig.cnf"));
4023
+ return path9.normalize(path9.join(this.rootDir, "./conf/caconfig.cnf"));
3336
4024
  }
3337
4025
  /** Path to the CA private key (`private/cakey.pem`); may be passphrase-encrypted, see {@link getPrivateKey}. */
3338
4026
  get privateKey() {
3339
- return path6.join(path6.resolve(this.rootDir), "private/cakey.pem");
4027
+ return path9.join(path9.resolve(this.rootDir), "private/cakey.pem");
3340
4028
  }
3341
4029
  /**
3342
4030
  * The CA private key, decrypted with the configured `privateKeyPassphrase`
@@ -3344,7 +4032,51 @@ var init_certificate_authority = __esm({
3344
4032
  * on an encrypted key with no or the wrong passphrase.
3345
4033
  */
3346
4034
  async getPrivateKey() {
3347
- return readPrivateKey2(this.privateKey, await this._privateKeyPassphrase());
4035
+ this.#assertKeyIsOnDisk("getPrivateKey");
4036
+ return readPrivateKey3(this.privateKey, await this._privateKeyPassphrase());
4037
+ }
4038
+ /**
4039
+ * True when this CA signs with an external {@link CaSigner} (HSM/KMS)
4040
+ * rather than with `private/cakey.pem`. There is then no private key
4041
+ * file on disk, and never was one.
4042
+ */
4043
+ get hasExternalSigner() {
4044
+ return this.#signer !== void 0;
4045
+ }
4046
+ /**
4047
+ * Does `cert` certify the key this CA signs with? With the key on disk
4048
+ * that is a private-key match; with a signer there is no private key to
4049
+ * match, so compare the certified public key against the signer's own -
4050
+ * the same question, asked the only way an HSM allows.
4051
+ */
4052
+ async #certificateMatchesOurKey(certPem, certDer) {
4053
+ if (this.#signer) {
4054
+ const ours = Buffer.from(await this.#signer.getPublicKey());
4055
+ const certified = Buffer.from(new x5092.X509Certificate(certPem).publicKey.rawData);
4056
+ return ours.equals(certified);
4057
+ }
4058
+ return certificateMatchesPrivateKey(certDer, await this.getPrivateKey());
4059
+ }
4060
+ /** Reject the key-file-only operations up front, rather than failing on a missing file later. */
4061
+ #assertKeyIsOnDisk(what) {
4062
+ if (this.#signer) {
4063
+ throw new Error(
4064
+ `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.`
4065
+ );
4066
+ }
4067
+ }
4068
+ /**
4069
+ * @internal The key every signing operation goes through. An injected
4070
+ * {@link CaSigner} is returned as-is; otherwise the on-disk key is read
4071
+ * (and decrypted) and handed over as a plain `CryptoKey`. Both are
4072
+ * accepted by the `node-opcua-crypto` signing primitives, so callers
4073
+ * never branch on which one they got.
4074
+ */
4075
+ async _getSigningKey() {
4076
+ if (this.#signer) {
4077
+ return this.#signer;
4078
+ }
4079
+ return privateKeyToCryptoKey2(await this.getPrivateKey());
3348
4080
  }
3349
4081
  /**
3350
4082
  * Enable, disable, or rotate the passphrase protecting `private/cakey.pem`
@@ -3353,18 +4085,19 @@ var init_certificate_authority = __esm({
3353
4085
  * passphrase to continue using it.
3354
4086
  */
3355
4087
  async reencryptPrivateKey(oldPassphrase, newPassphrase) {
4088
+ this.#assertKeyIsOnDisk("reencryptPrivateKey");
3356
4089
  const oldPass = await resolvePrivateKeyPassphrase(oldPassphrase);
3357
4090
  const newPass = await resolvePrivateKeyPassphrase(newPassphrase);
3358
- const key = readPrivateKey2(this.privateKey, oldPass);
4091
+ const key = readPrivateKey3(this.privateKey, oldPass);
3359
4092
  await this.#rewritePrivateKeyFile(key, newPass);
3360
4093
  }
3361
4094
  async #rewritePrivateKeyFile(privateKey, passphrase) {
3362
4095
  const tmpFilename = `${this.privateKey}.${process.pid}-${Date.now()}.tmp`;
3363
4096
  try {
3364
4097
  await writePrivateKeyFile2(tmpFilename, privateKey, { passphrase });
3365
- await fs10.promises.rename(tmpFilename, this.privateKey);
4098
+ await fs12.promises.rename(tmpFilename, this.privateKey);
3366
4099
  } finally {
3367
- await fs10.promises.rm(tmpFilename, { force: true });
4100
+ await fs12.promises.rm(tmpFilename, { force: true });
3368
4101
  }
3369
4102
  }
3370
4103
  /** @internal resolve the configured passphrase, at most once per instance */
@@ -3375,10 +4108,6 @@ var init_certificate_authority = __esm({
3375
4108
  }
3376
4109
  return this.#resolvedPassphrase;
3377
4110
  }
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
4111
  /**
3383
4112
  * @internal On an existing key: encrypt it in place if a passphrase is
3384
4113
  * configured and it is still plaintext (secure by default: the option
@@ -3387,16 +4116,58 @@ var init_certificate_authority = __esm({
3387
4116
  * first signing operation.
3388
4117
  */
3389
4118
  async _ensurePrivateKeyProtection() {
3390
- if (!fs10.existsSync(this.privateKey)) {
4119
+ if (this.#signer || !fs12.existsSync(this.privateKey)) {
3391
4120
  return;
3392
4121
  }
3393
4122
  if (this.#privateKeyPassphrase !== void 0 && !isEncryptedPrivateKeyFile(this.privateKey)) {
3394
4123
  warningLog("CertificateAuthority: private key is plaintext but a passphrase is configured; encrypting it in place");
3395
- const plaintextKey = readPrivateKey2(this.privateKey);
4124
+ const plaintextKey = readPrivateKey3(this.privateKey);
3396
4125
  await this.#rewritePrivateKeyFile(plaintextKey, await this._privateKeyPassphrase());
3397
4126
  }
3398
4127
  await this.getPrivateKey();
3399
4128
  }
4129
+ /**
4130
+ * Acquire a file-based lock on this CA's directory for the duration of
4131
+ * `action` — serializes every operation that mutates the certificate
4132
+ * database (`index.txt`, `serial`, `crlnumber`, `certs/`,
4133
+ * `crl/revocation_list.*`) or the CA's own key/certificate, so
4134
+ * concurrent calls on one `CertificateAuthority` (or two instances
4135
+ * pointed at the same directory, in this or another process) cannot
4136
+ * interleave. Mirrors `CertificateManager.withLock2`.
4137
+ *
4138
+ * Only ever call this from a top-level public operation — nested calls
4139
+ * on the same instance would deadlock, since the underlying file lock
4140
+ * is not reentrant.
4141
+ *
4142
+ * The wait is bounded: a waiter gives up (throws) after
4143
+ * `CA_LOCK_MAX_WAIT_MS`. Without a bound, a holder whose openssl child
4144
+ * hangs would block every CA operation in every process forever and
4145
+ * silently — the lock library's keepalive keeps refreshing the lock
4146
+ * file's mtime as long as the holder process is alive, so stale-lock
4147
+ * recovery never fires for a live-but-stuck holder. A dead holder's
4148
+ * lock goes stale (default 2 minutes) and is taken over well within
4149
+ * this bound.
4150
+ */
4151
+ async #withCaLock(action) {
4152
+ const lockFileName = path9.join(this.rootDir, ".ca.lock");
4153
+ return withLock2(
4154
+ {
4155
+ fileToLock: lockFileName,
4156
+ retries: { forever: true, maxRetryTime: CA_LOCK_MAX_WAIT_MS, ...CA_LOCK_RETRY }
4157
+ },
4158
+ action
4159
+ );
4160
+ }
4161
+ /**
4162
+ * @internal Run `action` under this CA's directory lock. For the one
4163
+ * legitimate cross-instance use: a subordinate CA's bootstrap signs its
4164
+ * certificate with `openssl x509 -CAserial`, which read-increment-writes
4165
+ * THIS issuer's `serial` file, and must therefore hold this issuer's
4166
+ * lock (ordering is always subordinate -> issuer, so no cycle).
4167
+ */
4168
+ async _withCaDirectoryLock(action) {
4169
+ return this.#withCaLock(action);
4170
+ }
3400
4171
  /** Path to the CA certificate in PEM format (`public/cacert.pem`). */
3401
4172
  get caCertificate() {
3402
4173
  return makePath(this.rootDir, "./public/cacert.pem");
@@ -3444,8 +4215,8 @@ var init_certificate_authority = __esm({
3444
4215
  * (call {@link initialize} first).
3445
4216
  */
3446
4217
  getCACertificateDER() {
3447
- const pem = readCertificatePEM(this.caCertificate);
3448
- return convertPEMtoDER(pem);
4218
+ const pem = readCertificatePEM2(this.caCertificate);
4219
+ return convertPEMtoDER2(pem);
3449
4220
  }
3450
4221
  /**
3451
4222
  * Return the CA certificate as a PEM-encoded string.
@@ -3454,7 +4225,7 @@ var init_certificate_authority = __esm({
3454
4225
  * (call {@link initialize} first).
3455
4226
  */
3456
4227
  getCACertificatePEM() {
3457
- const raw = readCertificatePEM(this.caCertificate);
4228
+ const raw = readCertificatePEM2(this.caCertificate);
3458
4229
  const beginMarker = "-----BEGIN CERTIFICATE-----";
3459
4230
  const idx = raw.indexOf(beginMarker);
3460
4231
  if (idx > 0) {
@@ -3470,10 +4241,10 @@ var init_certificate_authority = __esm({
3470
4241
  */
3471
4242
  getCRLDER() {
3472
4243
  const crlPath = this.revocationListDER;
3473
- if (!fs10.existsSync(crlPath)) {
4244
+ if (!fs12.existsSync(crlPath)) {
3474
4245
  return Buffer.alloc(0);
3475
4246
  }
3476
- return fs10.readFileSync(crlPath);
4247
+ return fs12.readFileSync(crlPath);
3477
4248
  }
3478
4249
  /**
3479
4250
  * Return the current Certificate Revocation List as a
@@ -3483,10 +4254,10 @@ var init_certificate_authority = __esm({
3483
4254
  */
3484
4255
  getCRLPEM() {
3485
4256
  const crlPath = this.revocationList;
3486
- if (!fs10.existsSync(crlPath)) {
4257
+ if (!fs12.existsSync(crlPath)) {
3487
4258
  return "";
3488
4259
  }
3489
- const raw = fs10.readFileSync(crlPath, "utf-8");
4260
+ const raw = fs12.readFileSync(crlPath, "utf-8");
3490
4261
  const beginMarker = "-----BEGIN X509 CRL-----";
3491
4262
  const idx = raw.indexOf(beginMarker);
3492
4263
  if (idx > 0) {
@@ -3505,14 +4276,14 @@ var init_certificate_authority = __esm({
3505
4276
  * expiry date, and (for revoked certs) the revocation date.
3506
4277
  */
3507
4278
  getIssuedCertificates() {
3508
- return this._parseIndexTxt();
4279
+ return this.#db.readIndex();
3509
4280
  }
3510
4281
  /**
3511
4282
  * Return the total number of certificates recorded in
3512
4283
  * `index.txt`.
3513
4284
  */
3514
4285
  getIssuedCertificateCount() {
3515
- return this._parseIndexTxt().length;
4286
+ return this.#db.readIndex().length;
3516
4287
  }
3517
4288
  /**
3518
4289
  * Return the status of a certificate by its serial number.
@@ -3522,9 +4293,7 @@ var init_certificate_authority = __esm({
3522
4293
  * `undefined` if not found
3523
4294
  */
3524
4295
  getCertificateStatus(serial) {
3525
- const upper = serial.toUpperCase();
3526
- const record = this._parseIndexTxt().find((r) => r.serial.toUpperCase() === upper);
3527
- return record?.status;
4296
+ return this.#db.findBySerial(serial)?.status;
3528
4297
  }
3529
4298
  /**
3530
4299
  * Read a specific issued certificate by serial number and
@@ -3537,82 +4306,13 @@ var init_certificate_authority = __esm({
3537
4306
  * @returns the DER buffer, or `undefined` if not found
3538
4307
  */
3539
4308
  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);
4309
+ return this.#db.getCertificateBySerial(serial);
3547
4310
  }
3548
4311
  /**
3549
4312
  * Path to the OpenSSL certificate database file.
3550
4313
  */
3551
4314
  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;
4315
+ return this.#db.indexFile;
3616
4316
  }
3617
4317
  // ---------------------------------------------------------------
3618
4318
  // Buffer-based CA operations (US-058)
@@ -3634,12 +4334,12 @@ var init_certificate_authority = __esm({
3634
4334
  * @returns the signed certificate as a DER-encoded buffer
3635
4335
  */
3636
4336
  async signCertificateRequestFromDER(csrDer, options) {
3637
- const tmpDir = await fs10.promises.mkdtemp(path6.join(os4.tmpdir(), "pki-sign-"));
4337
+ const tmpDir = await fs12.promises.mkdtemp(path9.join(os4.tmpdir(), "pki-sign-"));
3638
4338
  try {
3639
- const csrFile = path6.join(tmpDir, "request.csr");
3640
- const certFile = path6.join(tmpDir, "certificate.pem");
4339
+ const csrFile = path9.join(tmpDir, "request.csr");
4340
+ const certFile = path9.join(tmpDir, "certificate.pem");
3641
4341
  const csrPem = toPem2(csrDer, "CERTIFICATE REQUEST");
3642
- await fs10.promises.writeFile(csrFile, csrPem, "utf-8");
4342
+ await fs12.promises.writeFile(csrFile, csrPem, "utf-8");
3643
4343
  const signingParams = {};
3644
4344
  if (options?.validityMs !== void 0) signingParams.validityMs = options.validityMs;
3645
4345
  else signingParams.validity = options?.validity ?? 365;
@@ -3649,10 +4349,10 @@ var init_certificate_authority = __esm({
3649
4349
  if (options?.applicationUri) signingParams.applicationUri = options.applicationUri;
3650
4350
  if (options?.subject) signingParams.subject = options.subject;
3651
4351
  await this.signCertificateRequest(certFile, csrFile, signingParams);
3652
- const certPem = readCertificatePEM(certFile);
3653
- return convertPEMtoDER(certPem);
4352
+ const certPem = readCertificatePEM2(certFile);
4353
+ return convertPEMtoDER2(certPem);
3654
4354
  } finally {
3655
- await fs10.promises.rm(tmpDir, {
4355
+ await fs12.promises.rm(tmpDir, {
3656
4356
  recursive: true,
3657
4357
  force: true
3658
4358
  });
@@ -3702,27 +4402,35 @@ var init_certificate_authority = __esm({
3702
4402
  * @returns `{ certificateDer, privateKey }` — certificate as DER,
3703
4403
  * private key as a branded `PrivateKey` buffer
3704
4404
  */
4405
+ /**
4406
+ * An ephemeral key and a CSR for it, written into `tmpDir`. Both
4407
+ * `generateKeyPairAndSign*` methods need exactly this, and neither
4408
+ * needs a subprocess for it: the key comes from node's crypto and the
4409
+ * request is built and self-signed in process, so no `openssl.cnf` has
4410
+ * to be rendered either.
4411
+ */
4412
+ async #createEphemeralKeyAndCsr(tmpDir, keySize, options) {
4413
+ const privateKeyFile = path9.join(tmpDir, "private_key.pem");
4414
+ await generatePrivateKeyFile2(privateKeyFile, keySize);
4415
+ const { csr } = await createCertificateSigningRequest3({
4416
+ privateKey: await privateKeyToCryptoKey2(readPrivateKey3(privateKeyFile)),
4417
+ subject: options.subject ? new Subject6(options.subject).toString() : void 0,
4418
+ applicationUri: options.applicationUri,
4419
+ dns: options.dns ?? [],
4420
+ ip: options.ip ?? [],
4421
+ purpose: CertificatePurpose3.ForApplication
4422
+ });
4423
+ const csrFile = path9.join(tmpDir, "request.csr");
4424
+ await fs12.promises.writeFile(csrFile, csr);
4425
+ return { privateKeyFile, csrFile };
4426
+ }
3705
4427
  async generateKeyPairAndSignDER(options) {
3706
4428
  const keySize = options.keySize ?? 2048;
3707
4429
  const startDate = options.startDate ?? /* @__PURE__ */ new Date();
3708
- const tmpDir = await fs10.promises.mkdtemp(path6.join(os4.tmpdir(), "pki-keygen-"));
4430
+ const tmpDir = await fs12.promises.mkdtemp(path9.join(os4.tmpdir(), "pki-keygen-"));
3709
4431
  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");
4432
+ const { privateKeyFile, csrFile } = await this.#createEphemeralKeyAndCsr(tmpDir, keySize, options);
4433
+ const certFile = path9.join(tmpDir, "certificate.pem");
3726
4434
  const signingParams = {
3727
4435
  applicationUri: options.applicationUri,
3728
4436
  dns: options.dns,
@@ -3732,12 +4440,12 @@ var init_certificate_authority = __esm({
3732
4440
  if (options.validityMs !== void 0) signingParams.validityMs = options.validityMs;
3733
4441
  else signingParams.validity = options.validity ?? 365;
3734
4442
  await this.signCertificateRequest(certFile, csrFile, signingParams);
3735
- const certPem = readCertificatePEM(certFile);
3736
- const certificateDer = convertPEMtoDER(certPem);
3737
- const privateKey = readPrivateKey2(privateKeyFile);
4443
+ const certPem = readCertificatePEM2(certFile);
4444
+ const certificateDer = convertPEMtoDER2(certPem);
4445
+ const privateKey = readPrivateKey3(privateKeyFile);
3738
4446
  return { certificateDer, privateKey };
3739
4447
  } finally {
3740
- await fs10.promises.rm(tmpDir, {
4448
+ await fs12.promises.rm(tmpDir, {
3741
4449
  recursive: true,
3742
4450
  force: true
3743
4451
  });
@@ -3758,24 +4466,10 @@ var init_certificate_authority = __esm({
3758
4466
  const keySize = options.keySize ?? 2048;
3759
4467
  const startDate = options.startDate ?? /* @__PURE__ */ new Date();
3760
4468
  const passphrase = options.passphrase ?? "";
3761
- const tmpDir = await fs10.promises.mkdtemp(path6.join(os4.tmpdir(), "pki-keygen-pfx-"));
4469
+ const tmpDir = await fs12.promises.mkdtemp(path9.join(os4.tmpdir(), "pki-keygen-pfx-"));
3762
4470
  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");
4471
+ const { privateKeyFile, csrFile } = await this.#createEphemeralKeyAndCsr(tmpDir, keySize, options);
4472
+ const certFile = path9.join(tmpDir, "certificate.pem");
3779
4473
  const signingParams = {
3780
4474
  applicationUri: options.applicationUri,
3781
4475
  dns: options.dns,
@@ -3785,17 +4479,20 @@ var init_certificate_authority = __esm({
3785
4479
  if (options.validityMs !== void 0) signingParams.validityMs = options.validityMs;
3786
4480
  else signingParams.validity = options.validity ?? 365;
3787
4481
  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]
4482
+ const blocks = (await fs12.promises.readFile(certFile, "utf-8")).match(
4483
+ /-----BEGIN CERTIFICATE-----[\s\S]*?-----END CERTIFICATE-----/g
4484
+ );
4485
+ if (!blocks || blocks.length === 0) {
4486
+ throw new Error(`generateKeyPairAndSignPFX: no certificate was produced in ${certFile}`);
4487
+ }
4488
+ return await createPfx({
4489
+ certificate: convertPEMtoDER2(blocks[0]),
4490
+ certificateChain: blocks.slice(1).map(convertPEMtoDER2),
4491
+ privateKey: readPrivateKey3(privateKeyFile),
4492
+ password: passphrase
3795
4493
  });
3796
- return await fs10.promises.readFile(pfxFile);
3797
4494
  } finally {
3798
- await fs10.promises.rm(tmpDir, {
4495
+ await fs12.promises.rm(tmpDir, {
3799
4496
  recursive: true,
3800
4497
  force: true
3801
4498
  });
@@ -3816,8 +4513,8 @@ var init_certificate_authority = __esm({
3816
4513
  async revokeCertificateDER(certDer, reason) {
3817
4514
  const info = exploreCertificate2(certDer);
3818
4515
  const serial = info.tbsCertificate.serialNumber.replace(/:/g, "").toUpperCase();
3819
- const storedCertFile = path6.join(this.rootDir, "certs", `${serial}.pem`);
3820
- if (!fs10.existsSync(storedCertFile)) {
4516
+ const storedCertFile = path9.join(this.rootDir, "certs", `${serial}.pem`);
4517
+ if (!fs12.existsSync(storedCertFile)) {
3821
4518
  throw new Error(`Cannot revoke: no stored certificate found for serial ${serial} at ${storedCertFile}`);
3822
4519
  }
3823
4520
  await this.revokeCertificate(storedCertFile, {
@@ -3830,7 +4527,69 @@ var init_certificate_authority = __esm({
3830
4527
  * already exist.
3831
4528
  */
3832
4529
  async initialize() {
3833
- await construct_CertificateAuthority(this);
4530
+ mkdirRecursiveSync(path9.resolve(this.rootDir));
4531
+ await this.#withCaLock(() => this.#bootstrap());
4532
+ }
4533
+ /**
4534
+ * @internal Shared (backend-agnostic) part of `initialize()`: directory
4535
+ * layout, default database files, the "already initialized" / "partial
4536
+ * init" checks, and the openssl config file — then delegates CSR
4537
+ * generation, CA-certificate signing, and the initial CRL to
4538
+ * {@link CaBackend.bootstrap}. Must be called under {@link #withCaLock}.
4539
+ */
4540
+ async #bootstrap() {
4541
+ const caRootDir = path9.resolve(this.rootDir);
4542
+ mkdirRecursiveSync(caRootDir);
4543
+ ensurePrivateDirectory(path9.join(caRootDir, "private"));
4544
+ mkdirRecursiveSync(path9.join(caRootDir, "public"));
4545
+ mkdirRecursiveSync(path9.join(caRootDir, "certs"));
4546
+ mkdirRecursiveSync(path9.join(caRootDir, "crl"));
4547
+ mkdirRecursiveSync(path9.join(caRootDir, "conf"));
4548
+ const serial = path9.join(caRootDir, "serial");
4549
+ if (!fs12.existsSync(serial)) {
4550
+ await fs12.promises.writeFile(serial, "1000");
4551
+ }
4552
+ const crlNumber = path9.join(caRootDir, "crlnumber");
4553
+ if (!fs12.existsSync(crlNumber)) {
4554
+ await fs12.promises.writeFile(crlNumber, "1000");
4555
+ }
4556
+ const indexFile = path9.join(caRootDir, "index.txt");
4557
+ if (!fs12.existsSync(indexFile)) {
4558
+ await fs12.promises.writeFile(indexFile, "");
4559
+ }
4560
+ const signerBacked = this.hasExternalSigner;
4561
+ const caKeyExists = signerBacked || fs12.existsSync(path9.join(caRootDir, "private/cakey.pem"));
4562
+ const caCertExists = fs12.existsSync(path9.join(caRootDir, "public/cacert.pem"));
4563
+ if (caKeyExists && caCertExists && !config3.forceCA) {
4564
+ if (!signerBacked) {
4565
+ restrictPrivateFilePermissions(path9.join(caRootDir, "private/cakey.pem"), 384);
4566
+ await this._ensurePrivateKeyProtection();
4567
+ }
4568
+ debugLog("CA private key and certificate already exist ... skipping");
4569
+ return;
4570
+ }
4571
+ if (!signerBacked && caKeyExists && !caCertExists) {
4572
+ debugLog("CA private key exists but cacert.pem is missing \u2014 rebuilding CA");
4573
+ fs12.unlinkSync(path9.join(caRootDir, "private/cakey.pem"));
4574
+ const staleCsr = path9.join(caRootDir, "private/cakey.csr");
4575
+ if (fs12.existsSync(staleCsr)) {
4576
+ fs12.unlinkSync(staleCsr);
4577
+ }
4578
+ }
4579
+ displayTitle("Create Certificate Authority (CA)");
4580
+ const indexFileAttr = path9.join(caRootDir, "index.txt.attr");
4581
+ if (!fs12.existsSync(indexFileAttr)) {
4582
+ await fs12.promises.writeFile(indexFileAttr, "unique_subject = no");
4583
+ }
4584
+ const caConfigFile = this.configFile;
4585
+ await fs12.promises.writeFile(caConfigFile, renderCaConfig(caRootDir));
4586
+ if (!signerBacked) {
4587
+ const privateKeyFilename = path9.join(caRootDir, "private/cakey.pem");
4588
+ displayTitle(`Generate the CA private Key - ${this.keySize}`);
4589
+ await generatePrivateKeyFile2(privateKeyFilename, this.keySize, { passphrase: await this._privateKeyPassphrase() });
4590
+ restrictPrivateFilePermissions(privateKeyFilename, 384);
4591
+ }
4592
+ await this.#backend.bootstrap(this);
3834
4593
  }
3835
4594
  /**
3836
4595
  * Initialize the CA directory structure and generate the
@@ -3852,21 +4611,25 @@ var init_certificate_authority = __esm({
3852
4611
  * @returns an {@link InitializeCSRResult} describing the CA state
3853
4612
  */
3854
4613
  async initializeCSR() {
3855
- const caRootDir = path6.resolve(this.rootDir);
4614
+ const caRootDir = path9.resolve(this.rootDir);
3856
4615
  mkdirRecursiveSync(caRootDir);
4616
+ return this.#withCaLock(() => this.#initializeCSRLocked(caRootDir));
4617
+ }
4618
+ async #initializeCSRLocked(caRootDir) {
3857
4619
  for (const dir of ["public", "certs", "crl", "conf"]) {
3858
- mkdirRecursiveSync(path6.join(caRootDir, dir));
4620
+ mkdirRecursiveSync(path9.join(caRootDir, dir));
3859
4621
  }
3860
- ensurePrivateDirectory(path6.join(caRootDir, "private"));
4622
+ ensurePrivateDirectory(path9.join(caRootDir, "private"));
3861
4623
  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)) {
4624
+ const privateKeyFile = path9.join(caRootDir, "private/cakey.pem");
4625
+ const csrFile = path9.join(caRootDir, "private/cakey.csr");
4626
+ const keyAvailable = this.hasExternalSigner || fs12.existsSync(privateKeyFile);
4627
+ if (!this.hasExternalSigner && fs12.existsSync(privateKeyFile)) {
3865
4628
  restrictPrivateFilePermissions(privateKeyFile, 384);
3866
4629
  await this._ensurePrivateKeyProtection();
3867
4630
  }
3868
- if (fs10.existsSync(caCertFile)) {
3869
- const certDer = convertPEMtoDER(readCertificatePEM(caCertFile));
4631
+ if (fs12.existsSync(caCertFile)) {
4632
+ const certDer = convertPEMtoDER2(readCertificatePEM2(caCertFile));
3870
4633
  const certInfo = exploreCertificate2(certDer);
3871
4634
  const notAfter = certInfo.tbsCertificate.validity.notAfter;
3872
4635
  if (notAfter.getTime() < Date.now()) {
@@ -3877,29 +4640,29 @@ var init_certificate_authority = __esm({
3877
4640
  debugLog("CA certificate already exists and is valid \u2014 ready");
3878
4641
  return { status: "ready" };
3879
4642
  }
3880
- if (fs10.existsSync(privateKeyFile) && fs10.existsSync(csrFile)) {
4643
+ if (keyAvailable && fs12.existsSync(csrFile)) {
3881
4644
  debugLog("CA key + CSR already exist \u2014 pending external signing");
3882
4645
  return { status: "pending", csrPath: csrFile };
3883
4646
  }
3884
- const serial = path6.join(caRootDir, "serial");
3885
- if (!fs10.existsSync(serial)) {
3886
- await fs10.promises.writeFile(serial, "1000");
4647
+ const serial = path9.join(caRootDir, "serial");
4648
+ if (!fs12.existsSync(serial)) {
4649
+ await fs12.promises.writeFile(serial, "1000");
3887
4650
  }
3888
- const crlNumber = path6.join(caRootDir, "crlnumber");
3889
- if (!fs10.existsSync(crlNumber)) {
3890
- await fs10.promises.writeFile(crlNumber, "1000");
4651
+ const crlNumber = path9.join(caRootDir, "crlnumber");
4652
+ if (!fs12.existsSync(crlNumber)) {
4653
+ await fs12.promises.writeFile(crlNumber, "1000");
3891
4654
  }
3892
- const indexFile = path6.join(caRootDir, "index.txt");
3893
- if (!fs10.existsSync(indexFile)) {
3894
- await fs10.promises.writeFile(indexFile, "");
4655
+ const indexFile = path9.join(caRootDir, "index.txt");
4656
+ if (!fs12.existsSync(indexFile)) {
4657
+ await fs12.promises.writeFile(indexFile, "");
3895
4658
  }
3896
- const indexFileAttr = path6.join(caRootDir, "index.txt.attr");
3897
- if (!fs10.existsSync(indexFileAttr)) {
3898
- await fs10.promises.writeFile(indexFileAttr, "unique_subject = no");
4659
+ const indexFileAttr = path9.join(caRootDir, "index.txt.attr");
4660
+ if (!fs12.existsSync(indexFileAttr)) {
4661
+ await fs12.promises.writeFile(indexFileAttr, "unique_subject = no");
3899
4662
  }
3900
4663
  const caConfigFile = this.configFile;
3901
- await fs10.promises.writeFile(caConfigFile, renderCaConfig(caRootDir));
3902
- if (!fs10.existsSync(privateKeyFile)) {
4664
+ await fs12.promises.writeFile(caConfigFile, renderCaConfig(caRootDir));
4665
+ if (!keyAvailable) {
3903
4666
  await generatePrivateKeyFile2(privateKeyFile, this.keySize, { passphrase: await this._privateKeyPassphrase() });
3904
4667
  restrictPrivateFilePermissions(privateKeyFile, 384);
3905
4668
  }
@@ -3920,53 +4683,34 @@ var init_certificate_authority = __esm({
3920
4683
  * renewal is needed, `"ready"` if the cert is still valid
3921
4684
  */
3922
4685
  async renewCSR(thresholdDays = 30) {
3923
- const caRootDir = path6.resolve(this.rootDir);
4686
+ const caRootDir = path9.resolve(this.rootDir);
3924
4687
  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)) {
4688
+ const privateKeyFile = path9.join(caRootDir, "private/cakey.pem");
4689
+ const csrFile = path9.join(caRootDir, "private/cakey.csr");
4690
+ if (!fs12.existsSync(caCertFile)) {
3928
4691
  return this.initializeCSR();
3929
4692
  }
3930
- const certDer = convertPEMtoDER(readCertificatePEM(caCertFile));
4693
+ const certDer = convertPEMtoDER2(readCertificatePEM2(caCertFile));
3931
4694
  const certInfo = exploreCertificate2(certDer);
3932
4695
  const notAfter = certInfo.tbsCertificate.validity.notAfter;
3933
4696
  const thresholdMs = thresholdDays * 24 * 60 * 60 * 1e3;
3934
4697
  if (notAfter.getTime() - Date.now() < thresholdMs) {
3935
4698
  debugLog(`CA certificate expires within ${thresholdDays} days \u2014 generating renewal CSR`);
3936
- await this._generateCSR(caRootDir, privateKeyFile, csrFile);
4699
+ await this.#withCaLock(() => this._generateCSR(caRootDir, privateKeyFile, csrFile));
3937
4700
  return { status: "expired", csrPath: csrFile, expiryDate: notAfter };
3938
4701
  }
3939
4702
  return { status: "ready" };
3940
4703
  }
3941
4704
  /**
3942
4705
  * Generate a CSR using the existing private key.
4706
+ * Must be called under {@link #withCaLock} — the lock is taken by the
4707
+ * public callers (`initializeCSR`, `renewCSR`), not here, so that
4708
+ * `initializeCSR` can hold one lock across key generation AND CSR
4709
+ * generation without the non-reentrant file lock deadlocking.
3943
4710
  * @internal
3944
4711
  */
3945
4712
  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
- );
4713
+ await this.#backend.generateCaCsr(this, caRootDir, privateKeyFile, csrFile);
3970
4714
  }
3971
4715
  /**
3972
4716
  * Install an externally-signed CA certificate and generate
@@ -3985,43 +4729,41 @@ var init_certificate_authority = __esm({
3985
4729
  * `status: "success"` or `status: "error"` and a `reason`
3986
4730
  */
3987
4731
  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]}
4732
+ return this.#withCaLock(async () => {
4733
+ const caCertFile = this.caCertificate;
4734
+ const fullPem = await fs12.promises.readFile(signedCertFile, "utf8");
4735
+ const pemBlocks = fullPem.match(/-----BEGIN CERTIFICATE-----[\s\S]*?-----END CERTIFICATE-----/g);
4736
+ if (!pemBlocks || pemBlocks.length === 0) {
4737
+ return {
4738
+ status: "error",
4739
+ reason: "no_certificate_found",
4740
+ message: "The provided file does not contain any PEM-encoded certificate."
4741
+ };
4742
+ }
4743
+ const certDer = convertPEMtoDER2(pemBlocks[0]);
4744
+ if (!await this.#certificateMatchesOurKey(pemBlocks[0], certDer)) {
4745
+ return {
4746
+ status: "error",
4747
+ reason: "certificate_key_mismatch",
4748
+ message: "The provided certificate does not match the CA private key. Ensure the certificate was signed from the CSR generated by initializeCSR()."
4749
+ };
4750
+ }
4751
+ await fs12.promises.writeFile(caCertFile, `${pemBlocks[0]}
4009
4752
  `);
4010
- const issuerChainFile = this.issuerCertificateChain;
4011
- if (pemBlocks.length > 1) {
4012
- const issuerPem = `${pemBlocks.slice(1).join("\n")}
4753
+ const issuerChainFile = this.issuerCertificateChain;
4754
+ if (pemBlocks.length > 1) {
4755
+ const issuerPem = `${pemBlocks.slice(1).join("\n")}
4013
4756
  `;
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);
4757
+ await fs12.promises.writeFile(issuerChainFile, issuerPem);
4758
+ debugLog(`Stored ${pemBlocks.length - 1} issuer certificate(s) in issuer_chain.pem`);
4759
+ } else {
4760
+ if (fs12.existsSync(issuerChainFile)) {
4761
+ await fs12.promises.unlink(issuerChainFile);
4762
+ }
4019
4763
  }
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" };
4764
+ await this.#backend.regenerateCrl(this);
4765
+ return { status: "success" };
4766
+ });
4025
4767
  }
4026
4768
  /**
4027
4769
  * Sign a CSR with CA extensions (`v3_ca`), producing a
@@ -4037,39 +4779,11 @@ var init_certificate_authority = __esm({
4037
4779
  * @param params - signing parameters
4038
4780
  */
4039
4781
  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);
4782
+ await this.#withCaLock(async () => {
4783
+ const validity = params.validity ?? 3650;
4784
+ await this.#backend.signSubordinateCsr(this, csrFile, certFile, validity);
4785
+ await this.constructCertificateChain(certFile);
4786
+ });
4073
4787
  }
4074
4788
  /**
4075
4789
  * Rebuild the combined CA certificate + CRL file.
@@ -4080,13 +4794,13 @@ var init_certificate_authority = __esm({
4080
4794
  */
4081
4795
  async constructCACertificateWithCRL() {
4082
4796
  const cacertWithCRL = this.caCertificateWithCrl;
4083
- if (fs10.existsSync(this.revocationList)) {
4084
- await fs10.promises.writeFile(
4797
+ if (fs12.existsSync(this.revocationList)) {
4798
+ await fs12.promises.writeFile(
4085
4799
  cacertWithCRL,
4086
- fs10.readFileSync(this.caCertificate, "utf8") + fs10.readFileSync(this.revocationList, "utf8")
4800
+ fs12.readFileSync(this.caCertificate, "utf8") + fs12.readFileSync(this.revocationList, "utf8")
4087
4801
  );
4088
4802
  } else {
4089
- await fs10.promises.writeFile(cacertWithCRL, fs10.readFileSync(this.caCertificate));
4803
+ await fs12.promises.writeFile(cacertWithCRL, fs12.readFileSync(this.caCertificate));
4090
4804
  }
4091
4805
  }
4092
4806
  /**
@@ -4096,15 +4810,15 @@ var init_certificate_authority = __esm({
4096
4810
  * @param certificate - path to the certificate file to extend
4097
4811
  */
4098
4812
  async constructCertificateChain(certificate) {
4099
- assert10(fs10.existsSync(certificate));
4100
- assert10(fs10.existsSync(this.caCertificate));
4813
+ assert9(fs12.existsSync(certificate));
4814
+ assert9(fs12.existsSync(this.caCertificate));
4101
4815
  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");
4816
+ let chain = await fs12.promises.readFile(certificate, "utf8");
4817
+ chain += await fs12.promises.readFile(this.caCertificate, "utf8");
4818
+ if (fs12.existsSync(this.issuerCertificateChain)) {
4819
+ chain += await fs12.promises.readFile(this.issuerCertificateChain, "utf8");
4106
4820
  }
4107
- await fs10.promises.writeFile(certificate, chain);
4821
+ await fs12.promises.writeFile(certificate, chain);
4108
4822
  }
4109
4823
  /**
4110
4824
  * Create a self-signed certificate using OpenSSL.
@@ -4114,52 +4828,30 @@ var init_certificate_authority = __esm({
4114
4828
  * @param params - certificate parameters (subject, validity, SANs)
4115
4829
  */
4116
4830
  async createSelfSignedCertificate(certificateFile, privateKey, params) {
4117
- assert10(typeof privateKey === "string");
4118
- assert10(fs10.existsSync(privateKey));
4831
+ assert9(typeof privateKey === "string");
4832
+ assert9(fs12.existsSync(privateKey));
4119
4833
  if (!certificateFileExist(certificateFile)) {
4120
4834
  return;
4121
4835
  }
4122
4836
  adjustDate(params);
4123
4837
  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);
4838
+ params.dns = params.dns || [];
4839
+ params.ip = params.ip || [];
4840
+ await this.#withCaLock(async () => {
4841
+ await this.#backend.createSelfSignedCertificate(this, certificateFile, privateKey, params);
4842
+ });
4843
+ }
4844
+ /**
4845
+ * Regenerate `crl/revocation_list.{crl,der}` from the current database
4846
+ * state, without changing any certificate's status. Normally
4847
+ * unnecessary `revokeCertificate` already regenerates the CRL as
4848
+ * part of revoking — but useful to force a refresh (e.g. after
4849
+ * switching a CA's `backend` between `"openssl"` and `"native"`).
4850
+ */
4851
+ async regenerateCrl() {
4852
+ await this.#withCaLock(async () => {
4853
+ await this.#backend.regenerateCrl(this);
4854
+ });
4163
4855
  }
4164
4856
  /**
4165
4857
  * Revoke a certificate and regenerate the CRL.
@@ -4180,49 +4872,12 @@ var init_certificate_authority = __esm({
4180
4872
  "certificateHold",
4181
4873
  "removeFromCRL"
4182
4874
  ];
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
4875
  const reason = params.reason || "keyCompromise";
4193
- assert10(crlReasons.indexOf(reason) >= 0);
4876
+ assert9(crlReasons.indexOf(reason) >= 0);
4194
4877
  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
- );
4878
+ await this.#withCaLock(async () => {
4879
+ await this.#backend.revoke(this, certificate, reason);
4880
+ });
4226
4881
  }
4227
4882
  /**
4228
4883
  * Sign a Certificate Signing Request (CSR) with this CA.
@@ -4237,15 +4892,13 @@ var init_certificate_authority = __esm({
4237
4892
  * @returns the path to the signed certificate
4238
4893
  */
4239
4894
  async signCertificateRequest(certificate, certificateSigningRequestFilename, params1) {
4240
- await ensure_openssl_installed();
4241
- assert10(fs10.existsSync(certificateSigningRequestFilename));
4895
+ await this.#backend.preflight();
4896
+ assert9(fs12.existsSync(certificateSigningRequestFilename));
4242
4897
  if (!certificateFileExist(certificate)) {
4243
4898
  return "";
4244
4899
  }
4245
4900
  adjustDate(params1);
4246
4901
  adjustApplicationUri(params1);
4247
- processAltNames(params1);
4248
- const options = { cwd: this.rootDir };
4249
4902
  const csr = await readCertificateSigningRequest(certificateSigningRequestFilename);
4250
4903
  const csrInfo = exploreCertificateSigningRequest(csr);
4251
4904
  const applicationUri = csrInfo.extensionRequest.subjectAltName.uniformResourceIdentifier ? csrInfo.extensionRequest.subjectAltName.uniformResourceIdentifier[0] : void 0;
@@ -4255,73 +4908,122 @@ var init_certificate_authority = __esm({
4255
4908
  const dns2 = csrInfo.extensionRequest.subjectAltName.dNSName || [];
4256
4909
  let ip = csrInfo.extensionRequest.subjectAltName.iPAddress || [];
4257
4910
  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;
4911
+ const sanOverride = { applicationUri, dns: dns2, ip };
4912
+ return this.#withCaLock(async () => {
4913
+ await this.#backend.signEndEntityCsr(this, certificate, certificateSigningRequestFilename, params1, sanOverride);
4914
+ displaySubtitle("- construct CA certificate with CRL");
4915
+ await this.constructCACertificateWithCRL();
4916
+ displaySubtitle("- construct certificate chain");
4917
+ await this.constructCertificateChain(certificate);
4918
+ displaySubtitle("- verify certificate against the root CA");
4919
+ await this.verifyCertificate(certificate);
4920
+ return certificate;
4921
+ });
4295
4922
  }
4296
4923
  /**
4297
- * Verify a certificate against this CA.
4924
+ * Check that `certificate` really was signed by this CA, and throw if it
4925
+ * was not.
4926
+ *
4927
+ * This used to do nothing: `openssl verify` crashes on Windows, so the
4928
+ * check was left as a placeholder. It no longer needs a subprocess -
4929
+ * `verifyCertificateSignature` does it in pure JS, the same way
4930
+ * {@link CertificateManager} already validates a chain - so the check
4931
+ * that `signCertificateRequest` always claimed to perform now actually
4932
+ * happens on every issuance, whichever backend did the signing.
4933
+ *
4934
+ * Only the leading certificate is examined: the file may be a chain,
4935
+ * and the rest of it is this CA's own certificate and its issuers.
4298
4936
  *
4299
- * @param certificate - path to the certificate file to verify
4937
+ * @param certificate - path to the certificate (or chain) to verify
4300
4938
  */
4301
4939
  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
4940
+ const pem = await fs12.promises.readFile(certificate, "utf-8");
4941
+ const blocks = pem.match(/-----BEGIN CERTIFICATE-----[\s\S]*?-----END CERTIFICATE-----/g);
4942
+ if (!blocks || blocks.length === 0) {
4943
+ throw new Error(`verifyCertificate: ${certificate} contains no PEM-encoded certificate`);
4944
+ }
4945
+ const caCertificateDer = convertPEMtoDER2(readCertificatePEM2(this.caCertificate));
4946
+ if (!verifyCertificateSignature2(convertPEMtoDER2(blocks[0]), caCertificateDer)) {
4947
+ throw new Error(
4948
+ `verifyCertificate: ${certificate} was not signed by this certificate authority (${this.caCertificate})`
4949
+ );
4950
+ }
4951
+ }
4952
+ };
4953
+ }
4954
+ });
4955
+
4956
+ // packages/node-opcua-pki/lib/ca/certificate_authority.ts
4957
+ var CertificateAuthority;
4958
+ var init_certificate_authority = __esm({
4959
+ "packages/node-opcua-pki/lib/ca/certificate_authority.ts"() {
4960
+ "use strict";
4961
+ init_esm_shims();
4962
+ init_with_openssl();
4963
+ init_native_ca_backend();
4964
+ init_openssl_ca_backend();
4965
+ init_certificate_authority_core();
4966
+ init_certificate_authority_core();
4967
+ CertificateAuthority = class extends CertificateAuthorityCore {
4968
+ constructor(options) {
4969
+ if (options.signer && options.backend === "openssl") {
4970
+ throw new Error(
4971
+ "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
4972
  );
4311
4973
  }
4974
+ const backend = options.signer || options.backend === "native" ? new NativeCaBackend() : new OpenSslCaBackend();
4975
+ super({ ...options, backend });
4976
+ }
4977
+ /**
4978
+ * @internal `-passin env:` argv + env for an openssl call that loads
4979
+ * this CA's key (always emitted, empty when none).
4980
+ *
4981
+ * The openssl backend builds its own now; this remains so that external
4982
+ * code calling it keeps working, and lives here rather than on the core
4983
+ * because the flag means nothing to a backend that spawns nothing.
4984
+ */
4985
+ async _opensslPassin() {
4986
+ return passinArg(await this._privateKeyPassphrase());
4987
+ }
4988
+ /**
4989
+ * @internal
4990
+ * Legacy shim: publish the `CDP_URL` / `AIA_VALUE` config substitution
4991
+ * values to the shared env registry, or unset them so the matching
4992
+ * `{{#KEY}}...{{/KEY}}` blocks are stripped. Nothing in this package
4993
+ * reads the registry any more - every openssl config render receives
4994
+ * these values explicitly, from the same {@link caConfigEnvOverrides}
4995
+ * builder this delegates to, so the two cannot drift - kept only for
4996
+ * external code that renders openssl config templates against the
4997
+ * registry directly.
4998
+ *
4999
+ * It lives on this class rather than the core because it is meaningful
5000
+ * only to the openssl backend.
5001
+ */
5002
+ _wireRevocationEnvVars() {
5003
+ const overrides = caConfigEnvOverrides(this);
5004
+ if (overrides.CDP_URL) {
5005
+ setEnv("CDP_URL", overrides.CDP_URL);
5006
+ } else {
5007
+ unsetEnv("CDP_URL");
5008
+ }
5009
+ if (overrides.AIA_VALUE) {
5010
+ setEnv("AIA_VALUE", overrides.AIA_VALUE);
5011
+ } else {
5012
+ unsetEnv("AIA_VALUE");
5013
+ }
4312
5014
  }
4313
5015
  };
4314
5016
  }
4315
5017
  });
4316
5018
 
4317
5019
  // packages/node-opcua-pki/lib/ca/crypto_create_CA.ts
4318
- import assert11 from "assert";
4319
- import fs11 from "fs";
5020
+ import assert10 from "assert";
5021
+ import fs13 from "fs";
4320
5022
  import { createRequire } from "module";
4321
5023
  import os5 from "os";
4322
- import path7 from "path";
5024
+ import path10 from "path";
4323
5025
  import chalk7 from "chalk";
4324
- import { CertificatePurpose as CertificatePurpose3, generatePrivateKeyFile as generatePrivateKeyFile3, Subject as Subject5 } from "node-opcua-crypto";
5026
+ import { CertificatePurpose as CertificatePurpose4, generatePrivateKeyFile as generatePrivateKeyFile3, Subject as Subject7 } from "node-opcua-crypto";
4325
5027
  import commandLineArgs from "command-line-args";
4326
5028
  import commandLineUsage from "command-line-usage";
4327
5029
  function get_offset_date(date, nbDays) {
@@ -4329,9 +5031,9 @@ function get_offset_date(date, nbDays) {
4329
5031
  d.setDate(d.getDate() + nbDays);
4330
5032
  return d;
4331
5033
  }
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");
5034
+ async function construct_CertificateAuthority(subject) {
5035
+ assert10(typeof gLocalConfig.CAFolder === "string", "expecting a CAFolder in config");
5036
+ assert10(typeof gLocalConfig.keySize === "number", "expecting a keySize in config");
4335
5037
  if (!g_certificateAuthority) {
4336
5038
  g_certificateAuthority = new CertificateAuthority({
4337
5039
  keySize: gLocalConfig.keySize,
@@ -4342,7 +5044,7 @@ async function construct_CertificateAuthority2(subject) {
4342
5044
  }
4343
5045
  }
4344
5046
  async function construct_CertificateManager() {
4345
- assert11(typeof gLocalConfig.PKIFolder === "string", "expecting a PKIFolder in config");
5047
+ assert10(typeof gLocalConfig.PKIFolder === "string", "expecting a PKIFolder in config");
4346
5048
  if (!certificateManager) {
4347
5049
  certificateManager = new CertificateManager({
4348
5050
  keySize: gLocalConfig.keySize,
@@ -4353,35 +5055,35 @@ async function construct_CertificateManager() {
4353
5055
  }
4354
5056
  function default_template_content() {
4355
5057
  if (process.pkg?.entrypoint) {
4356
- const a = fs11.readFileSync(path7.join(__dirname, "../../bin/pki_config.example.js"), "utf8");
5058
+ const a = fs13.readFileSync(path10.join(__dirname, "../../bin/pki_config.example.js"), "utf8");
4357
5059
  return a;
4358
5060
  }
4359
5061
  function find_default_config_template() {
4360
5062
  const rootFolder = find_module_root_folder();
4361
5063
  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}`);
5064
+ let default_config_template2 = path10.join(rootFolder, "bin", configName);
5065
+ if (!fs13.existsSync(default_config_template2)) {
5066
+ default_config_template2 = path10.join(__dirname, "..", configName);
5067
+ if (!fs13.existsSync(default_config_template2)) {
5068
+ default_config_template2 = path10.join(__dirname, `../bin/${configName}`);
4367
5069
  }
4368
5070
  }
4369
5071
  return default_config_template2;
4370
5072
  }
4371
5073
  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");
5074
+ assert10(fs13.existsSync(default_config_template));
5075
+ const default_config_template_content = fs13.readFileSync(default_config_template, "utf8");
4374
5076
  return default_config_template_content;
4375
5077
  }
4376
5078
  function find_module_root_folder() {
4377
- let rootFolder = path7.join(__dirname);
5079
+ let rootFolder = path10.join(__dirname);
4378
5080
  for (let i = 0; i < 4; i++) {
4379
- if (fs11.existsSync(path7.join(rootFolder, "package.json"))) {
5081
+ if (fs13.existsSync(path10.join(rootFolder, "package.json"))) {
4380
5082
  return rootFolder;
4381
5083
  }
4382
- rootFolder = path7.join(rootFolder, "..");
5084
+ rootFolder = path10.join(rootFolder, "..");
4383
5085
  }
4384
- assert11(fs11.existsSync(path7.join(rootFolder, "package.json")), "root folder must have a package.json file");
5086
+ assert10(fs13.existsSync(path10.join(rootFolder, "package.json")), "root folder must have a package.json file");
4385
5087
  return rootFolder;
4386
5088
  }
4387
5089
  async function readConfiguration(argv) {
@@ -4406,41 +5108,41 @@ async function readConfiguration(argv) {
4406
5108
  return str;
4407
5109
  }
4408
5110
  function prepare(file) {
4409
- const tmp = path7.resolve(performSubstitution(file));
5111
+ const tmp = path10.resolve(performSubstitution(file));
4410
5112
  return makePath(tmp);
4411
5113
  }
4412
5114
  certificateDir = argv.root;
4413
- assert11(typeof certificateDir === "string");
5115
+ assert10(typeof certificateDir === "string");
4414
5116
  certificateDir = prepare(certificateDir);
4415
5117
  mkdirRecursiveSync(certificateDir);
4416
- assert11(fs11.existsSync(certificateDir));
4417
- const default_config = path7.join(certificateDir, "config.js");
4418
- if (!fs11.existsSync(default_config)) {
5118
+ assert10(fs13.existsSync(certificateDir));
5119
+ const default_config = path10.join(certificateDir, "config.js");
5120
+ if (!fs13.existsSync(default_config)) {
4419
5121
  debugLog(chalk7.yellow(" Creating default g_config file "), chalk7.cyan(default_config));
4420
5122
  const default_config_template_content = default_template_content();
4421
- fs11.writeFileSync(default_config, default_config_template_content);
5123
+ fs13.writeFileSync(default_config, default_config_template_content);
4422
5124
  } else {
4423
5125
  debugLog(chalk7.yellow(" using g_config file "), chalk7.cyan(default_config));
4424
5126
  }
4425
- if (!fs11.existsSync(default_config)) {
5127
+ if (!fs13.existsSync(default_config)) {
4426
5128
  debugLog(chalk7.redBright(" cannot find config file ", default_config));
4427
5129
  }
4428
- const defaultRandomFile = path7.join(path7.dirname(default_config), "random.rnd");
5130
+ const defaultRandomFile = path10.join(path10.dirname(default_config), "random.rnd");
4429
5131
  setEnv("RANDFILE", defaultRandomFile);
4430
5132
  const _require = createRequire(__filename);
4431
5133
  gLocalConfig = _require(default_config);
4432
- gLocalConfig.subject = new Subject5(gLocalConfig.subject || "");
5134
+ gLocalConfig.subject = new Subject7(gLocalConfig.subject || "");
4433
5135
  if (argv.subject) {
4434
- gLocalConfig.subject = new Subject5(argv.subject);
5136
+ gLocalConfig.subject = new Subject7(argv.subject);
4435
5137
  }
4436
5138
  if (!gLocalConfig.subject.commonName) {
4437
5139
  throw new Error("subject must have a Common Name");
4438
5140
  }
4439
5141
  gLocalConfig.certificateDir = certificateDir;
4440
- let CAFolder = argv.CAFolder || path7.join(certificateDir, "CA");
5142
+ let CAFolder = argv.CAFolder || path10.join(certificateDir, "CA");
4441
5143
  CAFolder = prepare(CAFolder);
4442
5144
  gLocalConfig.CAFolder = CAFolder;
4443
- gLocalConfig.PKIFolder = path7.join(gLocalConfig.certificateDir, "PKI");
5145
+ gLocalConfig.PKIFolder = path10.join(gLocalConfig.certificateDir, "PKI");
4444
5146
  if (argv.PKIFolder) {
4445
5147
  gLocalConfig.PKIFolder = prepare(argv.PKIFolder);
4446
5148
  }
@@ -4478,7 +5180,7 @@ async function readConfiguration(argv) {
4478
5180
  }
4479
5181
  }
4480
5182
  async function createDefaultCertificate(base_name, prefix, key_length, applicationUri, dev) {
4481
- assert11(key_length === 1024 || key_length === 2048 || key_length === 3072 || key_length === 4096);
5183
+ assert10(key_length === 1024 || key_length === 2048 || key_length === 3072 || key_length === 4096);
4482
5184
  const private_key_file = makePath(base_name, `${prefix}key_${key_length}.pem`);
4483
5185
  const public_key_file = makePath(base_name, `${prefix}public_key_${key_length}.pub`);
4484
5186
  const certificate_file = makePath(base_name, `${prefix}cert_${key_length}.pem`);
@@ -4498,7 +5200,7 @@ async function createDefaultCertificate(base_name, prefix, key_length, applicati
4498
5200
  }
4499
5201
  const ip = [];
4500
5202
  async function createCertificateIfNotExist(certificate, private_key, applicationUri2, startDate, validity) {
4501
- if (fs11.existsSync(certificate)) {
5203
+ if (fs13.existsSync(certificate)) {
4502
5204
  warningLog(chalk7.yellow(" certificate"), chalk7.cyan(certificate), chalk7.yellow(" already exists => skipping"));
4503
5205
  return "";
4504
5206
  } else {
@@ -4517,7 +5219,7 @@ async function createDefaultCertificate(base_name, prefix, key_length, applicati
4517
5219
  configFile,
4518
5220
  dns: dns3,
4519
5221
  ip: ip2,
4520
- purpose: CertificatePurpose3.ForApplication
5222
+ purpose: CertificatePurpose4.ForApplication
4521
5223
  };
4522
5224
  await createCertificateSigningRequestWithOpenSSL(certificateSigningRequestFile, params);
4523
5225
  return await g_certificateAuthority.signCertificateRequest(certificate, certificateSigningRequestFile, {
@@ -4541,7 +5243,7 @@ async function createDefaultCertificate(base_name, prefix, key_length, applicati
4541
5243
  await g_certificateAuthority.revokeCertificate(certificate, {});
4542
5244
  }
4543
5245
  async function createPrivateKeyIfNotExist(privateKey, keyLength) {
4544
- if (fs11.existsSync(privateKey)) {
5246
+ if (fs13.existsSync(privateKey)) {
4545
5247
  warningLog(chalk7.yellow(" privateKey"), chalk7.cyan(privateKey), chalk7.yellow(" already exists => skipping"));
4546
5248
  return;
4547
5249
  } else {
@@ -4555,14 +5257,14 @@ async function createDefaultCertificate(base_name, prefix, key_length, applicati
4555
5257
  displaySubtitle(` create Certificate ${certificate_file}`);
4556
5258
  await createCertificateIfNotExist(certificate_file, private_key_file, applicationUri, yesterday, 365);
4557
5259
  displaySubtitle(` create self signed Certificate ${self_signed_certificate_file}`);
4558
- if (fs11.existsSync(self_signed_certificate_file)) {
5260
+ if (fs13.existsSync(self_signed_certificate_file)) {
4559
5261
  return;
4560
5262
  }
4561
5263
  await createSelfSignedCertificate2(self_signed_certificate_file, private_key_file, applicationUri, yesterday, 365);
4562
5264
  if (dev) {
4563
5265
  await createCertificateIfNotExist(certificate_file_outofdate, private_key_file, applicationUri, two_years_ago, 365);
4564
5266
  await createCertificateIfNotExist(certificate_file_not_active_yet, private_key_file, applicationUri, next_year, 365);
4565
- if (!fs11.existsSync(certificate_revoked)) {
5267
+ if (!fs13.existsSync(certificate_revoked)) {
4566
5268
  const certificate = await createCertificateIfNotExist(
4567
5269
  certificate_revoked,
4568
5270
  private_key_file,
@@ -4584,9 +5286,9 @@ async function wrap(func) {
4584
5286
  }
4585
5287
  }
4586
5288
  async function create_default_certificates(dev) {
4587
- assert11(gLocalConfig);
5289
+ assert10(gLocalConfig);
4588
5290
  const base_name = gLocalConfig.certificateDir || "";
4589
- assert11(fs11.existsSync(base_name));
5291
+ assert10(fs13.existsSync(base_name));
4590
5292
  let clientURN;
4591
5293
  let serverURN;
4592
5294
  let discoveryServerURN;
@@ -4617,7 +5319,7 @@ async function create_default_certificates(dev) {
4617
5319
  });
4618
5320
  }
4619
5321
  async function createDefaultCertificates(dev) {
4620
- await construct_CertificateAuthority2("");
5322
+ await construct_CertificateAuthority("");
4621
5323
  await construct_CertificateManager();
4622
5324
  await create_default_certificates(dev);
4623
5325
  }
@@ -4683,7 +5385,7 @@ ${epilog}`
4683
5385
  }
4684
5386
  if (command === "version") {
4685
5387
  const rootFolder = find_module_root_folder();
4686
- const pkg = JSON.parse(fs11.readFileSync(path7.join(rootFolder, "package.json"), "utf-8"));
5388
+ const pkg = JSON.parse(fs13.readFileSync(path10.join(rootFolder, "package.json"), "utf-8"));
4687
5389
  console.log(pkg.version);
4688
5390
  return;
4689
5391
  }
@@ -4708,12 +5410,12 @@ ${epilog}`
4708
5410
  await readConfiguration(local_argv);
4709
5411
  if (local_argv.clean) {
4710
5412
  displayTitle("Cleaning old certificates");
4711
- assert11(gLocalConfig);
5413
+ assert10(gLocalConfig);
4712
5414
  const certificateDir = gLocalConfig.certificateDir || "";
4713
- const files = await fs11.promises.readdir(certificateDir);
5415
+ const files = await fs13.promises.readdir(certificateDir);
4714
5416
  for (const file of files) {
4715
5417
  if (file.includes(".pem") || file.includes(".pub")) {
4716
- await fs11.promises.unlink(path7.join(certificateDir, file));
5418
+ await fs13.promises.unlink(path10.join(certificateDir, file));
4717
5419
  }
4718
5420
  }
4719
5421
  mkdirRecursiveSync(certificateDir);
@@ -4734,7 +5436,7 @@ ${epilog}`
4734
5436
  await wrap(async () => {
4735
5437
  await ensure_openssl_installed();
4736
5438
  await readConfiguration(local_argv);
4737
- await construct_CertificateAuthority2(local_argv.subject);
5439
+ await construct_CertificateAuthority(local_argv.subject);
4738
5440
  });
4739
5441
  return;
4740
5442
  }
@@ -4803,7 +5505,7 @@ ${epilog}`
4803
5505
  await readConfiguration(local_argv2);
4804
5506
  await construct_CertificateManager();
4805
5507
  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 || "";
5508
+ let subject = local_argv2.subject && local_argv2.subject.length > 1 ? new Subject7(local_argv2.subject) : gLocalConfig.subject || "";
4807
5509
  subject = JSON.parse(JSON.stringify(subject));
4808
5510
  const params = {
4809
5511
  applicationUri: gLocalConfig.applicationUri || "",
@@ -4819,8 +5521,8 @@ ${epilog}`
4819
5521
  async function command_full_certificate(local_argv2) {
4820
5522
  await readConfiguration(local_argv2);
4821
5523
  await construct_CertificateManager();
4822
- await construct_CertificateAuthority2("");
4823
- assert11(fs11.existsSync(gLocalConfig.CAFolder || ""), " CA folder must exist");
5524
+ await construct_CertificateAuthority("");
5525
+ assert10(fs13.existsSync(gLocalConfig.CAFolder || ""), " CA folder must exist");
4824
5526
  gLocalConfig.privateKey = void 0;
4825
5527
  gLocalConfig.subject = local_argv2.subject && local_argv2.subject.length > 1 ? local_argv2.subject : gLocalConfig.subject;
4826
5528
  const csr_file = await certificateManager.createCertificateRequest(
@@ -4831,7 +5533,7 @@ ${epilog}`
4831
5533
  }
4832
5534
  warningLog(" csr_file = ", csr_file);
4833
5535
  const certificate = csr_file.replace(".csr", ".pem");
4834
- if (fs11.existsSync(certificate)) {
5536
+ if (fs13.existsSync(certificate)) {
4835
5537
  throw new Error(` File ${certificate} already exist`);
4836
5538
  }
4837
5539
  await g_certificateAuthority.signCertificateRequest(
@@ -4839,8 +5541,8 @@ ${epilog}`
4839
5541
  csr_file,
4840
5542
  gLocalConfig
4841
5543
  );
4842
- assert11(typeof gLocalConfig.outputFile === "string");
4843
- fs11.writeFileSync(gLocalConfig.outputFile || "", fs11.readFileSync(certificate, "ascii"));
5544
+ assert10(typeof gLocalConfig.outputFile === "string");
5545
+ fs13.writeFileSync(gLocalConfig.outputFile || "", fs13.readFileSync(certificate, "ascii"));
4844
5546
  }
4845
5547
  await wrap(async () => await command_certificate(local_argv));
4846
5548
  return;
@@ -4859,13 +5561,13 @@ ${epilog}`
4859
5561
  await g_certificateAuthority.revokeCertificate(certificate, {});
4860
5562
  }
4861
5563
  await wrap(async () => {
4862
- const certificate = path7.resolve(local_argv.certificateFile);
5564
+ const certificate = path10.resolve(local_argv.certificateFile);
4863
5565
  warningLog(chalk7.yellow(" Certificate to revoke : "), chalk7.cyan(certificate));
4864
- if (!fs11.existsSync(certificate)) {
5566
+ if (!fs13.existsSync(certificate)) {
4865
5567
  throw new Error(`cannot find certificate to revoke ${certificate}`);
4866
5568
  }
4867
5569
  await readConfiguration(local_argv);
4868
- await construct_CertificateAuthority2("");
5570
+ await construct_CertificateAuthority("");
4869
5571
  await revoke_certificate(certificate);
4870
5572
  warningLog("done ... ");
4871
5573
  warningLog(" crl = ", g_certificateAuthority.revocationList);
@@ -4908,11 +5610,11 @@ ${epilog}`
4908
5610
  if (local_argv.help) return showHelp("csr", "create a certificate signing request", optionsDef);
4909
5611
  await wrap(async () => {
4910
5612
  await readConfiguration(local_argv);
4911
- if (!fs11.existsSync(gLocalConfig.PKIFolder || "")) {
5613
+ if (!fs13.existsSync(gLocalConfig.PKIFolder || "")) {
4912
5614
  warningLog("PKI folder must exist");
4913
5615
  }
4914
5616
  await construct_CertificateManager();
4915
- if (!gLocalConfig.outputFile || fs11.existsSync(gLocalConfig.outputFile)) {
5617
+ if (!gLocalConfig.outputFile || fs13.existsSync(gLocalConfig.outputFile)) {
4916
5618
  throw new Error(` File ${gLocalConfig.outputFile} already exist`);
4917
5619
  }
4918
5620
  gLocalConfig.privateKey = void 0;
@@ -4927,8 +5629,8 @@ ${epilog}`
4927
5629
  warningLog("please specify a output file");
4928
5630
  return;
4929
5631
  }
4930
- const csr = await fs11.promises.readFile(internal_csr_file, "utf-8");
4931
- fs11.writeFileSync(gLocalConfig.outputFile || "", csr, "utf-8");
5632
+ const csr = await fs13.promises.readFile(internal_csr_file, "utf-8");
5633
+ fs13.writeFileSync(gLocalConfig.outputFile || "", csr, "utf-8");
4932
5634
  warningLog("Subject = ", gLocalConfig.subject);
4933
5635
  warningLog("applicationUri = ", gLocalConfig.applicationUri);
4934
5636
  warningLog("altNames = ", gLocalConfig.altNames);
@@ -4956,16 +5658,16 @@ ${epilog}`
4956
5658
  return showHelp("sign", "validate a certificate signing request and generate a certificate", optionsDef);
4957
5659
  await wrap(async () => {
4958
5660
  await readConfiguration(local_argv);
4959
- if (!fs11.existsSync(gLocalConfig.CAFolder || "")) {
5661
+ if (!fs13.existsSync(gLocalConfig.CAFolder || "")) {
4960
5662
  throw new Error(`CA folder must exist:${gLocalConfig.CAFolder}`);
4961
5663
  }
4962
- await construct_CertificateAuthority2("");
4963
- const csr_file = path7.resolve(local_argv.csr || "");
4964
- if (!fs11.existsSync(csr_file)) {
5664
+ await construct_CertificateAuthority("");
5665
+ const csr_file = path10.resolve(local_argv.csr || "");
5666
+ if (!fs13.existsSync(csr_file)) {
4965
5667
  throw new Error(`Certificate signing request doesn't exist: ${csr_file}`);
4966
5668
  }
4967
- const certificate = path7.resolve(local_argv.output || csr_file.replace(".csr", ".pem"));
4968
- if (fs11.existsSync(certificate)) {
5669
+ const certificate = path10.resolve(local_argv.output || csr_file.replace(".csr", ".pem"));
5670
+ if (fs13.existsSync(certificate)) {
4969
5671
  throw new Error(` File ${certificate} already exist`);
4970
5672
  }
4971
5673
  await g_certificateAuthority.signCertificateRequest(
@@ -4973,8 +5675,8 @@ ${epilog}`
4973
5675
  csr_file,
4974
5676
  gLocalConfig
4975
5677
  );
4976
- assert11(typeof gLocalConfig.outputFile === "string");
4977
- fs11.writeFileSync(gLocalConfig.outputFile || "", fs11.readFileSync(certificate, "ascii"));
5678
+ assert10(typeof gLocalConfig.outputFile === "string");
5679
+ fs13.writeFileSync(gLocalConfig.outputFile || "", fs13.readFileSync(certificate, "ascii"));
4978
5680
  });
4979
5681
  return;
4980
5682
  }