node-opcua-pki 6.19.1 → 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
@@ -91,6 +91,45 @@ var init_hostname = __esm({
91
91
  }
92
92
  });
93
93
 
94
+ // packages/node-opcua-pki/lib/toolbox/common.ts
95
+ import assert2 from "assert";
96
+ async function resolvePrivateKeyPassphrase(passphrase) {
97
+ if (passphrase === void 0) {
98
+ return void 0;
99
+ }
100
+ return typeof passphrase === "function" ? await passphrase() : passphrase;
101
+ }
102
+ function adjustDate(params) {
103
+ assert2(params instanceof Object);
104
+ params.startDate = params.startDate || /* @__PURE__ */ new Date();
105
+ assert2(params.startDate instanceof Date);
106
+ if (params.validityMs !== void 0) {
107
+ if (params.validityMs <= 0) {
108
+ throw new RangeError(`validityMs must be > 0 (got ${params.validityMs})`);
109
+ }
110
+ params.endDate = new Date(params.startDate.getTime() + params.validityMs);
111
+ params.validity = Math.ceil(params.validityMs / 864e5);
112
+ } else {
113
+ params.validity = params.validity || 365;
114
+ params.endDate = new Date(params.startDate.getTime());
115
+ params.endDate.setDate(params.startDate.getDate() + params.validity);
116
+ }
117
+ assert2(params.endDate instanceof Date);
118
+ assert2(params.startDate instanceof Date);
119
+ }
120
+ function adjustApplicationUri(params) {
121
+ const applicationUri = params.applicationUri || "";
122
+ if (applicationUri.length > 200) {
123
+ throw new Error(`Openssl doesn't support urn with length greater than 200${applicationUri}`);
124
+ }
125
+ }
126
+ var init_common = __esm({
127
+ "packages/node-opcua-pki/lib/toolbox/common.ts"() {
128
+ "use strict";
129
+ init_esm_shims();
130
+ }
131
+ });
132
+
94
133
  // packages/node-opcua-pki/lib/toolbox/config.ts
95
134
  var g_config;
96
135
  var init_config = __esm({
@@ -126,7 +165,7 @@ var init_debug = __esm({
126
165
  });
127
166
 
128
167
  // packages/node-opcua-pki/lib/toolbox/common2.ts
129
- import assert2 from "assert";
168
+ import assert3 from "assert";
130
169
  import fs from "fs";
131
170
  import path from "path";
132
171
  import chalk from "chalk";
@@ -145,12 +184,31 @@ function mkdirRecursiveSync(folder) {
145
184
  fs.mkdirSync(folder, { recursive: true });
146
185
  }
147
186
  }
187
+ function restrictPrivateFilePermissions(target, mode) {
188
+ if (process.platform === "win32") {
189
+ return;
190
+ }
191
+ try {
192
+ fs.chmodSync(target, mode);
193
+ } catch (err) {
194
+ warningLog(chalk.yellow(" could not restrict permissions on "), target, err.message);
195
+ }
196
+ }
197
+ function ensurePrivateDirectory(dir) {
198
+ if (!fs.existsSync(dir)) {
199
+ fs.mkdirSync(dir, { recursive: true, mode: 448 });
200
+ }
201
+ restrictPrivateFilePermissions(dir, 448);
202
+ }
203
+ function isEncryptedPrivateKeyFile(filename) {
204
+ return fs.readFileSync(filename, "utf-8").includes("-----BEGIN ENCRYPTED PRIVATE KEY-----");
205
+ }
148
206
  function makePath(folderName, filename) {
149
207
  let s;
150
208
  if (filename) {
151
209
  s = path.join(path.normalize(folderName), filename);
152
210
  } else {
153
- assert2(folderName);
211
+ assert3(folderName);
154
212
  s = folderName;
155
213
  }
156
214
  s = s.replace(/\\/g, "/");
@@ -203,21 +261,22 @@ var init_display = __esm({
203
261
  });
204
262
 
205
263
  // packages/node-opcua-pki/lib/toolbox/without_openssl/create_certificate_signing_request.ts
206
- import assert3 from "assert";
264
+ import assert4 from "assert";
207
265
  import fs2 from "fs";
208
- import { createCertificateSigningRequest, pemToPrivateKey, Subject } from "node-opcua-crypto";
266
+ import { coercePrivateKeyPem, createCertificateSigningRequest, pemToPrivateKey, Subject } from "node-opcua-crypto";
209
267
  async function createCertificateSigningRequestAsync(certificateSigningRequestFilename, params) {
210
- assert3(params);
211
- assert3(params.rootDir);
212
- assert3(params.configFile);
213
- assert3(params.privateKey);
214
- assert3(typeof params.privateKey === "string");
215
- assert3(fs2.existsSync(params.privateKey), `Private key must exist${params.privateKey}`);
216
- assert3(fs2.existsSync(params.rootDir), "RootDir key must exist");
217
- assert3(typeof certificateSigningRequestFilename === "string");
268
+ assert4(params);
269
+ assert4(params.rootDir);
270
+ assert4(params.configFile);
271
+ assert4(params.privateKey);
272
+ if (typeof params.privateKey === "string") {
273
+ assert4(fs2.existsSync(params.privateKey), `Private key must exist${params.privateKey}`);
274
+ }
275
+ assert4(fs2.existsSync(params.rootDir), "RootDir key must exist");
276
+ assert4(typeof certificateSigningRequestFilename === "string");
218
277
  const subject = params.subject ? new Subject(params.subject).toString() : void 0;
219
278
  displaySubtitle("- Creating a Certificate Signing Request with subtile");
220
- const privateKeyPem = await fs2.promises.readFile(params.privateKey, "utf-8");
279
+ const privateKeyPem = typeof params.privateKey === "string" ? await fs2.promises.readFile(params.privateKey, "utf-8") : coercePrivateKeyPem(params.privateKey);
221
280
  const privateKey = await pemToPrivateKey(privateKeyPem);
222
281
  const { csr } = await createCertificateSigningRequest({
223
282
  privateKey,
@@ -228,7 +287,7 @@ async function createCertificateSigningRequestAsync(certificateSigningRequestFil
228
287
  purpose: params.purpose
229
288
  });
230
289
  await fs2.promises.writeFile(certificateSigningRequestFilename, csr, "utf-8");
231
- display(`- privateKey ${params.privateKey}`);
290
+ display(`- privateKey ${typeof params.privateKey === "string" ? params.privateKey : "<in-memory>"}`);
232
291
  display(`- certificateSigningRequestFilename ${certificateSigningRequestFilename}`);
233
292
  }
234
293
  var init_create_certificate_signing_request = __esm({
@@ -239,47 +298,12 @@ var init_create_certificate_signing_request = __esm({
239
298
  }
240
299
  });
241
300
 
242
- // packages/node-opcua-pki/lib/toolbox/common.ts
243
- import assert4 from "assert";
244
- function quote(str) {
245
- return `"${str || ""}"`;
246
- }
247
- function adjustDate(params) {
248
- assert4(params instanceof Object);
249
- params.startDate = params.startDate || /* @__PURE__ */ new Date();
250
- assert4(params.startDate instanceof Date);
251
- if (params.validityMs !== void 0) {
252
- if (params.validityMs <= 0) {
253
- throw new RangeError(`validityMs must be > 0 (got ${params.validityMs})`);
254
- }
255
- params.endDate = new Date(params.startDate.getTime() + params.validityMs);
256
- params.validity = Math.ceil(params.validityMs / 864e5);
257
- } else {
258
- params.validity = params.validity || 365;
259
- params.endDate = new Date(params.startDate.getTime());
260
- params.endDate.setDate(params.startDate.getDate() + params.validity);
261
- }
262
- assert4(params.endDate instanceof Date);
263
- assert4(params.startDate instanceof Date);
264
- }
265
- function adjustApplicationUri(params) {
266
- const applicationUri = params.applicationUri || "";
267
- if (applicationUri.length > 200) {
268
- throw new Error(`Openssl doesn't support urn with length greater than 200${applicationUri}`);
269
- }
270
- }
271
- var init_common = __esm({
272
- "packages/node-opcua-pki/lib/toolbox/common.ts"() {
273
- "use strict";
274
- init_esm_shims();
275
- }
276
- });
277
-
278
301
  // packages/node-opcua-pki/lib/toolbox/without_openssl/create_self_signed_certificate.ts
279
302
  import assert5 from "assert";
280
303
  import fs3 from "fs";
281
304
  import {
282
305
  CertificatePurpose,
306
+ coercePrivateKeyPem as coercePrivateKeyPem2,
283
307
  createSelfSignedCertificate as createSelfSignedCertificate1,
284
308
  pemToPrivateKey as pemToPrivateKey2,
285
309
  Subject as Subject2
@@ -289,7 +313,9 @@ async function createSelfSignedCertificateAsync(certificate, params) {
289
313
  assert5(params.purpose, "Please provide a Certificate Purpose");
290
314
  assert5(fs3.existsSync(params.configFile));
291
315
  assert5(fs3.existsSync(params.rootDir));
292
- assert5(fs3.existsSync(params.privateKey));
316
+ if (typeof params.privateKey === "string") {
317
+ assert5(fs3.existsSync(params.privateKey));
318
+ }
293
319
  if (!params.subject) {
294
320
  throw Error("Missing subject");
295
321
  }
@@ -301,7 +327,7 @@ async function createSelfSignedCertificateAsync(certificate, params) {
301
327
  subject = subject.toString();
302
328
  const purpose = params.purpose;
303
329
  displayTitle("Generate a certificate request");
304
- const privateKeyPem = await fs3.promises.readFile(params.privateKey, "utf-8");
330
+ const privateKeyPem = typeof params.privateKey === "string" ? await fs3.promises.readFile(params.privateKey, "utf-8") : coercePrivateKeyPem2(params.privateKey);
305
331
  const privateKey = await pemToPrivateKey2(privateKeyPem);
306
332
  const { cert } = await createSelfSignedCertificate1({
307
333
  privateKey,
@@ -365,9 +391,11 @@ import {
365
391
  readCertificateChain,
366
392
  readCertificateChainAsync,
367
393
  readCertificateRevocationList,
394
+ readPrivateKey,
368
395
  split_der,
369
396
  toPem,
370
- verifyCertificateSignature
397
+ verifyCertificateSignature,
398
+ writePrivateKeyFile
371
399
  } from "node-opcua-crypto";
372
400
  function getOrComputeInfo(entry) {
373
401
  if (!entry.info) {
@@ -468,6 +496,7 @@ var init_certificate_manager = __esm({
468
496
  "packages/node-opcua-pki/lib/pki/certificate_manager.ts"() {
469
497
  "use strict";
470
498
  init_esm_shims();
499
+ init_common();
471
500
  init_common2();
472
501
  init_debug();
473
502
  init_without_openssl();
@@ -490,7 +519,31 @@ var init_certificate_manager = __esm({
490
519
  // even if the consumer forgets to call dispose().
491
520
  static #activeInstances = /* @__PURE__ */ new Set();
492
521
  static #cleanupInstalled = false;
493
- 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() {
494
547
  if (_CertificateManager.#cleanupInstalled) return;
495
548
  _CertificateManager.#cleanupInstalled = true;
496
549
  const closeDanglingWatchers = () => {
@@ -506,13 +559,22 @@ var init_certificate_manager = __esm({
506
559
  }
507
560
  _CertificateManager.#activeInstances.clear();
508
561
  };
509
- process.on("beforeExit", closeDanglingWatchers);
510
- for (const signal of ["SIGINT", "SIGTERM"]) {
511
- process.once(signal, () => {
512
- closeDanglingWatchers();
513
- process.exit();
514
- });
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;
515
576
  }
577
+ _CertificateManager.#cleanupInstalled = false;
516
578
  }
517
579
  /**
518
580
  * Dispose **all** active CertificateManager instances,
@@ -576,6 +638,15 @@ var init_certificate_manager = __esm({
576
638
  #initializingPromise;
577
639
  #addCertValidation;
578
640
  #disableFileWatchers;
641
+ #privateKeyPassphrase;
642
+ #privateKeyProvider;
643
+ /**
644
+ * The on-disk key, decrypted once and kept for the instance's lifetime,
645
+ * so the passphrase (or its resolver function) is consulted at most
646
+ * once. Cleared by `dispose()`. Not used when a provider is configured:
647
+ * the provider is the authority on the current key.
648
+ */
649
+ #cachedPrivateKey;
579
650
  #thumbs = {
580
651
  rejected: /* @__PURE__ */ new Map(),
581
652
  trusted: /* @__PURE__ */ new Map(),
@@ -610,6 +681,8 @@ var init_certificate_manager = __esm({
610
681
  maxChainLength: v.maxChainLength ?? 5
611
682
  };
612
683
  this.#disableFileWatchers = options.disableFileWatchers ?? process.env.OPCUA_PKI_DISABLE_FILE_WATCHERS === "true";
684
+ this.#privateKeyPassphrase = options.privateKeyPassphrase;
685
+ this.#privateKeyProvider = options.privateKeyProvider;
613
686
  mkdirRecursiveSync(options.location);
614
687
  if (!fs4.existsSync(this.#location)) {
615
688
  throw new Error(`CertificateManager cannot access location ${this.#location}`);
@@ -623,10 +696,108 @@ var init_certificate_manager = __esm({
623
696
  get rootDir() {
624
697
  return this.#location;
625
698
  }
626
- /** Path to the private key file (`own/private/private_key.pem`). */
699
+ /**
700
+ * Path to the private key file (`own/private/private_key.pem`).
701
+ *
702
+ * Kept for backward compatibility with code that reads the key
703
+ * directly from disk. When a passphrase or a `privateKeyProvider` is
704
+ * configured, prefer {@link getPrivateKey} instead — this getter still
705
+ * returns the on-disk path even if a provider is configured (there may
706
+ * be no meaningful file in that case).
707
+ */
627
708
  get privateKey() {
628
709
  return path2.join(this.rootDir, "own/private/private_key.pem");
629
710
  }
711
+ /**
712
+ * Resolve the private key: from `privateKeyProvider` if configured,
713
+ * otherwise from disk (decrypting with `privateKeyPassphrase` if the
714
+ * key is encrypted). Fails closed — throws
715
+ * `PrivateKeyPassphraseRequiredError` — if the on-disk key is encrypted
716
+ * and no passphrase is configured, or if the wrong passphrase is
717
+ * configured.
718
+ *
719
+ * The on-disk key is read and decrypted once and then cached for the
720
+ * lifetime of this instance, so a `privateKeyPassphrase` function is
721
+ * called at most once (concurrent first calls share the same read). A
722
+ * failed read is not cached, so a caller can fix the passphrase and
723
+ * retry. A `privateKeyProvider` is consulted on every call: it is the
724
+ * authority on what the current key is.
725
+ */
726
+ async getPrivateKey() {
727
+ if (this.#privateKeyProvider) {
728
+ return await this.#privateKeyProvider.getPrivateKey();
729
+ }
730
+ if (this.#cachedPrivateKey) {
731
+ return this.#cachedPrivateKey;
732
+ }
733
+ if (!this.#privateKeyPromise) {
734
+ this.#privateKeyPromise = (async () => {
735
+ const passphrase = await resolvePrivateKeyPassphrase(this.#privateKeyPassphrase);
736
+ return readPrivateKey(this.privateKey, passphrase);
737
+ })().then(
738
+ (key) => {
739
+ this.#cachedPrivateKey = key;
740
+ return key;
741
+ },
742
+ (err) => {
743
+ this.#privateKeyPromise = void 0;
744
+ throw err;
745
+ }
746
+ );
747
+ }
748
+ return await this.#privateKeyPromise;
749
+ }
750
+ /** In-flight first read of the on-disk key, so concurrent callers share one passphrase resolution. */
751
+ #privateKeyPromise;
752
+ /**
753
+ * Enable, disable, or rotate the passphrase protecting the on-disk
754
+ * private key: decrypt with `oldPassphrase` (omit if the key is
755
+ * currently unencrypted), then write back encrypted with
756
+ * `newPassphrase` (omit to leave it unencrypted). The write goes to a
757
+ * temporary file in the same directory and is atomically renamed into
758
+ * place, so a crash mid-rotation cannot leave a partially-written key;
759
+ * the temporary file is removed if anything fails, so a rotation *to*
760
+ * plaintext can never leave a stray cleartext copy behind. Runs under
761
+ * the same lock as `initialize()`.
762
+ *
763
+ * This only rewrites the on-disk file — it does not update this
764
+ * instance's own `privateKeyPassphrase` (set at construction), and it
765
+ * drops this instance's cached key so that disk stays the source of
766
+ * truth. Construct a new `CertificateManager` with the new passphrase to
767
+ * continue using it afterward.
768
+ *
769
+ * Not supported when a `privateKeyProvider` is configured (there is no
770
+ * disk file for this method to rewrite).
771
+ */
772
+ async reencryptPrivateKey(oldPassphrase, newPassphrase) {
773
+ if (this.#privateKeyProvider) {
774
+ throw new Error("reencryptPrivateKey: not supported when a privateKeyProvider is configured");
775
+ }
776
+ const oldPass = await resolvePrivateKeyPassphrase(oldPassphrase);
777
+ const newPass = await resolvePrivateKeyPassphrase(newPassphrase);
778
+ await this.withLock2(async () => {
779
+ const privateKey = readPrivateKey(this.privateKey, oldPass);
780
+ await this.#rewritePrivateKeyFile(privateKey, newPass);
781
+ });
782
+ this.#cachedPrivateKey = void 0;
783
+ this.#privateKeyPromise = void 0;
784
+ }
785
+ /**
786
+ * Atomically replace the on-disk private key with `privateKey`, written
787
+ * as PKCS#8 (encrypted with `passphrase` if given). Temp file next to the
788
+ * target, `0600`, renamed into place; the temp file is unlinked on any
789
+ * failure so no partial or cleartext copy can be left behind.
790
+ * Caller must hold the lock.
791
+ */
792
+ async #rewritePrivateKeyFile(privateKey, passphrase) {
793
+ const tmpFilename = `${this.privateKey}.${process.pid}-${Date.now()}.tmp`;
794
+ try {
795
+ await writePrivateKeyFile(tmpFilename, privateKey, { passphrase });
796
+ await fs4.promises.rename(tmpFilename, this.privateKey);
797
+ } finally {
798
+ await fs4.promises.rm(tmpFilename, { force: true });
799
+ }
800
+ }
630
801
  /** Path to the OpenSSL random seed file. */
631
802
  get randomFile() {
632
803
  return path2.join(this.rootDir, "./random.rnd");
@@ -828,23 +999,19 @@ var init_certificate_manager = __esm({
828
999
  return "BadCertificateUntrusted" /* BadCertificateUntrusted */;
829
1000
  }
830
1001
  }
831
- const _c2 = chain[1] ? exploreCertificateInfo(chain[1]) : "non";
832
- debugLog("chain[1] info=", _c2);
833
- const certificateInfo = exploreCertificateInfo(chain[0]);
1002
+ const { validity } = exploreCertificate(chain[0]).tbsCertificate;
834
1003
  const now = /* @__PURE__ */ new Date();
835
1004
  let isTimeInvalid = false;
836
- if (certificateInfo.notBefore.getTime() > now.getTime()) {
1005
+ if (validity.notBefore.getTime() > now.getTime()) {
837
1006
  debugLog(
838
- `${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}`
839
1008
  );
840
1009
  if (!options.acceptPendingCertificate) {
841
1010
  isTimeInvalid = true;
842
1011
  }
843
1012
  }
844
- if (certificateInfo.notAfter.getTime() <= now.getTime()) {
845
- debugLog(
846
- `${chalk3.red("certificate is invalid : certificate has expired !")} not after date =${certificateInfo.notAfter}`
847
- );
1013
+ if (validity.notAfter.getTime() <= now.getTime()) {
1014
+ debugLog(`${chalk3.red("certificate is invalid : certificate has expired !")} not after date =${validity.notAfter}`);
848
1015
  if (!options.acceptOutdatedCertificate) {
849
1016
  isTimeInvalid = true;
850
1017
  }
@@ -883,7 +1050,7 @@ var init_certificate_manager = __esm({
883
1050
  const chain = coerceCertificateChain(certificate);
884
1051
  for (const element of chain) {
885
1052
  try {
886
- exploreCertificateInfo(element);
1053
+ exploreCertificate(element);
887
1054
  } catch (_err) {
888
1055
  return "BadCertificateInvalid" /* BadCertificateInvalid */;
889
1056
  }
@@ -926,19 +1093,24 @@ var init_certificate_manager = __esm({
926
1093
  }
927
1094
  this.state = 1 /* Initializing */;
928
1095
  this.#initializingPromise = this.#initialize();
929
- await this.#initializingPromise;
1096
+ try {
1097
+ await this.#initializingPromise;
1098
+ } catch (err) {
1099
+ this.#initializingPromise = void 0;
1100
+ this.state = 0 /* Uninitialized */;
1101
+ throw err;
1102
+ }
930
1103
  this.#initializingPromise = void 0;
931
1104
  this.state = 2 /* Initialized */;
932
1105
  _CertificateManager.#activeInstances.add(this);
933
- _CertificateManager.#installProcessCleanup();
1106
+ _CertificateManager.#installExitCleanup();
934
1107
  }
935
1108
  async #initialize() {
936
- this.state = 1 /* Initializing */;
937
1109
  const pkiDir = this.#location;
938
1110
  mkdirRecursiveSync(pkiDir);
939
1111
  mkdirRecursiveSync(path2.join(pkiDir, "own"));
940
1112
  mkdirRecursiveSync(path2.join(pkiDir, "own/certs"));
941
- mkdirRecursiveSync(path2.join(pkiDir, "own/private"));
1113
+ ensurePrivateDirectory(path2.join(pkiDir, "own/private"));
942
1114
  mkdirRecursiveSync(path2.join(pkiDir, "rejected"));
943
1115
  mkdirRecursiveSync(path2.join(pkiDir, "trusted"));
944
1116
  mkdirRecursiveSync(path2.join(pkiDir, "trusted/certs"));
@@ -946,7 +1118,10 @@ var init_certificate_manager = __esm({
946
1118
  mkdirRecursiveSync(path2.join(pkiDir, "issuers"));
947
1119
  mkdirRecursiveSync(path2.join(pkiDir, "issuers/certs"));
948
1120
  mkdirRecursiveSync(path2.join(pkiDir, "issuers/crl"));
949
- if (!fs4.existsSync(this.configFile) || !fs4.existsSync(this.privateKey)) {
1121
+ const ownsDiskKey = !this.#privateKeyProvider;
1122
+ const needsKeyGeneration = ownsDiskKey && !fs4.existsSync(this.privateKey);
1123
+ const needsKeyEncryption = ownsDiskKey && !needsKeyGeneration && this.#privateKeyPassphrase !== void 0 && !isEncryptedPrivateKeyFile(this.privateKey);
1124
+ if (!fs4.existsSync(this.configFile) || needsKeyGeneration || needsKeyEncryption) {
950
1125
  return await this.withLock2(async () => {
951
1126
  if (this.state === 3 /* Disposing */ || this.state === 4 /* Disposed */) {
952
1127
  return;
@@ -954,15 +1129,29 @@ var init_certificate_manager = __esm({
954
1129
  if (!fs4.existsSync(this.configFile)) {
955
1130
  fs4.writeFileSync(this.configFile, configurationFileSimpleTemplate);
956
1131
  }
957
- if (!fs4.existsSync(this.privateKey)) {
1132
+ if (ownsDiskKey && !fs4.existsSync(this.privateKey)) {
958
1133
  debugLog("generating private key ...");
959
- await generatePrivateKeyFile(this.privateKey, this.keySize);
960
- await this.#readCertificates();
961
- } else {
962
- await this.#readCertificates();
1134
+ const passphrase = await resolvePrivateKeyPassphrase(this.#privateKeyPassphrase);
1135
+ await generatePrivateKeyFile(this.privateKey, this.keySize, { passphrase });
1136
+ this.#cachedPrivateKey = readPrivateKey(this.privateKey, passphrase);
1137
+ } else if (ownsDiskKey && this.#privateKeyPassphrase !== void 0 && !isEncryptedPrivateKeyFile(this.privateKey)) {
1138
+ warningLog("initialize: private key is plaintext but a passphrase is configured; encrypting it in place");
1139
+ const passphrase = await resolvePrivateKeyPassphrase(this.#privateKeyPassphrase);
1140
+ const plaintextKey = readPrivateKey(this.privateKey);
1141
+ await this.#rewritePrivateKeyFile(plaintextKey, passphrase);
1142
+ this.#cachedPrivateKey = plaintextKey;
963
1143
  }
1144
+ if (ownsDiskKey) {
1145
+ restrictPrivateFilePermissions(this.privateKey, 384);
1146
+ }
1147
+ await this.getPrivateKey();
1148
+ await this.#readCertificates();
964
1149
  });
965
1150
  } else {
1151
+ if (ownsDiskKey) {
1152
+ restrictPrivateFilePermissions(this.privateKey, 384);
1153
+ }
1154
+ await this.getPrivateKey();
966
1155
  await this.#readCertificates();
967
1156
  }
968
1157
  }
@@ -998,7 +1187,10 @@ var init_certificate_manager = __esm({
998
1187
  this.#watchers.splice(0);
999
1188
  } finally {
1000
1189
  this.state = 4 /* Disposed */;
1190
+ this.#cachedPrivateKey = void 0;
1191
+ this.#privateKeyPromise = void 0;
1001
1192
  _CertificateManager.#activeInstances.delete(this);
1193
+ _CertificateManager.#uninstallExitCleanupIfIdle();
1002
1194
  }
1003
1195
  }
1004
1196
  /**
@@ -1044,16 +1236,18 @@ var init_certificate_manager = __esm({
1044
1236
  if (typeof params.applicationUri !== "string") {
1045
1237
  throw new Error("createSelfSignedCertificate: expecting applicationUri to be a string");
1046
1238
  }
1047
- if (!fs4.existsSync(this.privateKey)) {
1239
+ if (!this.#privateKeyProvider && !fs4.existsSync(this.privateKey)) {
1048
1240
  throw new Error(`Cannot find private key ${this.privateKey}`);
1049
1241
  }
1050
1242
  let certificateFilename = path2.join(this.rootDir, "own/certs/self_signed_certificate.pem");
1051
1243
  certificateFilename = params.outputFile || certificateFilename;
1052
- const _params = params;
1053
- _params.rootDir = this.rootDir;
1054
- _params.configFile = this.configFile;
1055
- _params.privateKey = this.privateKey;
1056
- _params.subject = params.subject || "CN=FIXME";
1244
+ const _params = {
1245
+ ...params,
1246
+ rootDir: this.rootDir,
1247
+ configFile: this.configFile,
1248
+ privateKey: await this.getPrivateKey(),
1249
+ subject: params.subject || "CN=FIXME"
1250
+ };
1057
1251
  await this.withLock2(async () => {
1058
1252
  await createSelfSignedCertificate(certificateFilename, _params);
1059
1253
  });
@@ -1072,13 +1266,15 @@ var init_certificate_manager = __esm({
1072
1266
  if (!params) {
1073
1267
  throw new Error("params is required");
1074
1268
  }
1075
- const _params = params;
1076
- if (Object.prototype.hasOwnProperty.call(_params, "rootDir")) {
1269
+ if (Object.prototype.hasOwnProperty.call(params, "rootDir")) {
1077
1270
  throw new Error("rootDir should not be specified ");
1078
1271
  }
1079
- _params.rootDir = path2.resolve(this.rootDir);
1080
- _params.configFile = path2.resolve(this.configFile);
1081
- _params.privateKey = path2.resolve(this.privateKey);
1272
+ const _params = {
1273
+ ...params,
1274
+ rootDir: path2.resolve(this.rootDir),
1275
+ configFile: path2.resolve(this.configFile),
1276
+ privateKey: await this.getPrivateKey()
1277
+ };
1082
1278
  return await this.withLock2(async () => {
1083
1279
  const now = /* @__PURE__ */ new Date();
1084
1280
  const today2 = `${now.toISOString().slice(0, 10)}_${now.getTime()}`;
@@ -1343,17 +1539,12 @@ var init_certificate_manager = __esm({
1343
1539
  return "BadSecurityChecksFailed" /* BadSecurityChecksFailed */;
1344
1540
  }
1345
1541
  if (!opts.acceptExpiredCertificate) {
1346
- let certDetails;
1347
- try {
1348
- certDetails = exploreCertificateInfo(currentCert);
1349
- } catch (_err) {
1350
- return "BadCertificateInvalid" /* BadCertificateInvalid */;
1351
- }
1542
+ const { validity } = currentInfo.tbsCertificate;
1352
1543
  const now = /* @__PURE__ */ new Date();
1353
- if (certDetails.notBefore.getTime() > now.getTime()) {
1544
+ if (validity.notBefore.getTime() > now.getTime()) {
1354
1545
  return "BadCertificateTimeInvalid" /* BadCertificateTimeInvalid */;
1355
1546
  }
1356
- if (certDetails.notAfter.getTime() <= now.getTime()) {
1547
+ if (validity.notAfter.getTime() <= now.getTime()) {
1357
1548
  return depth === 1 ? "BadCertificateTimeInvalid" /* BadCertificateTimeInvalid */ : "BadCertificateIssuerTimeInvalid" /* BadCertificateIssuerTimeInvalid */;
1358
1549
  }
1359
1550
  }
@@ -1703,6 +1894,7 @@ var init_certificate_manager = __esm({
1703
1894
  const chokidarOptions = {
1704
1895
  usePolling,
1705
1896
  ...usePolling ? { interval: pollingInterval } : {},
1897
+ depth: 0,
1706
1898
  persistent: false
1707
1899
  };
1708
1900
  const allCapturedHandles = [];
@@ -1940,6 +2132,19 @@ var init_toolbox = __esm({
1940
2132
  });
1941
2133
 
1942
2134
  // packages/node-opcua-pki/lib/toolbox/with_openssl/_env.ts
2135
+ function buildChildEnv(extra) {
2136
+ const env = {};
2137
+ for (const key of Object.keys(process.env)) {
2138
+ if (SAFE_ENV_PASSTHROUGH.has(key.toLowerCase())) {
2139
+ env[key] = process.env[key];
2140
+ }
2141
+ }
2142
+ return { ...env, ...extra };
2143
+ }
2144
+ function redactEnvForLog(options) {
2145
+ const { env, ...rest } = options;
2146
+ return env ? { ...rest, env: Object.keys(env) } : rest;
2147
+ }
1943
2148
  function setEnv(varName, value) {
1944
2149
  if (!g_config.silent) {
1945
2150
  warningLog(` set ${varName}=${value}`);
@@ -1966,29 +2171,56 @@ function getEnvironmentVarNames() {
1966
2171
  return { key: varName, pattern: `\\$ENV\\:\\:${varName}` };
1967
2172
  });
1968
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
+ }
1969
2181
  function processAltNames(params) {
1970
2182
  params.dns = params.dns || [];
1971
2183
  params.ip = params.ip || [];
1972
- let subjectAltName = [];
1973
- subjectAltName.push(`URI:${params.applicationUri}`);
1974
- subjectAltName = [].concat(
1975
- subjectAltName,
1976
- params.dns.map((d) => `DNS:${d}`)
1977
- );
1978
- subjectAltName = [].concat(
1979
- subjectAltName,
1980
- params.ip.map((d) => `IP:${d}`)
1981
- );
1982
- const subjectAltNameString = subjectAltName.join(", ");
1983
- setEnv("ALTNAME", subjectAltNameString);
2184
+ setEnv("ALTNAME", buildSubjectAltNameString(params));
1984
2185
  }
1985
- var exportedEnvVars;
2186
+ var SAFE_ENV_PASSTHROUGH, exportedEnvVars;
1986
2187
  var init_env = __esm({
1987
2188
  "packages/node-opcua-pki/lib/toolbox/with_openssl/_env.ts"() {
1988
2189
  "use strict";
1989
2190
  init_esm_shims();
1990
2191
  init_config();
1991
2192
  init_debug();
2193
+ SAFE_ENV_PASSTHROUGH = /* @__PURE__ */ new Set([
2194
+ // POSIX/Windows shell and process essentials
2195
+ "path",
2196
+ "home",
2197
+ "userprofile",
2198
+ "temp",
2199
+ "tmp",
2200
+ "tmpdir",
2201
+ "systemroot",
2202
+ "windir",
2203
+ "comspec",
2204
+ "pathext",
2205
+ "appdata",
2206
+ "localappdata",
2207
+ // dynamic loader: a custom-built or relocated openssl (e.g. under /opt,
2208
+ // or Homebrew on macOS) may need these to find its own libcrypto/libssl
2209
+ "ld_library_path",
2210
+ "dyld_library_path",
2211
+ "dyld_fallback_library_path",
2212
+ // locale, so openssl's textual output stays parseable
2213
+ "lang",
2214
+ "lc_all",
2215
+ "lc_ctype",
2216
+ // openssl configuration this app, or the host OS/user, may rely on
2217
+ "openssl_conf",
2218
+ "randfile",
2219
+ "openssl_modules",
2220
+ "openssl_engines",
2221
+ "ssl_cert_file",
2222
+ "ssl_cert_dir"
2223
+ ]);
1992
2224
  exportedEnvVars = {};
1993
2225
  }
1994
2226
  });
@@ -2011,24 +2243,17 @@ import { pipeline } from "stream/promises";
2011
2243
  import byline from "byline";
2012
2244
  import chalk4 from "chalk";
2013
2245
  import yauzl from "yauzl";
2014
- async function execute(cmd, cwd) {
2246
+ async function execute(file, args, cwd) {
2015
2247
  let output = "";
2016
- const options = {
2017
- cwd,
2018
- windowsHide: true
2019
- };
2020
2248
  return await new Promise((resolve, reject) => {
2021
- const child = child_process.exec(
2022
- cmd,
2023
- options,
2024
- (err) => {
2025
- const exitCode = err === null ? 0 : typeof err.code === "number" ? err.code : 1;
2026
- if (err) reject(err);
2027
- else {
2028
- resolve({ exitCode, output });
2029
- }
2030
- }
2031
- );
2249
+ const child = child_process.spawn(file, args, {
2250
+ cwd,
2251
+ windowsHide: true,
2252
+ env: buildChildEnv(),
2253
+ stdio: ["ignore", "pipe", "pipe"]
2254
+ });
2255
+ child.on("error", (err) => reject(err));
2256
+ child.on("close", (code) => resolve({ exitCode: code ?? 1, output }));
2032
2257
  const stream1 = byline(child.stdout);
2033
2258
  stream1.on("data", (line) => {
2034
2259
  output += `${line}
@@ -2040,16 +2265,13 @@ async function execute(cmd, cwd) {
2040
2265
  });
2041
2266
  });
2042
2267
  }
2043
- function quote2(str) {
2044
- return `"${str.replace(/\\/g, "/")}"`;
2045
- }
2046
2268
  function is_expected_openssl_version(strVersion) {
2047
2269
  return !!strVersion.match(/OpenSSL \d/);
2048
2270
  }
2049
2271
  async function getopensslExecPath() {
2050
2272
  let result1;
2051
2273
  try {
2052
- result1 = await execute("which openssl");
2274
+ result1 = await execute("which", ["openssl"]);
2053
2275
  } catch (err) {
2054
2276
  warningLog("warning: ", err.message);
2055
2277
  throw new Error("Cannot find openssl");
@@ -2066,11 +2288,10 @@ async function getopensslExecPath() {
2066
2288
  }
2067
2289
  async function check_system_openssl_version() {
2068
2290
  const opensslExecPath = await getopensslExecPath();
2069
- const q_opensslExecPath = quote2(opensslExecPath);
2070
2291
  if (doDebug2) {
2071
2292
  warningLog(` OpenSSL found in : ${chalk4.yellow(opensslExecPath)}`);
2072
2293
  }
2073
- const result = await execute(`${q_opensslExecPath} version`);
2294
+ const result = await execute(opensslExecPath, ["version"]);
2074
2295
  const exitCode = result?.exitCode;
2075
2296
  const output = result?.output;
2076
2297
  const version = output.trim();
@@ -2110,9 +2331,8 @@ async function install_and_check_win32_openssl_version() {
2110
2331
  version: `cannot find file ${opensslExecPath2}`
2111
2332
  };
2112
2333
  } else {
2113
- const q_openssl_exe_path = quote2(opensslExecPath2);
2114
2334
  const cwd = ".";
2115
- const { exitCode, output } = await execute(`${q_openssl_exe_path} version`, cwd);
2335
+ const { exitCode, output } = await execute(opensslExecPath2, ["version"], cwd);
2116
2336
  const version = output.trim();
2117
2337
  if (doDebug2) {
2118
2338
  warningLog(" Version = ", version);
@@ -2125,7 +2345,7 @@ async function install_and_check_win32_openssl_version() {
2125
2345
  }
2126
2346
  async function find_system_openssl_win32() {
2127
2347
  try {
2128
- const result = await execute("where openssl");
2348
+ const result = await execute("where", ["openssl"]);
2129
2349
  if (result.exitCode !== 0) {
2130
2350
  return void 0;
2131
2351
  }
@@ -2133,8 +2353,7 @@ async function install_and_check_win32_openssl_version() {
2133
2353
  if (!opensslPath2 || !fs5.existsSync(opensslPath2)) {
2134
2354
  return void 0;
2135
2355
  }
2136
- const q5 = quote2(opensslPath2);
2137
- const versionResult = await execute(`${q5} version`);
2356
+ const versionResult = await execute(opensslPath2, ["version"]);
2138
2357
  const version = versionResult.output.trim();
2139
2358
  if (versionResult.exitCode === 0 && is_expected_openssl_version(version)) {
2140
2359
  warningLog(
@@ -2299,6 +2518,7 @@ var init_install_prerequisite = __esm({
2299
2518
  "use strict";
2300
2519
  init_esm_shims();
2301
2520
  init_debug();
2521
+ init_env();
2302
2522
  doDebug2 = process.env.NODEOPCUAPKIDEBUG || false;
2303
2523
  }
2304
2524
  });
@@ -2310,36 +2530,50 @@ import fs6 from "fs";
2310
2530
  import os3 from "os";
2311
2531
  import byline2 from "byline";
2312
2532
  import chalk5 from "chalk";
2313
- async function execute2(cmd, options) {
2533
+ function passinArg(passphrase = "") {
2534
+ return { args: ["-passin", `env:${PASSIN_ENV_VAR}`], env: { [PASSIN_ENV_VAR]: passphrase } };
2535
+ }
2536
+ function renderForDisplay(file, args) {
2537
+ return [file, ...args].map((a) => a === "" || /[\s"'`$\\]/.test(a) ? JSON.stringify(a) : a).join(" ");
2538
+ }
2539
+ async function execute2(file, args, options) {
2314
2540
  const from = new Error();
2315
2541
  options.cwd = options.cwd || process.cwd();
2316
2542
  if (!g_config.silent) {
2317
2543
  warningLog(chalk5.cyan(" CWD "), options.cwd);
2318
2544
  }
2319
2545
  const outputs = [];
2546
+ const errorOutputs = [];
2547
+ const display2 = renderForDisplay(file, args);
2320
2548
  return await new Promise((resolve, reject) => {
2321
- const child = child_process2.exec(
2322
- cmd,
2323
- {
2324
- cwd: options.cwd,
2325
- windowsHide: true
2326
- },
2327
- (err) => {
2328
- if (err) {
2329
- if (!options.hideErrorMessage) {
2330
- const fence = "###########################################";
2331
- console.error(chalk5.bgWhiteBright.redBright(`${fence} OPENSSL ERROR ${fence}`));
2332
- console.error(chalk5.bgWhiteBright.redBright(`CWD = ${options.cwd}`));
2333
- console.error(chalk5.bgWhiteBright.redBright(err.message));
2334
- console.error(chalk5.bgWhiteBright.redBright(`${fence} OPENSSL ERROR ${fence}`));
2335
- console.error(from.stack);
2336
- }
2337
- reject(new Error(err.message));
2338
- return;
2339
- }
2549
+ const child = child_process2.spawn(file, [...args], {
2550
+ cwd: options.cwd,
2551
+ windowsHide: true,
2552
+ env: buildChildEnv(options.env),
2553
+ stdio: ["ignore", "pipe", "pipe"]
2554
+ });
2555
+ const fail = (message) => {
2556
+ if (!options.hideErrorMessage && !g_config.silent) {
2557
+ const fence = "###########################################";
2558
+ console.error(chalk5.bgWhiteBright.redBright(`${fence} OPENSSL ERROR ${fence}`));
2559
+ console.error(chalk5.bgWhiteBright.redBright(`CWD = ${options.cwd}`));
2560
+ console.error(chalk5.bgWhiteBright.redBright(message));
2561
+ console.error(chalk5.bgWhiteBright.redBright(`${fence} OPENSSL ERROR ${fence}`));
2562
+ console.error(from.stack);
2563
+ }
2564
+ reject(new Error(message));
2565
+ };
2566
+ child.on("error", (err) => fail(`Command failed: ${display2}
2567
+ ${err.message}`));
2568
+ child.on("close", (code, signal) => {
2569
+ if (code === 0) {
2340
2570
  resolve(outputs.join(""));
2571
+ return;
2341
2572
  }
2342
- );
2573
+ const why = signal ? `signal ${signal}` : `exit code ${code}`;
2574
+ fail(`Command failed: ${display2}
2575
+ ${errorOutputs.join("")}(${why})`);
2576
+ });
2343
2577
  if (child.stdout) {
2344
2578
  const stream2 = byline2(child.stdout);
2345
2579
  stream2.on("data", (line) => {
@@ -2356,17 +2590,17 @@ async function execute2(cmd, options) {
2356
2590
  });
2357
2591
  }
2358
2592
  }
2359
- if (!g_config.silent) {
2360
- if (child.stderr) {
2361
- const stream1 = byline2(child.stderr);
2362
- stream1.on("data", (line) => {
2363
- line = line.toString();
2364
- if (displayError) {
2365
- process.stdout.write(`${chalk5.white(" stderr ") + chalk5.red(line)}
2593
+ if (child.stderr) {
2594
+ const stream1 = byline2(child.stderr);
2595
+ stream1.on("data", (line) => {
2596
+ line = line.toString();
2597
+ errorOutputs.push(`${line}
2366
2598
  `);
2367
- }
2368
- });
2369
- }
2599
+ if (!g_config.silent && displayError) {
2600
+ process.stdout.write(`${chalk5.white(" stderr ") + chalk5.red(line)}
2601
+ `);
2602
+ }
2603
+ });
2370
2604
  }
2371
2605
  });
2372
2606
  }
@@ -2376,18 +2610,18 @@ async function find_openssl() {
2376
2610
  async function ensure_openssl_installed() {
2377
2611
  if (!opensslPath) {
2378
2612
  opensslPath = await find_openssl();
2379
- const outputs = await execute_openssl("version", { cwd: "." });
2613
+ const outputs = await execute_openssl(["version"], { cwd: "." });
2380
2614
  g_config.opensslVersion = outputs.trim();
2381
2615
  if (doDebug) {
2382
2616
  warningLog("OpenSSL version : ", g_config.opensslVersion);
2383
2617
  }
2384
2618
  }
2385
2619
  }
2386
- async function execute_openssl_no_failure(cmd, options) {
2620
+ async function execute_openssl_no_failure(args, options) {
2387
2621
  options = options || {};
2388
2622
  options.hideErrorMessage = true;
2389
2623
  try {
2390
- return await execute_openssl(cmd, options);
2624
+ return await execute_openssl(args, options);
2391
2625
  } catch (err) {
2392
2626
  debugLog(" (ignored error = ERROR : )", err.message);
2393
2627
  }
@@ -2395,8 +2629,8 @@ async function execute_openssl_no_failure(cmd, options) {
2395
2629
  function getTempFolder() {
2396
2630
  return os3.tmpdir();
2397
2631
  }
2398
- async function execute_openssl(cmd, options) {
2399
- debugLog("execute_openssl", cmd, options);
2632
+ async function execute_openssl(args, options) {
2633
+ debugLog("execute_openssl", args, redactEnvForLog(options));
2400
2634
  const empty_config_file = n(getTempFolder(), "empty_config.cnf");
2401
2635
  if (!fs6.existsSync(empty_config_file)) {
2402
2636
  await fs6.promises.writeFile(empty_config_file, "# empty config file");
@@ -2408,23 +2642,23 @@ async function execute_openssl(cmd, options) {
2408
2642
  if (!g_config.silent) {
2409
2643
  warningLog(chalk5.cyan(" OPENSSL_CONF"), process.env.OPENSSL_CONF);
2410
2644
  warningLog(chalk5.cyan(" RANDFILE "), process.env.RANDFILE);
2411
- warningLog(chalk5.cyan(" CMD openssl "), chalk5.cyanBright(cmd));
2645
+ warningLog(chalk5.cyan(" CMD "), chalk5.cyanBright(renderForDisplay("openssl", args)));
2412
2646
  }
2413
2647
  await ensure_openssl_installed();
2414
- return await execute2(`${quote(opensslPath)} ${cmd}`, options);
2648
+ return await execute2(opensslPath, args, options);
2415
2649
  }
2416
- var opensslPath, n;
2650
+ var opensslPath, n, PASSIN_ENV_VAR;
2417
2651
  var init_execute_openssl = __esm({
2418
2652
  "packages/node-opcua-pki/lib/toolbox/with_openssl/execute_openssl.ts"() {
2419
2653
  "use strict";
2420
2654
  init_esm_shims();
2421
- init_common();
2422
2655
  init_common2();
2423
2656
  init_config();
2424
2657
  init_debug();
2425
2658
  init_env();
2426
2659
  init_install_prerequisite();
2427
2660
  n = makePath;
2661
+ PASSIN_ENV_VAR = "NODE_OPCUA_PKI_OPENSSL_PASSIN";
2428
2662
  }
2429
2663
  });
2430
2664
 
@@ -2440,19 +2674,27 @@ function openssl_require2DigitYearInDate() {
2440
2674
  }
2441
2675
  return g_config.opensslVersion.match(/OpenSSL 0\.9/);
2442
2676
  }
2443
- function stripConditionalBlocks(template) {
2677
+ function stripConditionalBlocks(template, envOverrides) {
2444
2678
  return template.replace(/\{\{#([A-Z_][A-Z0-9_]*)\}\}([\s\S]*?)\{\{\/\1\}\}\r?\n?/g, (_match, key, content) => {
2445
- const keep = hasEnv(key) && getEnv(key) !== "";
2679
+ const keep = envOverrides && Object.prototype.hasOwnProperty.call(envOverrides, key) ? envOverrides[key] !== "" : hasEnv(key) && getEnv(key) !== "";
2446
2680
  return keep ? content : "";
2447
2681
  });
2448
2682
  }
2449
- function generateStaticConfig(configPath, options) {
2683
+ function generateStaticConfig(configPath, options, envOverrides) {
2450
2684
  const prePath = options?.cwd || "";
2451
2685
  const originalFilename = !path4.isAbsolute(configPath) ? path4.join(prePath, configPath) : configPath;
2452
2686
  let staticConfig = fs7.readFileSync(originalFilename, { encoding: "utf8" });
2453
- staticConfig = stripConditionalBlocks(staticConfig);
2687
+ staticConfig = stripConditionalBlocks(staticConfig, envOverrides);
2454
2688
  for (const envVar of getEnvironmentVarNames()) {
2455
- 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
+ }
2456
2698
  }
2457
2699
  const staticConfigPath = `${configPath}.${process.pid}-${_counter++}.tmp`;
2458
2700
  const temporaryConfigPath = !path4.isAbsolute(configPath) ? path4.join(prePath, staticConfigPath) : staticConfigPath;
@@ -2463,9 +2705,15 @@ function generateStaticConfig(configPath, options) {
2463
2705
  return temporaryConfigPath;
2464
2706
  }
2465
2707
  }
2466
- async function getPublicKeyFromPrivateKey(privateKeyFilename, publicKeyFilename) {
2708
+ async function cleanupStaticConfig(configFile, options) {
2709
+ await fs7.promises.rm(path4.resolve(options?.cwd ?? "", configFile), { force: true });
2710
+ }
2711
+ async function getPublicKeyFromPrivateKey(privateKeyFilename, publicKeyFilename, passphrase) {
2467
2712
  assert7(fs7.existsSync(privateKeyFilename));
2468
- await execute_openssl(`rsa -pubout -in ${q(n2(privateKeyFilename))} -out ${q(n2(publicKeyFilename))}`, {});
2713
+ const passin = passinArg(passphrase);
2714
+ await execute_openssl(["rsa", "-pubout", "-in", n2(privateKeyFilename), "-out", n2(publicKeyFilename), ...passin.args], {
2715
+ env: passin.env
2716
+ });
2469
2717
  }
2470
2718
  function x509Date(date) {
2471
2719
  date = date || /* @__PURE__ */ new Date();
@@ -2486,30 +2734,28 @@ function x509Date(date) {
2486
2734
  }
2487
2735
  async function dumpCertificate(certificate) {
2488
2736
  assert7(fs7.existsSync(certificate));
2489
- return await execute_openssl(`x509 -in ${q(n2(certificate))} -text -noout`, {});
2737
+ return await execute_openssl(["x509", "-in", n2(certificate), "-text", "-noout"], {});
2490
2738
  }
2491
2739
  async function toDer(certificatePem) {
2492
2740
  assert7(fs7.existsSync(certificatePem));
2493
2741
  const certificateDer = certificatePem.replace(".pem", ".der");
2494
- return await execute_openssl(`x509 -outform der -in ${certificatePem} -out ${certificateDer}`, {});
2742
+ return await execute_openssl(["x509", "-outform", "der", "-in", certificatePem, "-out", certificateDer], {});
2495
2743
  }
2496
2744
  async function fingerprint(certificatePem) {
2497
2745
  assert7(fs7.existsSync(certificatePem));
2498
- return await execute_openssl(`x509 -fingerprint -noout -in ${certificatePem}`, {});
2746
+ return await execute_openssl(["x509", "-fingerprint", "-noout", "-in", certificatePem], {});
2499
2747
  }
2500
- var _counter, q, n2;
2748
+ var _counter, n2;
2501
2749
  var init_toolbox2 = __esm({
2502
2750
  "packages/node-opcua-pki/lib/toolbox/with_openssl/toolbox.ts"() {
2503
2751
  "use strict";
2504
2752
  init_esm_shims();
2505
- init_common();
2506
2753
  init_common2();
2507
2754
  init_config();
2508
2755
  init_env();
2509
2756
  init_execute_openssl();
2510
2757
  g_config.opensslVersion = "";
2511
2758
  _counter = 0;
2512
- q = quote;
2513
2759
  n2 = makePath;
2514
2760
  }
2515
2761
  });
@@ -2530,29 +2776,42 @@ async function createCertificateSigningRequestWithOpenSSL(certificateSigningRequ
2530
2776
  assert8(typeof certificateSigningRequestFilename === "string");
2531
2777
  processAltNames(params);
2532
2778
  const configFile = generateStaticConfig(params.configFile, { cwd: params.rootDir });
2533
- const options = { cwd: params.rootDir, openssl_conf: path5.relative(params.rootDir, configFile) };
2534
- const configOption = ` -config ${q2(n3(configFile))}`;
2535
- const subject = params.subject ? new Subject3(params.subject).toString() : void 0;
2536
- const subjectOptions = subject ? ` -subj "${subject}"` : "";
2537
- displaySubtitle("- Creating a Certificate Signing Request with openssl");
2538
- await execute_openssl(
2539
- "req -new -sha256 -batch -text " + configOption + " -key " + q2(n3(params.privateKey)) + subjectOptions + " -out " + q2(n3(certificateSigningRequestFilename)),
2540
- options
2541
- );
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
+ }
2542
2803
  }
2543
- var q2, n3;
2804
+ var n3;
2544
2805
  var init_create_certificate_signing_request2 = __esm({
2545
2806
  "packages/node-opcua-pki/lib/toolbox/with_openssl/create_certificate_signing_request.ts"() {
2546
2807
  "use strict";
2547
2808
  init_esm_shims();
2548
2809
  init_subject();
2549
- init_common();
2550
2810
  init_common2();
2551
2811
  init_display();
2552
2812
  init_env();
2553
2813
  init_execute_openssl();
2554
2814
  init_toolbox2();
2555
- q2 = quote;
2556
2815
  n3 = makePath;
2557
2816
  }
2558
2817
  });
@@ -2571,94 +2830,909 @@ var init_with_openssl = __esm({
2571
2830
  }
2572
2831
  });
2573
2832
 
2574
- // packages/node-opcua-pki/lib/pki/toolbox_pfx.ts
2575
- import assert9 from "assert";
2833
+ // packages/node-opcua-pki/lib/ca/core/ca_database.ts
2576
2834
  import fs9 from "fs";
2577
- async function createPFX(options) {
2578
- const { certificateFile, privateKeyFile, outputFile, passphrase = "", caCertificateFiles } = options;
2579
- assert9(fs9.existsSync(certificateFile), `Certificate file does not exist: ${certificateFile}`);
2580
- assert9(fs9.existsSync(privateKeyFile), `Private key file does not exist: ${privateKeyFile}`);
2581
- let cmd = `pkcs12 -export`;
2582
- cmd += ` -in ${q3(n4(certificateFile))}`;
2583
- cmd += ` -inkey ${q3(n4(privateKeyFile))}`;
2584
- if (caCertificateFiles) {
2585
- for (const caFile of caCertificateFiles) {
2586
- assert9(fs9.existsSync(caFile), `CA certificate file does not exist: ${caFile}`);
2587
- cmd += ` -certfile ${q3(n4(caFile))}`;
2588
- }
2589
- }
2590
- cmd += ` -out ${q3(n4(outputFile))}`;
2591
- cmd += ` -passout pass:${passphrase}`;
2592
- await execute_openssl(cmd, {});
2835
+ import path6 from "path";
2836
+ import { convertPEMtoDER, readCertificatePEM } from "node-opcua-crypto";
2837
+ function parseOpenSSLDate(dateStr) {
2838
+ const raw = dateStr?.split(",")[0] ?? "";
2839
+ if (raw.length < 12) return "";
2840
+ const yy = parseInt(raw.substring(0, 2), 10);
2841
+ const year = yy >= 70 ? 1900 + yy : 2e3 + yy;
2842
+ const month = raw.substring(2, 4);
2843
+ const day = raw.substring(4, 6);
2844
+ const hour = raw.substring(6, 8);
2845
+ const min = raw.substring(8, 10);
2846
+ const sec = raw.substring(10, 12);
2847
+ return `${year}-${month}-${day}T${hour}:${min}:${sec}Z`;
2593
2848
  }
2594
- var q3, n4;
2595
- var init_toolbox_pfx = __esm({
2596
- "packages/node-opcua-pki/lib/pki/toolbox_pfx.ts"() {
2597
- "use strict";
2598
- init_esm_shims();
2599
- init_common();
2600
- init_common2();
2601
- init_execute_openssl();
2602
- q3 = quote;
2603
- n4 = makePath;
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)}`);
2604
2872
  }
2605
- });
2606
-
2607
- // packages/node-opcua-pki/lib/ca/templates/ca_config_template.cnf.ts
2608
- var config2, ca_config_template_cnf_default;
2609
- var init_ca_config_template_cnf = __esm({
2610
- "packages/node-opcua-pki/lib/ca/templates/ca_config_template.cnf.ts"() {
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"() {
2611
2878
  "use strict";
2612
2879
  init_esm_shims();
2613
- config2 = `#.........DO NOT MODIFY BY HAND .........................
2614
- [ ca ]
2615
- default_ca = CA_default
2616
- [ CA_default ]
2617
- dir = %%ROOT_FOLDER%% # the main CA folder
2618
- certs = $dir/certs # where to store certificates
2619
- new_certs_dir = $dir/certs #
2620
- database = $dir/index.txt # the certificate database
2621
- serial = $dir/serial # the serial number counter
2622
- certificate = $dir/public/cacert.pem # The root CA certificate
2623
- private_key = $dir/private/cakey.pem # the CA private key
2624
- x509_extensions = usr_cert #
2625
- default_days = 3650 # default validity : 10 years
2626
-
2627
- # default_md = sha1
2628
-
2629
- default_md = sha256 # The default digest algorithm
2630
-
2631
- preserve = no
2632
- policy = policy_match
2633
- # randfile = $dir/random.rnd
2634
- # default_startdate = YYMMDDHHMMSSZ
2635
- # default_enddate = YYMMDDHHMMSSZ
2636
- crl_dir = $dir/crl
2637
- crl_extensions = crl_ext
2638
- crl = $dir/revocation_list.crl # the Revocation list
2639
- crlnumber = $dir/crlnumber # CRL number file
2640
- default_crl_days = 30
2641
- default_crl_hours = 24
2642
- #msie_hack
2643
-
2644
- [ policy_match ]
2645
- countryName = optional
2646
- stateOrProvinceName = optional
2647
- localityName = optional
2648
- organizationName = optional
2649
- organizationalUnitName = optional
2650
- commonName = optional
2651
- emailAddress = optional
2652
-
2653
- [ req ]
2654
- default_bits = 4096 # Size of keys
2655
- default_keyfile = key.pem # name of generated keys
2656
- distinguished_name = req_distinguished_name
2657
- attributes = req_attributes
2658
- x509_extensions = v3_ca
2659
- #input_password
2660
- #output_password
2661
- string_mask = nombstr # permitted characters
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
2662
3736
  req_extensions = v3_req
2663
3737
 
2664
3738
  [ req_distinguished_name ]
@@ -2745,136 +3819,40 @@ authorityKeyIdentifier = keyid:always,issuer:always
2745
3819
  }
2746
3820
  });
2747
3821
 
2748
- // packages/node-opcua-pki/lib/ca/certificate_authority.ts
2749
- import assert10 from "assert";
2750
- import fs10 from "fs";
3822
+ // packages/node-opcua-pki/lib/ca/core/certificate_authority_core.ts
3823
+ import assert9 from "assert";
3824
+ import fs12 from "fs";
2751
3825
  import os4 from "os";
2752
- import path6 from "path";
3826
+ import path9 from "path";
3827
+ import { withLock as withLock2 } from "@ster5/global-mutex";
2753
3828
  import chalk6 from "chalk";
2754
3829
  import {
2755
- CertificatePurpose as CertificatePurpose2,
3830
+ CertificatePurpose as CertificatePurpose3,
2756
3831
  certificateMatchesPrivateKey,
2757
- convertPEMtoDER,
3832
+ convertPEMtoDER as convertPEMtoDER2,
3833
+ createCertificateSigningRequest as createCertificateSigningRequest3,
3834
+ createPfx,
2758
3835
  exploreCertificate as exploreCertificate2,
2759
3836
  exploreCertificateSigningRequest,
2760
3837
  generatePrivateKeyFile as generatePrivateKeyFile2,
2761
- readCertificatePEM,
3838
+ privateKeyToCryptoKey as privateKeyToCryptoKey2,
3839
+ readCertificatePEM as readCertificatePEM2,
2762
3840
  readCertificateSigningRequest,
2763
- readPrivateKey,
2764
- Subject as Subject4,
2765
- toPem as toPem2
3841
+ readPrivateKey as readPrivateKey3,
3842
+ Subject as Subject6,
3843
+ toPem as toPem2,
3844
+ verifyCertificateSignature as verifyCertificateSignature2,
3845
+ writePrivateKeyFile as writePrivateKeyFile2,
3846
+ x509 as x5092
2766
3847
  } from "node-opcua-crypto";
2767
- function octetStringToIpAddress(a) {
2768
- 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();
2769
- }
2770
- async function construct_CertificateAuthority(certificateAuthority) {
2771
- const subject = certificateAuthority.subject;
2772
- const caRootDir = path6.resolve(certificateAuthority.rootDir);
2773
- async function make_folders() {
2774
- mkdirRecursiveSync(caRootDir);
2775
- mkdirRecursiveSync(path6.join(caRootDir, "private"));
2776
- mkdirRecursiveSync(path6.join(caRootDir, "public"));
2777
- mkdirRecursiveSync(path6.join(caRootDir, "certs"));
2778
- mkdirRecursiveSync(path6.join(caRootDir, "crl"));
2779
- mkdirRecursiveSync(path6.join(caRootDir, "conf"));
2780
- }
2781
- await make_folders();
2782
- async function construct_default_files() {
2783
- const serial = path6.join(caRootDir, "serial");
2784
- if (!fs10.existsSync(serial)) {
2785
- await fs10.promises.writeFile(serial, "1000");
2786
- }
2787
- const crlNumber = path6.join(caRootDir, "crlnumber");
2788
- if (!fs10.existsSync(crlNumber)) {
2789
- await fs10.promises.writeFile(crlNumber, "1000");
2790
- }
2791
- const indexFile = path6.join(caRootDir, "index.txt");
2792
- if (!fs10.existsSync(indexFile)) {
2793
- await fs10.promises.writeFile(indexFile, "");
2794
- }
2795
- }
2796
- await construct_default_files();
2797
- const caKeyExists = fs10.existsSync(path6.join(caRootDir, "private/cakey.pem"));
2798
- const caCertExists = fs10.existsSync(path6.join(caRootDir, "public/cacert.pem"));
2799
- if (caKeyExists && caCertExists && !config3.forceCA) {
2800
- debugLog("CA private key and certificate already exist ... skipping");
2801
- return;
2802
- }
2803
- if (caKeyExists && !caCertExists) {
2804
- debugLog("CA private key exists but cacert.pem is missing \u2014 rebuilding CA");
2805
- fs10.unlinkSync(path6.join(caRootDir, "private/cakey.pem"));
2806
- const staleCsr = path6.join(caRootDir, "private/cakey.csr");
2807
- if (fs10.existsSync(staleCsr)) {
2808
- fs10.unlinkSync(staleCsr);
2809
- }
2810
- }
2811
- displayTitle("Create Certificate Authority (CA)");
2812
- const indexFileAttr = path6.join(caRootDir, "index.txt.attr");
2813
- if (!fs10.existsSync(indexFileAttr)) {
2814
- await fs10.promises.writeFile(indexFileAttr, "unique_subject = no");
2815
- }
2816
- const caConfigFile = certificateAuthority.configFile;
2817
- if (1) {
2818
- let data = configurationFileTemplate;
2819
- data = makePath(data.replace(/%%ROOT_FOLDER%%/, caRootDir));
2820
- await fs10.promises.writeFile(caConfigFile, data);
2821
- }
2822
- const subjectOpt = ` -subj "${subject.toString()}" `;
2823
- const caCommonName = subject.commonName || "NodeOPCUA-CA";
2824
- setEnv("ALTNAME", `URI:urn:${caCommonName}`);
2825
- certificateAuthority._wireRevocationEnvVars();
2826
- const options = { cwd: caRootDir };
2827
- const configFile = generateStaticConfig("conf/caconfig.cnf", options);
2828
- const configOption = ` -config ${q4(n5(configFile))}`;
2829
- const keySize = certificateAuthority.keySize;
2830
- const privateKeyFilename = path6.join(caRootDir, "private/cakey.pem");
2831
- const csrFilename = path6.join(caRootDir, "private/cakey.csr");
2832
- displayTitle(`Generate the CA private Key - ${keySize}`);
2833
- await generatePrivateKeyFile2(privateKeyFilename, keySize);
2834
- displayTitle("Generate a certificate request for the CA key");
2835
- await execute_openssl(
2836
- "req -new -sha256 -text -extensions v3_ca_req" + configOption + " -key " + q4(n5(privateKeyFilename)) + " -out " + q4(n5(csrFilename)) + " " + subjectOpt,
2837
- options
2838
- );
2839
- const issuerCA = certificateAuthority._issuerCA;
2840
- if (issuerCA) {
2841
- displayTitle("Generate CA Certificate (signed by issuer CA)");
2842
- const issuerCert = path6.resolve(issuerCA.caCertificate);
2843
- const issuerKey = path6.resolve(issuerCA.rootDir, "private/cakey.pem");
2844
- const issuerSerial = path6.resolve(issuerCA.rootDir, "serial");
2845
- await execute_openssl(
2846
- " x509 -sha256 -req -days 3650 -text -extensions v3_ca -extfile " + q4(n5(configFile)) + " -in private/cakey.csr -CA " + q4(n5(issuerCert)) + " -CAkey " + q4(n5(issuerKey)) + " -CAserial " + q4(n5(issuerSerial)) + " -out public/cacert.pem",
2847
- options
2848
- );
2849
- } else {
2850
- displayTitle("Generate CA Certificate (self-signed)");
2851
- await execute_openssl(
2852
- " x509 -sha256 -req -days 3650 -text -extensions v3_ca -extfile " + q4(n5(configFile)) + " -in private/cakey.csr -signkey " + q4(n5(privateKeyFilename)) + " -out public/cacert.pem",
2853
- options
2854
- );
2855
- }
2856
- displaySubtitle("generate initial CRL (Certificate Revocation List)");
2857
- await regenerateCrl(certificateAuthority.revocationList, configOption, options);
2858
- displayTitle("Create Certificate Authority (CA) ---> DONE");
3848
+ function escapeOpensslConfDoubleQuoted(value) {
3849
+ return value.replace(/\\/g, "\\\\").replace(/"/g, '\\"');
2859
3850
  }
2860
- async function regenerateCrl(revocationList, configOption, options) {
2861
- displaySubtitle("regenerate CRL (Certificate Revocation List)");
2862
- await execute_openssl(`ca -gencrl ${configOption} -out crl/revocation_list.crl`, options);
2863
- await execute_openssl("crl -in crl/revocation_list.crl -out crl/revocation_list.der -outform der", options);
2864
- displaySubtitle("Display (Certificate Revocation List)");
2865
- await execute_openssl(`crl -in ${q4(n5(revocationList))} -text -noout`, options);
3851
+ function renderCaConfig(caRootDir) {
3852
+ return configurationFileTemplate.replace(/%%ROOT_FOLDER%%/, escapeOpensslConfDoubleQuoted(makePath(caRootDir)));
2866
3853
  }
2867
- function parseOpenSSLDate(dateStr) {
2868
- const raw = dateStr?.split(",")[0] ?? "";
2869
- if (raw.length < 12) return "";
2870
- const yy = parseInt(raw.substring(0, 2), 10);
2871
- const year = yy >= 70 ? 1900 + yy : 2e3 + yy;
2872
- const month = raw.substring(2, 4);
2873
- const day = raw.substring(4, 6);
2874
- const hour = raw.substring(6, 8);
2875
- const min = raw.substring(8, 10);
2876
- const sec = raw.substring(10, 12);
2877
- return `${year}-${month}-${day}T${hour}:${min}:${sec}Z`;
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();
2878
3856
  }
2879
3857
  function validateRevocationUrl(url, fieldName) {
2880
3858
  if (url === void 0) {
@@ -2903,28 +3881,25 @@ function validateRevocationUrl(url, fieldName) {
2903
3881
  }
2904
3882
  return url;
2905
3883
  }
2906
- var defaultSubject, configurationFileTemplate, configurationFileSimpleTemplate2, config3, n5, q4, CertificateAuthority;
2907
- var init_certificate_authority = __esm({
2908
- "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"() {
2909
3887
  "use strict";
2910
3888
  init_esm_shims();
2911
- init_toolbox_pfx();
2912
3889
  init_toolbox();
2913
- init_with_openssl();
2914
- init_simple_config_template_cnf();
3890
+ init_ca_database();
2915
3891
  init_ca_config_template_cnf();
2916
3892
  defaultSubject = "/C=FR/ST=IDF/L=Paris/O=Local NODE-OPCUA Certificate Authority/CN=NodeOPCUA-CA";
2917
3893
  configurationFileTemplate = ca_config_template_cnf_default;
2918
- configurationFileSimpleTemplate2 = simple_config_template_cnf_default;
2919
3894
  config3 = {
2920
3895
  certificateDir: "INVALID",
2921
3896
  forceCA: false,
2922
3897
  pkiDir: "INVALID"
2923
3898
  };
2924
- n5 = makePath;
2925
- q4 = quote;
2926
- assert10(octetStringToIpAddress("c07b9179") === "192.123.145.121");
2927
- 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 {
2928
3903
  /** RSA key size used when generating the CA private key. */
2929
3904
  keySize;
2930
3905
  /** Root filesystem path of the CA directory structure. */
@@ -2937,13 +3912,45 @@ var init_certificate_authority = __esm({
2937
3912
  _crlDistributionUrl;
2938
3913
  _ocspResponderUrl;
2939
3914
  _caIssuersUrl;
3915
+ #privateKeyPassphrase;
3916
+ /** resolved once (see `privateKeyPassphrase`); `#passphraseResolved` distinguishes "none" from "not yet" */
3917
+ #resolvedPassphrase;
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;
2940
3925
  constructor(options) {
2941
- assert10(Object.prototype.hasOwnProperty.call(options, "location"));
2942
- assert10(Object.prototype.hasOwnProperty.call(options, "keySize"));
3926
+ assert9(Object.prototype.hasOwnProperty.call(options, "location"));
3927
+ assert9(Object.prototype.hasOwnProperty.call(options, "keySize"));
2943
3928
  this.location = options.location;
2944
3929
  this.keySize = options.keySize || 2048;
2945
- this.subject = new Subject4(options.subject || defaultSubject);
3930
+ this.subject = new Subject6(options.subject || defaultSubject);
2946
3931
  this._issuerCA = options.issuerCA;
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);
2947
3954
  if (options.crlDistributionUrl !== void 0) {
2948
3955
  this.setCrlDistributionUrl(options.crlDistributionUrl);
2949
3956
  }
@@ -3007,38 +4014,159 @@ var init_certificate_authority = __esm({
3007
4014
  setCaIssuersUrl(url) {
3008
4015
  this._caIssuersUrl = validateRevocationUrl(url, "caIssuersUrl");
3009
4016
  }
4017
+ /** Absolute path to the CA root directory (alias for {@link location}). */
4018
+ get rootDir() {
4019
+ return this.location;
4020
+ }
4021
+ /** Path to the OpenSSL configuration file (`conf/caconfig.cnf`). */
4022
+ get configFile() {
4023
+ return path9.normalize(path9.join(this.rootDir, "./conf/caconfig.cnf"));
4024
+ }
4025
+ /** Path to the CA private key (`private/cakey.pem`); may be passphrase-encrypted, see {@link getPrivateKey}. */
4026
+ get privateKey() {
4027
+ return path9.join(path9.resolve(this.rootDir), "private/cakey.pem");
4028
+ }
3010
4029
  /**
3011
- * @internal
3012
- * Populate the OpenSSL config substitution env vars (`CDP_URL` and
3013
- * `AIA_VALUE`) from the configured URLs, or unset them so the
3014
- * matching `{{#KEY}}...{{/KEY}}` blocks in the templates are
3015
- * stripped. MUST be called before every `generateStaticConfig`
3016
- * invocation that signs a certificate.
4030
+ * The CA private key, decrypted with the configured `privateKeyPassphrase`
4031
+ * if it is encrypted. Fails closed (`PrivateKeyPassphraseRequiredError`)
4032
+ * on an encrypted key with no or the wrong passphrase.
3017
4033
  */
3018
- _wireRevocationEnvVars() {
3019
- unsetEnv("CDP_URL");
3020
- unsetEnv("AIA_VALUE");
3021
- if (this._crlDistributionUrl) {
3022
- setEnv("CDP_URL", this._crlDistributionUrl);
4034
+ async getPrivateKey() {
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
+ );
3023
4066
  }
3024
- const aiaLegs = [];
3025
- if (this._ocspResponderUrl) {
3026
- aiaLegs.push(`OCSP;URI:${this._ocspResponderUrl}`);
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;
3027
4078
  }
3028
- if (this._caIssuersUrl) {
3029
- aiaLegs.push(`caIssuers;URI:${this._caIssuersUrl}`);
4079
+ return privateKeyToCryptoKey2(await this.getPrivateKey());
4080
+ }
4081
+ /**
4082
+ * Enable, disable, or rotate the passphrase protecting `private/cakey.pem`
4083
+ * (temp file + atomic rename, temp file removed on failure). Only
4084
+ * rewrites the file: construct a new `CertificateAuthority` with the new
4085
+ * passphrase to continue using it.
4086
+ */
4087
+ async reencryptPrivateKey(oldPassphrase, newPassphrase) {
4088
+ this.#assertKeyIsOnDisk("reencryptPrivateKey");
4089
+ const oldPass = await resolvePrivateKeyPassphrase(oldPassphrase);
4090
+ const newPass = await resolvePrivateKeyPassphrase(newPassphrase);
4091
+ const key = readPrivateKey3(this.privateKey, oldPass);
4092
+ await this.#rewritePrivateKeyFile(key, newPass);
4093
+ }
4094
+ async #rewritePrivateKeyFile(privateKey, passphrase) {
4095
+ const tmpFilename = `${this.privateKey}.${process.pid}-${Date.now()}.tmp`;
4096
+ try {
4097
+ await writePrivateKeyFile2(tmpFilename, privateKey, { passphrase });
4098
+ await fs12.promises.rename(tmpFilename, this.privateKey);
4099
+ } finally {
4100
+ await fs12.promises.rm(tmpFilename, { force: true });
3030
4101
  }
3031
- if (aiaLegs.length > 0) {
3032
- setEnv("AIA_VALUE", aiaLegs.join(","));
4102
+ }
4103
+ /** @internal resolve the configured passphrase, at most once per instance */
4104
+ async _privateKeyPassphrase() {
4105
+ if (!this.#passphraseResolved) {
4106
+ this.#resolvedPassphrase = await resolvePrivateKeyPassphrase(this.#privateKeyPassphrase);
4107
+ this.#passphraseResolved = true;
3033
4108
  }
4109
+ return this.#resolvedPassphrase;
3034
4110
  }
3035
- /** Absolute path to the CA root directory (alias for {@link location}). */
3036
- get rootDir() {
3037
- return this.location;
4111
+ /**
4112
+ * @internal On an existing key: encrypt it in place if a passphrase is
4113
+ * configured and it is still plaintext (secure by default: the option
4114
+ * means "protect this key", not "ignore me"), then read it back so a
4115
+ * wrong or missing passphrase fails initialize() closed rather than the
4116
+ * first signing operation.
4117
+ */
4118
+ async _ensurePrivateKeyProtection() {
4119
+ if (this.#signer || !fs12.existsSync(this.privateKey)) {
4120
+ return;
4121
+ }
4122
+ if (this.#privateKeyPassphrase !== void 0 && !isEncryptedPrivateKeyFile(this.privateKey)) {
4123
+ warningLog("CertificateAuthority: private key is plaintext but a passphrase is configured; encrypting it in place");
4124
+ const plaintextKey = readPrivateKey3(this.privateKey);
4125
+ await this.#rewritePrivateKeyFile(plaintextKey, await this._privateKeyPassphrase());
4126
+ }
4127
+ await this.getPrivateKey();
3038
4128
  }
3039
- /** Path to the OpenSSL configuration file (`conf/caconfig.cnf`). */
3040
- get configFile() {
3041
- return path6.normalize(path6.join(this.rootDir, "./conf/caconfig.cnf"));
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);
3042
4170
  }
3043
4171
  /** Path to the CA certificate in PEM format (`public/cacert.pem`). */
3044
4172
  get caCertificate() {
@@ -3087,8 +4215,8 @@ var init_certificate_authority = __esm({
3087
4215
  * (call {@link initialize} first).
3088
4216
  */
3089
4217
  getCACertificateDER() {
3090
- const pem = readCertificatePEM(this.caCertificate);
3091
- return convertPEMtoDER(pem);
4218
+ const pem = readCertificatePEM2(this.caCertificate);
4219
+ return convertPEMtoDER2(pem);
3092
4220
  }
3093
4221
  /**
3094
4222
  * Return the CA certificate as a PEM-encoded string.
@@ -3097,7 +4225,7 @@ var init_certificate_authority = __esm({
3097
4225
  * (call {@link initialize} first).
3098
4226
  */
3099
4227
  getCACertificatePEM() {
3100
- const raw = readCertificatePEM(this.caCertificate);
4228
+ const raw = readCertificatePEM2(this.caCertificate);
3101
4229
  const beginMarker = "-----BEGIN CERTIFICATE-----";
3102
4230
  const idx = raw.indexOf(beginMarker);
3103
4231
  if (idx > 0) {
@@ -3113,10 +4241,10 @@ var init_certificate_authority = __esm({
3113
4241
  */
3114
4242
  getCRLDER() {
3115
4243
  const crlPath = this.revocationListDER;
3116
- if (!fs10.existsSync(crlPath)) {
4244
+ if (!fs12.existsSync(crlPath)) {
3117
4245
  return Buffer.alloc(0);
3118
4246
  }
3119
- return fs10.readFileSync(crlPath);
4247
+ return fs12.readFileSync(crlPath);
3120
4248
  }
3121
4249
  /**
3122
4250
  * Return the current Certificate Revocation List as a
@@ -3126,10 +4254,10 @@ var init_certificate_authority = __esm({
3126
4254
  */
3127
4255
  getCRLPEM() {
3128
4256
  const crlPath = this.revocationList;
3129
- if (!fs10.existsSync(crlPath)) {
4257
+ if (!fs12.existsSync(crlPath)) {
3130
4258
  return "";
3131
4259
  }
3132
- const raw = fs10.readFileSync(crlPath, "utf-8");
4260
+ const raw = fs12.readFileSync(crlPath, "utf-8");
3133
4261
  const beginMarker = "-----BEGIN X509 CRL-----";
3134
4262
  const idx = raw.indexOf(beginMarker);
3135
4263
  if (idx > 0) {
@@ -3148,14 +4276,14 @@ var init_certificate_authority = __esm({
3148
4276
  * expiry date, and (for revoked certs) the revocation date.
3149
4277
  */
3150
4278
  getIssuedCertificates() {
3151
- return this._parseIndexTxt();
4279
+ return this.#db.readIndex();
3152
4280
  }
3153
4281
  /**
3154
4282
  * Return the total number of certificates recorded in
3155
4283
  * `index.txt`.
3156
4284
  */
3157
4285
  getIssuedCertificateCount() {
3158
- return this._parseIndexTxt().length;
4286
+ return this.#db.readIndex().length;
3159
4287
  }
3160
4288
  /**
3161
4289
  * Return the status of a certificate by its serial number.
@@ -3165,9 +4293,7 @@ var init_certificate_authority = __esm({
3165
4293
  * `undefined` if not found
3166
4294
  */
3167
4295
  getCertificateStatus(serial) {
3168
- const upper = serial.toUpperCase();
3169
- const record = this._parseIndexTxt().find((r) => r.serial.toUpperCase() === upper);
3170
- return record?.status;
4296
+ return this.#db.findBySerial(serial)?.status;
3171
4297
  }
3172
4298
  /**
3173
4299
  * Read a specific issued certificate by serial number and
@@ -3180,82 +4306,13 @@ var init_certificate_authority = __esm({
3180
4306
  * @returns the DER buffer, or `undefined` if not found
3181
4307
  */
3182
4308
  getCertificateBySerial(serial) {
3183
- const upper = serial.toUpperCase();
3184
- const certFile = path6.join(this.rootDir, "certs", `${upper}.pem`);
3185
- if (!fs10.existsSync(certFile)) {
3186
- return void 0;
3187
- }
3188
- const pem = readCertificatePEM(certFile);
3189
- return convertPEMtoDER(pem);
4309
+ return this.#db.getCertificateBySerial(serial);
3190
4310
  }
3191
4311
  /**
3192
4312
  * Path to the OpenSSL certificate database file.
3193
4313
  */
3194
4314
  get indexFile() {
3195
- return path6.join(this.rootDir, "index.txt");
3196
- }
3197
- /**
3198
- * Parse the OpenSSL `index.txt` certificate database.
3199
- *
3200
- * Each line has tab-separated fields:
3201
- * ```
3202
- * status expiry [revocationDate] serial unknown subject
3203
- * ```
3204
- *
3205
- * - status: `V` (valid), `R` (revoked), `E` (expired)
3206
- * - expiry: `YYMMDDHHmmssZ`
3207
- * - revocationDate: present only for revoked certs
3208
- * - serial: hex string
3209
- * - unknown: always `"unknown"`
3210
- * - subject: X.500 slash-delimited string
3211
- */
3212
- _parseIndexTxt() {
3213
- const indexPath = this.indexFile;
3214
- if (!fs10.existsSync(indexPath)) {
3215
- return [];
3216
- }
3217
- const content = fs10.readFileSync(indexPath, "utf-8");
3218
- const lines = content.split("\n").filter((l) => l.trim().length > 0);
3219
- const records = [];
3220
- for (const line of lines) {
3221
- const fields = line.split(" ");
3222
- if (fields.length < 4) continue;
3223
- const statusChar = fields[0];
3224
- const expiryStr = fields[1];
3225
- let serial;
3226
- let subject;
3227
- let revocationDate;
3228
- if (statusChar === "R") {
3229
- revocationDate = fields[2];
3230
- serial = fields[3];
3231
- subject = fields.length >= 6 ? fields[5] : "";
3232
- } else {
3233
- serial = fields[3];
3234
- subject = fields.length >= 6 ? fields[5] : "";
3235
- }
3236
- let status;
3237
- switch (statusChar) {
3238
- case "V":
3239
- status = "valid";
3240
- break;
3241
- case "R":
3242
- status = "revoked";
3243
- break;
3244
- case "E":
3245
- status = "expired";
3246
- break;
3247
- default:
3248
- continue;
3249
- }
3250
- records.push({
3251
- serial,
3252
- status,
3253
- subject,
3254
- expiryDate: parseOpenSSLDate(expiryStr),
3255
- revocationDate: revocationDate ? parseOpenSSLDate(revocationDate) : void 0
3256
- });
3257
- }
3258
- return records;
4315
+ return this.#db.indexFile;
3259
4316
  }
3260
4317
  // ---------------------------------------------------------------
3261
4318
  // Buffer-based CA operations (US-058)
@@ -3277,12 +4334,12 @@ var init_certificate_authority = __esm({
3277
4334
  * @returns the signed certificate as a DER-encoded buffer
3278
4335
  */
3279
4336
  async signCertificateRequestFromDER(csrDer, options) {
3280
- 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-"));
3281
4338
  try {
3282
- const csrFile = path6.join(tmpDir, "request.csr");
3283
- const certFile = path6.join(tmpDir, "certificate.pem");
4339
+ const csrFile = path9.join(tmpDir, "request.csr");
4340
+ const certFile = path9.join(tmpDir, "certificate.pem");
3284
4341
  const csrPem = toPem2(csrDer, "CERTIFICATE REQUEST");
3285
- await fs10.promises.writeFile(csrFile, csrPem, "utf-8");
4342
+ await fs12.promises.writeFile(csrFile, csrPem, "utf-8");
3286
4343
  const signingParams = {};
3287
4344
  if (options?.validityMs !== void 0) signingParams.validityMs = options.validityMs;
3288
4345
  else signingParams.validity = options?.validity ?? 365;
@@ -3292,10 +4349,10 @@ var init_certificate_authority = __esm({
3292
4349
  if (options?.applicationUri) signingParams.applicationUri = options.applicationUri;
3293
4350
  if (options?.subject) signingParams.subject = options.subject;
3294
4351
  await this.signCertificateRequest(certFile, csrFile, signingParams);
3295
- const certPem = readCertificatePEM(certFile);
3296
- return convertPEMtoDER(certPem);
4352
+ const certPem = readCertificatePEM2(certFile);
4353
+ return convertPEMtoDER2(certPem);
3297
4354
  } finally {
3298
- await fs10.promises.rm(tmpDir, {
4355
+ await fs12.promises.rm(tmpDir, {
3299
4356
  recursive: true,
3300
4357
  force: true
3301
4358
  });
@@ -3345,27 +4402,35 @@ var init_certificate_authority = __esm({
3345
4402
  * @returns `{ certificateDer, privateKey }` — certificate as DER,
3346
4403
  * private key as a branded `PrivateKey` buffer
3347
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
+ }
3348
4427
  async generateKeyPairAndSignDER(options) {
3349
4428
  const keySize = options.keySize ?? 2048;
3350
4429
  const startDate = options.startDate ?? /* @__PURE__ */ new Date();
3351
- 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-"));
3352
4431
  try {
3353
- const privateKeyFile = path6.join(tmpDir, "private_key.pem");
3354
- await generatePrivateKeyFile2(privateKeyFile, keySize);
3355
- const configFile = path6.join(tmpDir, "openssl.cnf");
3356
- await fs10.promises.writeFile(configFile, configurationFileSimpleTemplate2, "utf-8");
3357
- const csrFile = path6.join(tmpDir, "request.csr");
3358
- await createCertificateSigningRequestWithOpenSSL(csrFile, {
3359
- rootDir: tmpDir,
3360
- configFile,
3361
- privateKey: privateKeyFile,
3362
- applicationUri: options.applicationUri,
3363
- subject: options.subject,
3364
- dns: options.dns ?? [],
3365
- ip: options.ip ?? [],
3366
- purpose: CertificatePurpose2.ForApplication
3367
- });
3368
- 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");
3369
4434
  const signingParams = {
3370
4435
  applicationUri: options.applicationUri,
3371
4436
  dns: options.dns,
@@ -3375,12 +4440,12 @@ var init_certificate_authority = __esm({
3375
4440
  if (options.validityMs !== void 0) signingParams.validityMs = options.validityMs;
3376
4441
  else signingParams.validity = options.validity ?? 365;
3377
4442
  await this.signCertificateRequest(certFile, csrFile, signingParams);
3378
- const certPem = readCertificatePEM(certFile);
3379
- const certificateDer = convertPEMtoDER(certPem);
3380
- const privateKey = readPrivateKey(privateKeyFile);
4443
+ const certPem = readCertificatePEM2(certFile);
4444
+ const certificateDer = convertPEMtoDER2(certPem);
4445
+ const privateKey = readPrivateKey3(privateKeyFile);
3381
4446
  return { certificateDer, privateKey };
3382
4447
  } finally {
3383
- await fs10.promises.rm(tmpDir, {
4448
+ await fs12.promises.rm(tmpDir, {
3384
4449
  recursive: true,
3385
4450
  force: true
3386
4451
  });
@@ -3401,24 +4466,10 @@ var init_certificate_authority = __esm({
3401
4466
  const keySize = options.keySize ?? 2048;
3402
4467
  const startDate = options.startDate ?? /* @__PURE__ */ new Date();
3403
4468
  const passphrase = options.passphrase ?? "";
3404
- 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-"));
3405
4470
  try {
3406
- const privateKeyFile = path6.join(tmpDir, "private_key.pem");
3407
- await generatePrivateKeyFile2(privateKeyFile, keySize);
3408
- const configFile = path6.join(tmpDir, "openssl.cnf");
3409
- await fs10.promises.writeFile(configFile, configurationFileSimpleTemplate2, "utf-8");
3410
- const csrFile = path6.join(tmpDir, "request.csr");
3411
- await createCertificateSigningRequestWithOpenSSL(csrFile, {
3412
- rootDir: tmpDir,
3413
- configFile,
3414
- privateKey: privateKeyFile,
3415
- applicationUri: options.applicationUri,
3416
- subject: options.subject,
3417
- dns: options.dns ?? [],
3418
- ip: options.ip ?? [],
3419
- purpose: CertificatePurpose2.ForApplication
3420
- });
3421
- 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");
3422
4473
  const signingParams = {
3423
4474
  applicationUri: options.applicationUri,
3424
4475
  dns: options.dns,
@@ -3428,17 +4479,20 @@ var init_certificate_authority = __esm({
3428
4479
  if (options.validityMs !== void 0) signingParams.validityMs = options.validityMs;
3429
4480
  else signingParams.validity = options.validity ?? 365;
3430
4481
  await this.signCertificateRequest(certFile, csrFile, signingParams);
3431
- const pfxFile = path6.join(tmpDir, "bundle.pfx");
3432
- await createPFX({
3433
- certificateFile: certFile,
3434
- privateKeyFile,
3435
- outputFile: pfxFile,
3436
- passphrase,
3437
- 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
3438
4493
  });
3439
- return await fs10.promises.readFile(pfxFile);
3440
4494
  } finally {
3441
- await fs10.promises.rm(tmpDir, {
4495
+ await fs12.promises.rm(tmpDir, {
3442
4496
  recursive: true,
3443
4497
  force: true
3444
4498
  });
@@ -3459,8 +4513,8 @@ var init_certificate_authority = __esm({
3459
4513
  async revokeCertificateDER(certDer, reason) {
3460
4514
  const info = exploreCertificate2(certDer);
3461
4515
  const serial = info.tbsCertificate.serialNumber.replace(/:/g, "").toUpperCase();
3462
- const storedCertFile = path6.join(this.rootDir, "certs", `${serial}.pem`);
3463
- if (!fs10.existsSync(storedCertFile)) {
4516
+ const storedCertFile = path9.join(this.rootDir, "certs", `${serial}.pem`);
4517
+ if (!fs12.existsSync(storedCertFile)) {
3464
4518
  throw new Error(`Cannot revoke: no stored certificate found for serial ${serial} at ${storedCertFile}`);
3465
4519
  }
3466
4520
  await this.revokeCertificate(storedCertFile, {
@@ -3473,7 +4527,69 @@ var init_certificate_authority = __esm({
3473
4527
  * already exist.
3474
4528
  */
3475
4529
  async initialize() {
3476
- 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);
3477
4593
  }
3478
4594
  /**
3479
4595
  * Initialize the CA directory structure and generate the
@@ -3495,16 +4611,25 @@ var init_certificate_authority = __esm({
3495
4611
  * @returns an {@link InitializeCSRResult} describing the CA state
3496
4612
  */
3497
4613
  async initializeCSR() {
3498
- const caRootDir = path6.resolve(this.rootDir);
4614
+ const caRootDir = path9.resolve(this.rootDir);
3499
4615
  mkdirRecursiveSync(caRootDir);
3500
- for (const dir of ["private", "public", "certs", "crl", "conf"]) {
3501
- mkdirRecursiveSync(path6.join(caRootDir, dir));
4616
+ return this.#withCaLock(() => this.#initializeCSRLocked(caRootDir));
4617
+ }
4618
+ async #initializeCSRLocked(caRootDir) {
4619
+ for (const dir of ["public", "certs", "crl", "conf"]) {
4620
+ mkdirRecursiveSync(path9.join(caRootDir, dir));
3502
4621
  }
4622
+ ensurePrivateDirectory(path9.join(caRootDir, "private"));
3503
4623
  const caCertFile = this.caCertificate;
3504
- const privateKeyFile = path6.join(caRootDir, "private/cakey.pem");
3505
- const csrFile = path6.join(caRootDir, "private/cakey.csr");
3506
- if (fs10.existsSync(caCertFile)) {
3507
- const certDer = convertPEMtoDER(readCertificatePEM(caCertFile));
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)) {
4628
+ restrictPrivateFilePermissions(privateKeyFile, 384);
4629
+ await this._ensurePrivateKeyProtection();
4630
+ }
4631
+ if (fs12.existsSync(caCertFile)) {
4632
+ const certDer = convertPEMtoDER2(readCertificatePEM2(caCertFile));
3508
4633
  const certInfo = exploreCertificate2(certDer);
3509
4634
  const notAfter = certInfo.tbsCertificate.validity.notAfter;
3510
4635
  if (notAfter.getTime() < Date.now()) {
@@ -3515,32 +4640,31 @@ var init_certificate_authority = __esm({
3515
4640
  debugLog("CA certificate already exists and is valid \u2014 ready");
3516
4641
  return { status: "ready" };
3517
4642
  }
3518
- if (fs10.existsSync(privateKeyFile) && fs10.existsSync(csrFile)) {
4643
+ if (keyAvailable && fs12.existsSync(csrFile)) {
3519
4644
  debugLog("CA key + CSR already exist \u2014 pending external signing");
3520
4645
  return { status: "pending", csrPath: csrFile };
3521
4646
  }
3522
- const serial = path6.join(caRootDir, "serial");
3523
- if (!fs10.existsSync(serial)) {
3524
- 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");
3525
4650
  }
3526
- const crlNumber = path6.join(caRootDir, "crlnumber");
3527
- if (!fs10.existsSync(crlNumber)) {
3528
- 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");
3529
4654
  }
3530
- const indexFile = path6.join(caRootDir, "index.txt");
3531
- if (!fs10.existsSync(indexFile)) {
3532
- await fs10.promises.writeFile(indexFile, "");
4655
+ const indexFile = path9.join(caRootDir, "index.txt");
4656
+ if (!fs12.existsSync(indexFile)) {
4657
+ await fs12.promises.writeFile(indexFile, "");
3533
4658
  }
3534
- const indexFileAttr = path6.join(caRootDir, "index.txt.attr");
3535
- if (!fs10.existsSync(indexFileAttr)) {
3536
- 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");
3537
4662
  }
3538
4663
  const caConfigFile = this.configFile;
3539
- let data = configurationFileTemplate;
3540
- data = makePath(data.replace(/%%ROOT_FOLDER%%/, caRootDir));
3541
- await fs10.promises.writeFile(caConfigFile, data);
3542
- if (!fs10.existsSync(privateKeyFile)) {
3543
- await generatePrivateKeyFile2(privateKeyFile, this.keySize);
4664
+ await fs12.promises.writeFile(caConfigFile, renderCaConfig(caRootDir));
4665
+ if (!keyAvailable) {
4666
+ await generatePrivateKeyFile2(privateKeyFile, this.keySize, { passphrase: await this._privateKeyPassphrase() });
4667
+ restrictPrivateFilePermissions(privateKeyFile, 384);
3544
4668
  }
3545
4669
  await this._generateCSR(caRootDir, privateKeyFile, csrFile);
3546
4670
  return { status: "created", csrPath: csrFile };
@@ -3559,38 +4683,34 @@ var init_certificate_authority = __esm({
3559
4683
  * renewal is needed, `"ready"` if the cert is still valid
3560
4684
  */
3561
4685
  async renewCSR(thresholdDays = 30) {
3562
- const caRootDir = path6.resolve(this.rootDir);
4686
+ const caRootDir = path9.resolve(this.rootDir);
3563
4687
  const caCertFile = this.caCertificate;
3564
- const privateKeyFile = path6.join(caRootDir, "private/cakey.pem");
3565
- const csrFile = path6.join(caRootDir, "private/cakey.csr");
3566
- 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)) {
3567
4691
  return this.initializeCSR();
3568
4692
  }
3569
- const certDer = convertPEMtoDER(readCertificatePEM(caCertFile));
4693
+ const certDer = convertPEMtoDER2(readCertificatePEM2(caCertFile));
3570
4694
  const certInfo = exploreCertificate2(certDer);
3571
4695
  const notAfter = certInfo.tbsCertificate.validity.notAfter;
3572
4696
  const thresholdMs = thresholdDays * 24 * 60 * 60 * 1e3;
3573
4697
  if (notAfter.getTime() - Date.now() < thresholdMs) {
3574
4698
  debugLog(`CA certificate expires within ${thresholdDays} days \u2014 generating renewal CSR`);
3575
- await this._generateCSR(caRootDir, privateKeyFile, csrFile);
4699
+ await this.#withCaLock(() => this._generateCSR(caRootDir, privateKeyFile, csrFile));
3576
4700
  return { status: "expired", csrPath: csrFile, expiryDate: notAfter };
3577
4701
  }
3578
4702
  return { status: "ready" };
3579
4703
  }
3580
4704
  /**
3581
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.
3582
4710
  * @internal
3583
4711
  */
3584
4712
  async _generateCSR(caRootDir, privateKeyFile, csrFile) {
3585
- const subjectOpt = ` -subj "${this.subject.toString()}" `;
3586
- processAltNames({});
3587
- const options = { cwd: caRootDir };
3588
- const configFile = generateStaticConfig("conf/caconfig.cnf", options);
3589
- const configOption = ` -config ${q4(n5(configFile))}`;
3590
- await execute_openssl(
3591
- "req -new -sha256 -text -extensions v3_ca_req" + configOption + " -key " + q4(n5(privateKeyFile)) + " -out " + q4(n5(csrFile)) + " " + subjectOpt,
3592
- options
3593
- );
4713
+ await this.#backend.generateCaCsr(this, caRootDir, privateKeyFile, csrFile);
3594
4714
  }
3595
4715
  /**
3596
4716
  * Install an externally-signed CA certificate and generate
@@ -3609,45 +4729,41 @@ var init_certificate_authority = __esm({
3609
4729
  * `status: "success"` or `status: "error"` and a `reason`
3610
4730
  */
3611
4731
  async installCACertificate(signedCertFile) {
3612
- const caRootDir = path6.resolve(this.rootDir);
3613
- const caCertFile = this.caCertificate;
3614
- const privateKeyFile = path6.join(caRootDir, "private/cakey.pem");
3615
- const fullPem = await fs10.promises.readFile(signedCertFile, "utf8");
3616
- const pemBlocks = fullPem.match(/-----BEGIN CERTIFICATE-----[\s\S]*?-----END CERTIFICATE-----/g);
3617
- if (!pemBlocks || pemBlocks.length === 0) {
3618
- return {
3619
- status: "error",
3620
- reason: "no_certificate_found",
3621
- message: "The provided file does not contain any PEM-encoded certificate."
3622
- };
3623
- }
3624
- const certDer = convertPEMtoDER(pemBlocks[0]);
3625
- const privateKey = readPrivateKey(privateKeyFile);
3626
- if (!certificateMatchesPrivateKey(certDer, privateKey)) {
3627
- return {
3628
- status: "error",
3629
- reason: "certificate_key_mismatch",
3630
- message: "The provided certificate does not match the CA private key. Ensure the certificate was signed from the CSR generated by initializeCSR()."
3631
- };
3632
- }
3633
- 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]}
3634
4752
  `);
3635
- const issuerChainFile = this.issuerCertificateChain;
3636
- if (pemBlocks.length > 1) {
3637
- 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")}
3638
4756
  `;
3639
- await fs10.promises.writeFile(issuerChainFile, issuerPem);
3640
- debugLog(`Stored ${pemBlocks.length - 1} issuer certificate(s) in issuer_chain.pem`);
3641
- } else {
3642
- if (fs10.existsSync(issuerChainFile)) {
3643
- 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
+ }
3644
4763
  }
3645
- }
3646
- const options = { cwd: caRootDir };
3647
- const configFile = generateStaticConfig("conf/caconfig.cnf", options);
3648
- const configOption = ` -config ${q4(n5(configFile))}`;
3649
- await regenerateCrl(this.revocationList, configOption, options);
3650
- return { status: "success" };
4764
+ await this.#backend.regenerateCrl(this);
4765
+ return { status: "success" };
4766
+ });
3651
4767
  }
3652
4768
  /**
3653
4769
  * Sign a CSR with CA extensions (`v3_ca`), producing a
@@ -3663,16 +4779,11 @@ var init_certificate_authority = __esm({
3663
4779
  * @param params - signing parameters
3664
4780
  */
3665
4781
  async signCACertificateRequest(certFile, csrFile, params) {
3666
- const caRootDir = path6.resolve(this.rootDir);
3667
- const options = { cwd: caRootDir };
3668
- this._wireRevocationEnvVars();
3669
- const configFile = generateStaticConfig("conf/caconfig.cnf", options);
3670
- const validity = params.validity ?? 3650;
3671
- await execute_openssl(
3672
- ` x509 -sha256 -req -days ${validity} -text -extensions v3_ca -extfile ` + q4(n5(configFile)) + " -in " + q4(n5(csrFile)) + " -CA " + q4(n5(this.caCertificate)) + " -CAkey " + q4(n5(path6.join(caRootDir, "private/cakey.pem"))) + " -CAserial " + q4(n5(path6.join(caRootDir, "serial"))) + " -out " + q4(n5(certFile)),
3673
- options
3674
- );
3675
- 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
+ });
3676
4787
  }
3677
4788
  /**
3678
4789
  * Rebuild the combined CA certificate + CRL file.
@@ -3683,13 +4794,13 @@ var init_certificate_authority = __esm({
3683
4794
  */
3684
4795
  async constructCACertificateWithCRL() {
3685
4796
  const cacertWithCRL = this.caCertificateWithCrl;
3686
- if (fs10.existsSync(this.revocationList)) {
3687
- await fs10.promises.writeFile(
4797
+ if (fs12.existsSync(this.revocationList)) {
4798
+ await fs12.promises.writeFile(
3688
4799
  cacertWithCRL,
3689
- fs10.readFileSync(this.caCertificate, "utf8") + fs10.readFileSync(this.revocationList, "utf8")
4800
+ fs12.readFileSync(this.caCertificate, "utf8") + fs12.readFileSync(this.revocationList, "utf8")
3690
4801
  );
3691
4802
  } else {
3692
- await fs10.promises.writeFile(cacertWithCRL, fs10.readFileSync(this.caCertificate));
4803
+ await fs12.promises.writeFile(cacertWithCRL, fs12.readFileSync(this.caCertificate));
3693
4804
  }
3694
4805
  }
3695
4806
  /**
@@ -3699,15 +4810,15 @@ var init_certificate_authority = __esm({
3699
4810
  * @param certificate - path to the certificate file to extend
3700
4811
  */
3701
4812
  async constructCertificateChain(certificate) {
3702
- assert10(fs10.existsSync(certificate));
3703
- assert10(fs10.existsSync(this.caCertificate));
4813
+ assert9(fs12.existsSync(certificate));
4814
+ assert9(fs12.existsSync(this.caCertificate));
3704
4815
  debugLog(chalk6.yellow(" certificate file :"), chalk6.cyan(certificate));
3705
- let chain = await fs10.promises.readFile(certificate, "utf8");
3706
- chain += await fs10.promises.readFile(this.caCertificate, "utf8");
3707
- if (fs10.existsSync(this.issuerCertificateChain)) {
3708
- 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");
3709
4820
  }
3710
- await fs10.promises.writeFile(certificate, chain);
4821
+ await fs12.promises.writeFile(certificate, chain);
3711
4822
  }
3712
4823
  /**
3713
4824
  * Create a self-signed certificate using OpenSSL.
@@ -3717,39 +4828,30 @@ var init_certificate_authority = __esm({
3717
4828
  * @param params - certificate parameters (subject, validity, SANs)
3718
4829
  */
3719
4830
  async createSelfSignedCertificate(certificateFile, privateKey, params) {
3720
- assert10(typeof privateKey === "string");
3721
- assert10(fs10.existsSync(privateKey));
4831
+ assert9(typeof privateKey === "string");
4832
+ assert9(fs12.existsSync(privateKey));
3722
4833
  if (!certificateFileExist(certificateFile)) {
3723
4834
  return;
3724
4835
  }
3725
4836
  adjustDate(params);
3726
4837
  adjustApplicationUri(params);
3727
- processAltNames(params);
3728
- const csrFile = `${certificateFile}_csr`;
3729
- assert10(csrFile);
3730
- const configFile = generateStaticConfig(this.configFile, { cwd: this.rootDir });
3731
- const options = {
3732
- cwd: this.rootDir,
3733
- openssl_conf: makePath(configFile)
3734
- };
3735
- const configOption = "";
3736
- const subject = params.subject ? new Subject4(params.subject).toString() : "";
3737
- const subjectOptions = subject && subject.length > 1 ? ` -subj ${subject} ` : "";
3738
- displaySubtitle("- the certificate signing request");
3739
- await execute_openssl(
3740
- "req -new -sha256 -text " + configOption + subjectOptions + " -batch -key " + q4(n5(privateKey)) + " -out " + q4(n5(csrFile)),
3741
- options
3742
- );
3743
- displaySubtitle("- creating the self-signed certificate");
3744
- await execute_openssl(
3745
- "ca -selfsign -keyfile " + q4(n5(privateKey)) + " -startdate " + x509Date(params.startDate) + " -enddate " + x509Date(params.endDate) + " -batch -out " + q4(n5(certificateFile)) + " -in " + q4(n5(csrFile)),
3746
- options
3747
- );
3748
- displaySubtitle("- dump the certificate for a check");
3749
- await execute_openssl(`x509 -in ${q4(n5(certificateFile))} -dates -fingerprint -purpose -noout`, {});
3750
- displaySubtitle("- verify self-signed certificate");
3751
- await execute_openssl_no_failure(`verify -verbose -CAfile ${q4(n5(certificateFile))} ${q4(n5(certificateFile))}`, options);
3752
- 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
+ });
3753
4855
  }
3754
4856
  /**
3755
4857
  * Revoke a certificate and regenerate the CRL.
@@ -3770,30 +4872,12 @@ var init_certificate_authority = __esm({
3770
4872
  "certificateHold",
3771
4873
  "removeFromCRL"
3772
4874
  ];
3773
- const configFile = generateStaticConfig("conf/caconfig.cnf", { cwd: this.rootDir });
3774
- const options = {
3775
- cwd: this.rootDir,
3776
- openssl_conf: makePath(configFile)
3777
- };
3778
- setEnv("ALTNAME", "");
3779
- const randomFile = path6.join(this.rootDir, "random.rnd");
3780
- setEnv("RANDFILE", randomFile);
3781
- const configOption = ` -config ${q4(n5(configFile))}`;
3782
4875
  const reason = params.reason || "keyCompromise";
3783
- assert10(crlReasons.indexOf(reason) >= 0);
4876
+ assert9(crlReasons.indexOf(reason) >= 0);
3784
4877
  displayTitle(`Revoking certificate ${certificate}`);
3785
- displaySubtitle("Revoke certificate");
3786
- await execute_openssl_no_failure(`ca -verbose ${configOption} -revoke ${q4(certificate)} -crl_reason ${reason}`, options);
3787
- await regenerateCrl(this.revocationList, configOption, options);
3788
- displaySubtitle("Verify that certificate is revoked");
3789
- await execute_openssl_no_failure(
3790
- "verify -verbose -CRLfile " + q4(n5(this.revocationList)) + " -CAfile " + q4(n5(this.caCertificate)) + " -crl_check " + q4(n5(certificate)),
3791
- options
3792
- );
3793
- displaySubtitle("Produce CRL in DER form ");
3794
- await execute_openssl(`crl -in ${q4(n5(this.revocationList))} -out crl/revocation_list.der -outform der`, options);
3795
- displaySubtitle("Produce CRL in PEM form ");
3796
- await execute_openssl(`crl -in ${q4(n5(this.revocationList))} -out crl/revocation_list.pem -outform pem -text `, options);
4878
+ await this.#withCaLock(async () => {
4879
+ await this.#backend.revoke(this, certificate, reason);
4880
+ });
3797
4881
  }
3798
4882
  /**
3799
4883
  * Sign a Certificate Signing Request (CSR) with this CA.
@@ -3808,15 +4892,13 @@ var init_certificate_authority = __esm({
3808
4892
  * @returns the path to the signed certificate
3809
4893
  */
3810
4894
  async signCertificateRequest(certificate, certificateSigningRequestFilename, params1) {
3811
- await ensure_openssl_installed();
3812
- assert10(fs10.existsSync(certificateSigningRequestFilename));
4895
+ await this.#backend.preflight();
4896
+ assert9(fs12.existsSync(certificateSigningRequestFilename));
3813
4897
  if (!certificateFileExist(certificate)) {
3814
4898
  return "";
3815
4899
  }
3816
4900
  adjustDate(params1);
3817
4901
  adjustApplicationUri(params1);
3818
- processAltNames(params1);
3819
- const options = { cwd: this.rootDir };
3820
4902
  const csr = await readCertificateSigningRequest(certificateSigningRequestFilename);
3821
4903
  const csrInfo = exploreCertificateSigningRequest(csr);
3822
4904
  const applicationUri = csrInfo.extensionRequest.subjectAltName.uniformResourceIdentifier ? csrInfo.extensionRequest.subjectAltName.uniformResourceIdentifier[0] : void 0;
@@ -3826,61 +4908,122 @@ var init_certificate_authority = __esm({
3826
4908
  const dns2 = csrInfo.extensionRequest.subjectAltName.dNSName || [];
3827
4909
  let ip = csrInfo.extensionRequest.subjectAltName.iPAddress || [];
3828
4910
  ip = ip.map(octetStringToIpAddress);
3829
- const params = {
3830
- applicationUri,
3831
- dns: dns2,
3832
- ip
3833
- };
3834
- processAltNames(params);
3835
- this._wireRevocationEnvVars();
3836
- const configFile = generateStaticConfig("conf/caconfig.cnf", options);
3837
- displaySubtitle("- then we ask the authority to sign the certificate signing request");
3838
- const configOption = ` -config ${configFile}`;
3839
- await execute_openssl(
3840
- "ca " + configOption + " -startdate " + x509Date(params1.startDate) + " -enddate " + x509Date(params1.endDate) + " -batch -out " + q4(n5(certificate)) + " -in " + q4(n5(certificateSigningRequestFilename)),
3841
- options
3842
- );
3843
- displaySubtitle("- dump the certificate for a check");
3844
- await execute_openssl(`x509 -in ${q4(n5(certificate))} -dates -fingerprint -purpose -noout`, options);
3845
- displaySubtitle("- construct CA certificate with CRL");
3846
- await this.constructCACertificateWithCRL();
3847
- displaySubtitle("- construct certificate chain");
3848
- await this.constructCertificateChain(certificate);
3849
- displaySubtitle("- verify certificate against the root CA");
3850
- await this.verifyCertificate(certificate);
3851
- 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
+ });
3852
4922
  }
3853
4923
  /**
3854
- * Verify a certificate against this CA.
4924
+ * Check that `certificate` really was signed by this CA, and throw if it
4925
+ * was not.
3855
4926
  *
3856
- * @param certificate - path to the certificate file to verify
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.
4936
+ *
4937
+ * @param certificate - path to the certificate (or chain) to verify
3857
4938
  */
3858
4939
  async verifyCertificate(certificate) {
3859
- const isImplemented = false;
3860
- if (isImplemented) {
3861
- const options = { cwd: this.rootDir };
3862
- const configFile = generateStaticConfig("conf/caconfig.cnf", options);
3863
- setEnv("OPENSSL_CONF", makePath(configFile));
3864
- const _configOption = ` -config ${configFile}`;
3865
- _configOption;
3866
- await execute_openssl_no_failure(
3867
- `verify -verbose -CAfile ${q4(n5(this.caCertificateWithCrl))} ${q4(n5(certificate))}`,
3868
- 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."
3869
4972
  );
3870
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
+ }
3871
5014
  }
3872
5015
  };
3873
5016
  }
3874
5017
  });
3875
5018
 
3876
5019
  // packages/node-opcua-pki/lib/ca/crypto_create_CA.ts
3877
- import assert11 from "assert";
3878
- import fs11 from "fs";
5020
+ import assert10 from "assert";
5021
+ import fs13 from "fs";
3879
5022
  import { createRequire } from "module";
3880
5023
  import os5 from "os";
3881
- import path7 from "path";
5024
+ import path10 from "path";
3882
5025
  import chalk7 from "chalk";
3883
- 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";
3884
5027
  import commandLineArgs from "command-line-args";
3885
5028
  import commandLineUsage from "command-line-usage";
3886
5029
  function get_offset_date(date, nbDays) {
@@ -3888,9 +5031,9 @@ function get_offset_date(date, nbDays) {
3888
5031
  d.setDate(d.getDate() + nbDays);
3889
5032
  return d;
3890
5033
  }
3891
- async function construct_CertificateAuthority2(subject) {
3892
- assert11(typeof gLocalConfig.CAFolder === "string", "expecting a CAFolder in config");
3893
- 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");
3894
5037
  if (!g_certificateAuthority) {
3895
5038
  g_certificateAuthority = new CertificateAuthority({
3896
5039
  keySize: gLocalConfig.keySize,
@@ -3901,7 +5044,7 @@ async function construct_CertificateAuthority2(subject) {
3901
5044
  }
3902
5045
  }
3903
5046
  async function construct_CertificateManager() {
3904
- assert11(typeof gLocalConfig.PKIFolder === "string", "expecting a PKIFolder in config");
5047
+ assert10(typeof gLocalConfig.PKIFolder === "string", "expecting a PKIFolder in config");
3905
5048
  if (!certificateManager) {
3906
5049
  certificateManager = new CertificateManager({
3907
5050
  keySize: gLocalConfig.keySize,
@@ -3912,35 +5055,35 @@ async function construct_CertificateManager() {
3912
5055
  }
3913
5056
  function default_template_content() {
3914
5057
  if (process.pkg?.entrypoint) {
3915
- 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");
3916
5059
  return a;
3917
5060
  }
3918
5061
  function find_default_config_template() {
3919
5062
  const rootFolder = find_module_root_folder();
3920
5063
  const configName = "pki_config.example.js";
3921
- let default_config_template2 = path7.join(rootFolder, "bin", configName);
3922
- if (!fs11.existsSync(default_config_template2)) {
3923
- default_config_template2 = path7.join(__dirname, "..", configName);
3924
- if (!fs11.existsSync(default_config_template2)) {
3925
- 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}`);
3926
5069
  }
3927
5070
  }
3928
5071
  return default_config_template2;
3929
5072
  }
3930
5073
  const default_config_template = find_default_config_template();
3931
- assert11(fs11.existsSync(default_config_template));
3932
- 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");
3933
5076
  return default_config_template_content;
3934
5077
  }
3935
5078
  function find_module_root_folder() {
3936
- let rootFolder = path7.join(__dirname);
5079
+ let rootFolder = path10.join(__dirname);
3937
5080
  for (let i = 0; i < 4; i++) {
3938
- if (fs11.existsSync(path7.join(rootFolder, "package.json"))) {
5081
+ if (fs13.existsSync(path10.join(rootFolder, "package.json"))) {
3939
5082
  return rootFolder;
3940
5083
  }
3941
- rootFolder = path7.join(rootFolder, "..");
5084
+ rootFolder = path10.join(rootFolder, "..");
3942
5085
  }
3943
- 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");
3944
5087
  return rootFolder;
3945
5088
  }
3946
5089
  async function readConfiguration(argv) {
@@ -3965,41 +5108,41 @@ async function readConfiguration(argv) {
3965
5108
  return str;
3966
5109
  }
3967
5110
  function prepare(file) {
3968
- const tmp = path7.resolve(performSubstitution(file));
5111
+ const tmp = path10.resolve(performSubstitution(file));
3969
5112
  return makePath(tmp);
3970
5113
  }
3971
5114
  certificateDir = argv.root;
3972
- assert11(typeof certificateDir === "string");
5115
+ assert10(typeof certificateDir === "string");
3973
5116
  certificateDir = prepare(certificateDir);
3974
5117
  mkdirRecursiveSync(certificateDir);
3975
- assert11(fs11.existsSync(certificateDir));
3976
- const default_config = path7.join(certificateDir, "config.js");
3977
- 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)) {
3978
5121
  debugLog(chalk7.yellow(" Creating default g_config file "), chalk7.cyan(default_config));
3979
5122
  const default_config_template_content = default_template_content();
3980
- fs11.writeFileSync(default_config, default_config_template_content);
5123
+ fs13.writeFileSync(default_config, default_config_template_content);
3981
5124
  } else {
3982
5125
  debugLog(chalk7.yellow(" using g_config file "), chalk7.cyan(default_config));
3983
5126
  }
3984
- if (!fs11.existsSync(default_config)) {
5127
+ if (!fs13.existsSync(default_config)) {
3985
5128
  debugLog(chalk7.redBright(" cannot find config file ", default_config));
3986
5129
  }
3987
- const defaultRandomFile = path7.join(path7.dirname(default_config), "random.rnd");
5130
+ const defaultRandomFile = path10.join(path10.dirname(default_config), "random.rnd");
3988
5131
  setEnv("RANDFILE", defaultRandomFile);
3989
5132
  const _require = createRequire(__filename);
3990
5133
  gLocalConfig = _require(default_config);
3991
- gLocalConfig.subject = new Subject5(gLocalConfig.subject || "");
5134
+ gLocalConfig.subject = new Subject7(gLocalConfig.subject || "");
3992
5135
  if (argv.subject) {
3993
- gLocalConfig.subject = new Subject5(argv.subject);
5136
+ gLocalConfig.subject = new Subject7(argv.subject);
3994
5137
  }
3995
5138
  if (!gLocalConfig.subject.commonName) {
3996
5139
  throw new Error("subject must have a Common Name");
3997
5140
  }
3998
5141
  gLocalConfig.certificateDir = certificateDir;
3999
- let CAFolder = argv.CAFolder || path7.join(certificateDir, "CA");
5142
+ let CAFolder = argv.CAFolder || path10.join(certificateDir, "CA");
4000
5143
  CAFolder = prepare(CAFolder);
4001
5144
  gLocalConfig.CAFolder = CAFolder;
4002
- gLocalConfig.PKIFolder = path7.join(gLocalConfig.certificateDir, "PKI");
5145
+ gLocalConfig.PKIFolder = path10.join(gLocalConfig.certificateDir, "PKI");
4003
5146
  if (argv.PKIFolder) {
4004
5147
  gLocalConfig.PKIFolder = prepare(argv.PKIFolder);
4005
5148
  }
@@ -4037,7 +5180,7 @@ async function readConfiguration(argv) {
4037
5180
  }
4038
5181
  }
4039
5182
  async function createDefaultCertificate(base_name, prefix, key_length, applicationUri, dev) {
4040
- 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);
4041
5184
  const private_key_file = makePath(base_name, `${prefix}key_${key_length}.pem`);
4042
5185
  const public_key_file = makePath(base_name, `${prefix}public_key_${key_length}.pub`);
4043
5186
  const certificate_file = makePath(base_name, `${prefix}cert_${key_length}.pem`);
@@ -4057,7 +5200,7 @@ async function createDefaultCertificate(base_name, prefix, key_length, applicati
4057
5200
  }
4058
5201
  const ip = [];
4059
5202
  async function createCertificateIfNotExist(certificate, private_key, applicationUri2, startDate, validity) {
4060
- if (fs11.existsSync(certificate)) {
5203
+ if (fs13.existsSync(certificate)) {
4061
5204
  warningLog(chalk7.yellow(" certificate"), chalk7.cyan(certificate), chalk7.yellow(" already exists => skipping"));
4062
5205
  return "";
4063
5206
  } else {
@@ -4076,7 +5219,7 @@ async function createDefaultCertificate(base_name, prefix, key_length, applicati
4076
5219
  configFile,
4077
5220
  dns: dns3,
4078
5221
  ip: ip2,
4079
- purpose: CertificatePurpose3.ForApplication
5222
+ purpose: CertificatePurpose4.ForApplication
4080
5223
  };
4081
5224
  await createCertificateSigningRequestWithOpenSSL(certificateSigningRequestFile, params);
4082
5225
  return await g_certificateAuthority.signCertificateRequest(certificate, certificateSigningRequestFile, {
@@ -4100,7 +5243,7 @@ async function createDefaultCertificate(base_name, prefix, key_length, applicati
4100
5243
  await g_certificateAuthority.revokeCertificate(certificate, {});
4101
5244
  }
4102
5245
  async function createPrivateKeyIfNotExist(privateKey, keyLength) {
4103
- if (fs11.existsSync(privateKey)) {
5246
+ if (fs13.existsSync(privateKey)) {
4104
5247
  warningLog(chalk7.yellow(" privateKey"), chalk7.cyan(privateKey), chalk7.yellow(" already exists => skipping"));
4105
5248
  return;
4106
5249
  } else {
@@ -4114,14 +5257,14 @@ async function createDefaultCertificate(base_name, prefix, key_length, applicati
4114
5257
  displaySubtitle(` create Certificate ${certificate_file}`);
4115
5258
  await createCertificateIfNotExist(certificate_file, private_key_file, applicationUri, yesterday, 365);
4116
5259
  displaySubtitle(` create self signed Certificate ${self_signed_certificate_file}`);
4117
- if (fs11.existsSync(self_signed_certificate_file)) {
5260
+ if (fs13.existsSync(self_signed_certificate_file)) {
4118
5261
  return;
4119
5262
  }
4120
5263
  await createSelfSignedCertificate2(self_signed_certificate_file, private_key_file, applicationUri, yesterday, 365);
4121
5264
  if (dev) {
4122
5265
  await createCertificateIfNotExist(certificate_file_outofdate, private_key_file, applicationUri, two_years_ago, 365);
4123
5266
  await createCertificateIfNotExist(certificate_file_not_active_yet, private_key_file, applicationUri, next_year, 365);
4124
- if (!fs11.existsSync(certificate_revoked)) {
5267
+ if (!fs13.existsSync(certificate_revoked)) {
4125
5268
  const certificate = await createCertificateIfNotExist(
4126
5269
  certificate_revoked,
4127
5270
  private_key_file,
@@ -4131,7 +5274,7 @@ async function createDefaultCertificate(base_name, prefix, key_length, applicati
4131
5274
  365
4132
5275
  );
4133
5276
  warningLog(" certificate to revoke => ", certificate);
4134
- revoke_certificate(certificate_revoked);
5277
+ await revoke_certificate(certificate_revoked);
4135
5278
  }
4136
5279
  }
4137
5280
  }
@@ -4143,13 +5286,13 @@ async function wrap(func) {
4143
5286
  }
4144
5287
  }
4145
5288
  async function create_default_certificates(dev) {
4146
- assert11(gLocalConfig);
5289
+ assert10(gLocalConfig);
4147
5290
  const base_name = gLocalConfig.certificateDir || "";
4148
- assert11(fs11.existsSync(base_name));
5291
+ assert10(fs13.existsSync(base_name));
4149
5292
  let clientURN;
4150
5293
  let serverURN;
4151
5294
  let discoveryServerURN;
4152
- wrap(async () => {
5295
+ await wrap(async () => {
4153
5296
  await extractFullyQualifiedDomainName();
4154
5297
  const hostname = os5.hostname();
4155
5298
  const fqdn2 = getFullyQualifiedDomainName();
@@ -4176,7 +5319,7 @@ async function create_default_certificates(dev) {
4176
5319
  });
4177
5320
  }
4178
5321
  async function createDefaultCertificates(dev) {
4179
- await construct_CertificateAuthority2("");
5322
+ await construct_CertificateAuthority("");
4180
5323
  await construct_CertificateManager();
4181
5324
  await create_default_certificates(dev);
4182
5325
  }
@@ -4242,7 +5385,7 @@ ${epilog}`
4242
5385
  }
4243
5386
  if (command === "version") {
4244
5387
  const rootFolder = find_module_root_folder();
4245
- 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"));
4246
5389
  console.log(pkg.version);
4247
5390
  return;
4248
5391
  }
@@ -4267,12 +5410,12 @@ ${epilog}`
4267
5410
  await readConfiguration(local_argv);
4268
5411
  if (local_argv.clean) {
4269
5412
  displayTitle("Cleaning old certificates");
4270
- assert11(gLocalConfig);
5413
+ assert10(gLocalConfig);
4271
5414
  const certificateDir = gLocalConfig.certificateDir || "";
4272
- const files = await fs11.promises.readdir(certificateDir);
5415
+ const files = await fs13.promises.readdir(certificateDir);
4273
5416
  for (const file of files) {
4274
5417
  if (file.includes(".pem") || file.includes(".pub")) {
4275
- await fs11.promises.unlink(path7.join(certificateDir, file));
5418
+ await fs13.promises.unlink(path10.join(certificateDir, file));
4276
5419
  }
4277
5420
  }
4278
5421
  mkdirRecursiveSync(certificateDir);
@@ -4293,7 +5436,7 @@ ${epilog}`
4293
5436
  await wrap(async () => {
4294
5437
  await ensure_openssl_installed();
4295
5438
  await readConfiguration(local_argv);
4296
- await construct_CertificateAuthority2(local_argv.subject);
5439
+ await construct_CertificateAuthority(local_argv.subject);
4297
5440
  });
4298
5441
  return;
4299
5442
  }
@@ -4362,7 +5505,7 @@ ${epilog}`
4362
5505
  await readConfiguration(local_argv2);
4363
5506
  await construct_CertificateManager();
4364
5507
  displaySubtitle(` create self signed Certificate ${gLocalConfig.outputFile}`);
4365
- 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 || "";
4366
5509
  subject = JSON.parse(JSON.stringify(subject));
4367
5510
  const params = {
4368
5511
  applicationUri: gLocalConfig.applicationUri || "",
@@ -4378,8 +5521,8 @@ ${epilog}`
4378
5521
  async function command_full_certificate(local_argv2) {
4379
5522
  await readConfiguration(local_argv2);
4380
5523
  await construct_CertificateManager();
4381
- await construct_CertificateAuthority2("");
4382
- assert11(fs11.existsSync(gLocalConfig.CAFolder || ""), " CA folder must exist");
5524
+ await construct_CertificateAuthority("");
5525
+ assert10(fs13.existsSync(gLocalConfig.CAFolder || ""), " CA folder must exist");
4383
5526
  gLocalConfig.privateKey = void 0;
4384
5527
  gLocalConfig.subject = local_argv2.subject && local_argv2.subject.length > 1 ? local_argv2.subject : gLocalConfig.subject;
4385
5528
  const csr_file = await certificateManager.createCertificateRequest(
@@ -4390,7 +5533,7 @@ ${epilog}`
4390
5533
  }
4391
5534
  warningLog(" csr_file = ", csr_file);
4392
5535
  const certificate = csr_file.replace(".csr", ".pem");
4393
- if (fs11.existsSync(certificate)) {
5536
+ if (fs13.existsSync(certificate)) {
4394
5537
  throw new Error(` File ${certificate} already exist`);
4395
5538
  }
4396
5539
  await g_certificateAuthority.signCertificateRequest(
@@ -4398,8 +5541,8 @@ ${epilog}`
4398
5541
  csr_file,
4399
5542
  gLocalConfig
4400
5543
  );
4401
- assert11(typeof gLocalConfig.outputFile === "string");
4402
- fs11.writeFileSync(gLocalConfig.outputFile || "", fs11.readFileSync(certificate, "ascii"));
5544
+ assert10(typeof gLocalConfig.outputFile === "string");
5545
+ fs13.writeFileSync(gLocalConfig.outputFile || "", fs13.readFileSync(certificate, "ascii"));
4403
5546
  }
4404
5547
  await wrap(async () => await command_certificate(local_argv));
4405
5548
  return;
@@ -4418,13 +5561,13 @@ ${epilog}`
4418
5561
  await g_certificateAuthority.revokeCertificate(certificate, {});
4419
5562
  }
4420
5563
  await wrap(async () => {
4421
- const certificate = path7.resolve(local_argv.certificateFile);
5564
+ const certificate = path10.resolve(local_argv.certificateFile);
4422
5565
  warningLog(chalk7.yellow(" Certificate to revoke : "), chalk7.cyan(certificate));
4423
- if (!fs11.existsSync(certificate)) {
5566
+ if (!fs13.existsSync(certificate)) {
4424
5567
  throw new Error(`cannot find certificate to revoke ${certificate}`);
4425
5568
  }
4426
5569
  await readConfiguration(local_argv);
4427
- await construct_CertificateAuthority2("");
5570
+ await construct_CertificateAuthority("");
4428
5571
  await revoke_certificate(certificate);
4429
5572
  warningLog("done ... ");
4430
5573
  warningLog(" crl = ", g_certificateAuthority.revocationList);
@@ -4467,11 +5610,11 @@ ${epilog}`
4467
5610
  if (local_argv.help) return showHelp("csr", "create a certificate signing request", optionsDef);
4468
5611
  await wrap(async () => {
4469
5612
  await readConfiguration(local_argv);
4470
- if (!fs11.existsSync(gLocalConfig.PKIFolder || "")) {
5613
+ if (!fs13.existsSync(gLocalConfig.PKIFolder || "")) {
4471
5614
  warningLog("PKI folder must exist");
4472
5615
  }
4473
5616
  await construct_CertificateManager();
4474
- if (!gLocalConfig.outputFile || fs11.existsSync(gLocalConfig.outputFile)) {
5617
+ if (!gLocalConfig.outputFile || fs13.existsSync(gLocalConfig.outputFile)) {
4475
5618
  throw new Error(` File ${gLocalConfig.outputFile} already exist`);
4476
5619
  }
4477
5620
  gLocalConfig.privateKey = void 0;
@@ -4486,8 +5629,8 @@ ${epilog}`
4486
5629
  warningLog("please specify a output file");
4487
5630
  return;
4488
5631
  }
4489
- const csr = await fs11.promises.readFile(internal_csr_file, "utf-8");
4490
- 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");
4491
5634
  warningLog("Subject = ", gLocalConfig.subject);
4492
5635
  warningLog("applicationUri = ", gLocalConfig.applicationUri);
4493
5636
  warningLog("altNames = ", gLocalConfig.altNames);
@@ -4515,16 +5658,16 @@ ${epilog}`
4515
5658
  return showHelp("sign", "validate a certificate signing request and generate a certificate", optionsDef);
4516
5659
  await wrap(async () => {
4517
5660
  await readConfiguration(local_argv);
4518
- if (!fs11.existsSync(gLocalConfig.CAFolder || "")) {
5661
+ if (!fs13.existsSync(gLocalConfig.CAFolder || "")) {
4519
5662
  throw new Error(`CA folder must exist:${gLocalConfig.CAFolder}`);
4520
5663
  }
4521
- await construct_CertificateAuthority2("");
4522
- const csr_file = path7.resolve(local_argv.csr || "");
4523
- if (!fs11.existsSync(csr_file)) {
5664
+ await construct_CertificateAuthority("");
5665
+ const csr_file = path10.resolve(local_argv.csr || "");
5666
+ if (!fs13.existsSync(csr_file)) {
4524
5667
  throw new Error(`Certificate signing request doesn't exist: ${csr_file}`);
4525
5668
  }
4526
- const certificate = path7.resolve(local_argv.output || csr_file.replace(".csr", ".pem"));
4527
- if (fs11.existsSync(certificate)) {
5669
+ const certificate = path10.resolve(local_argv.output || csr_file.replace(".csr", ".pem"));
5670
+ if (fs13.existsSync(certificate)) {
4528
5671
  throw new Error(` File ${certificate} already exist`);
4529
5672
  }
4530
5673
  await g_certificateAuthority.signCertificateRequest(
@@ -4532,8 +5675,8 @@ ${epilog}`
4532
5675
  csr_file,
4533
5676
  gLocalConfig
4534
5677
  );
4535
- assert11(typeof gLocalConfig.outputFile === "string");
4536
- fs11.writeFileSync(gLocalConfig.outputFile || "", fs11.readFileSync(certificate, "ascii"));
5678
+ assert10(typeof gLocalConfig.outputFile === "string");
5679
+ fs13.writeFileSync(gLocalConfig.outputFile || "", fs13.readFileSync(certificate, "ascii"));
4537
5680
  });
4538
5681
  return;
4539
5682
  }