node-opcua-pki 6.19.0 → 6.20.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/index.mjs CHANGED
@@ -1,3 +1,6 @@
1
+ // packages/node-opcua-pki/lib/index.ts
2
+ import { PrivateKeyPassphraseRequiredError } from "node-opcua-crypto";
3
+
1
4
  // packages/node-opcua-pki/lib/ca/certificate_authority.ts
2
5
  import assert7 from "assert";
3
6
  import fs7 from "fs";
@@ -15,45 +18,16 @@ import {
15
18
  readCertificateSigningRequest,
16
19
  readPrivateKey,
17
20
  Subject as Subject2,
18
- toPem
21
+ toPem,
22
+ writePrivateKeyFile
19
23
  } from "node-opcua-crypto";
20
24
 
21
25
  // packages/node-opcua-pki/lib/pki/toolbox_pfx.ts
22
- import assert4 from "assert";
26
+ import assert3 from "assert";
23
27
  import fs4 from "fs";
24
28
 
25
- // packages/node-opcua-pki/lib/toolbox/common.ts
26
- import assert from "assert";
27
- function quote(str) {
28
- return `"${str || ""}"`;
29
- }
30
- function adjustDate(params) {
31
- assert(params instanceof Object);
32
- params.startDate = params.startDate || /* @__PURE__ */ new Date();
33
- assert(params.startDate instanceof Date);
34
- if (params.validityMs !== void 0) {
35
- if (params.validityMs <= 0) {
36
- throw new RangeError(`validityMs must be > 0 (got ${params.validityMs})`);
37
- }
38
- params.endDate = new Date(params.startDate.getTime() + params.validityMs);
39
- params.validity = Math.ceil(params.validityMs / 864e5);
40
- } else {
41
- params.validity = params.validity || 365;
42
- params.endDate = new Date(params.startDate.getTime());
43
- params.endDate.setDate(params.startDate.getDate() + params.validity);
44
- }
45
- assert(params.endDate instanceof Date);
46
- assert(params.startDate instanceof Date);
47
- }
48
- function adjustApplicationUri(params) {
49
- const applicationUri = params.applicationUri || "";
50
- if (applicationUri.length > 200) {
51
- throw new Error(`Openssl doesn't support urn with length greater than 200${applicationUri}`);
52
- }
53
- }
54
-
55
29
  // packages/node-opcua-pki/lib/toolbox/common2.ts
56
- import assert2 from "assert";
30
+ import assert from "assert";
57
31
  import fs from "fs";
58
32
  import path from "path";
59
33
  import chalk from "chalk";
@@ -94,12 +68,31 @@ function mkdirRecursiveSync(folder) {
94
68
  fs.mkdirSync(folder, { recursive: true });
95
69
  }
96
70
  }
71
+ function restrictPrivateFilePermissions(target, mode) {
72
+ if (process.platform === "win32") {
73
+ return;
74
+ }
75
+ try {
76
+ fs.chmodSync(target, mode);
77
+ } catch (err) {
78
+ warningLog(chalk.yellow(" could not restrict permissions on "), target, err.message);
79
+ }
80
+ }
81
+ function ensurePrivateDirectory(dir) {
82
+ if (!fs.existsSync(dir)) {
83
+ fs.mkdirSync(dir, { recursive: true, mode: 448 });
84
+ }
85
+ restrictPrivateFilePermissions(dir, 448);
86
+ }
87
+ function isEncryptedPrivateKeyFile(filename) {
88
+ return fs.readFileSync(filename, "utf-8").includes("-----BEGIN ENCRYPTED PRIVATE KEY-----");
89
+ }
97
90
  function makePath(folderName, filename) {
98
91
  let s;
99
92
  if (filename) {
100
93
  s = path.join(path.normalize(folderName), filename);
101
94
  } else {
102
- assert2(folderName);
95
+ assert(folderName);
103
96
  s = folderName;
104
97
  }
105
98
  s = s.replace(/\\/g, "/");
@@ -107,7 +100,7 @@ function makePath(folderName, filename) {
107
100
  }
108
101
 
109
102
  // packages/node-opcua-pki/lib/toolbox/with_openssl/execute_openssl.ts
110
- import assert3 from "assert";
103
+ import assert2 from "assert";
111
104
  import child_process2 from "child_process";
112
105
  import fs3 from "fs";
113
106
  import os2 from "os";
@@ -115,6 +108,50 @@ import byline2 from "byline";
115
108
  import chalk3 from "chalk";
116
109
 
117
110
  // packages/node-opcua-pki/lib/toolbox/with_openssl/_env.ts
111
+ var SAFE_ENV_PASSTHROUGH = /* @__PURE__ */ new Set([
112
+ // POSIX/Windows shell and process essentials
113
+ "path",
114
+ "home",
115
+ "userprofile",
116
+ "temp",
117
+ "tmp",
118
+ "tmpdir",
119
+ "systemroot",
120
+ "windir",
121
+ "comspec",
122
+ "pathext",
123
+ "appdata",
124
+ "localappdata",
125
+ // dynamic loader: a custom-built or relocated openssl (e.g. under /opt,
126
+ // or Homebrew on macOS) may need these to find its own libcrypto/libssl
127
+ "ld_library_path",
128
+ "dyld_library_path",
129
+ "dyld_fallback_library_path",
130
+ // locale, so openssl's textual output stays parseable
131
+ "lang",
132
+ "lc_all",
133
+ "lc_ctype",
134
+ // openssl configuration this app, or the host OS/user, may rely on
135
+ "openssl_conf",
136
+ "randfile",
137
+ "openssl_modules",
138
+ "openssl_engines",
139
+ "ssl_cert_file",
140
+ "ssl_cert_dir"
141
+ ]);
142
+ function buildChildEnv(extra) {
143
+ const env = {};
144
+ for (const key of Object.keys(process.env)) {
145
+ if (SAFE_ENV_PASSTHROUGH.has(key.toLowerCase())) {
146
+ env[key] = process.env[key];
147
+ }
148
+ }
149
+ return { ...env, ...extra };
150
+ }
151
+ function redactEnvForLog(options) {
152
+ const { env, ...rest } = options;
153
+ return env ? { ...rest, env: Object.keys(env) } : rest;
154
+ }
118
155
  var exportedEnvVars = {};
119
156
  function setEnv(varName, value) {
120
157
  if (!g_config.silent) {
@@ -169,24 +206,17 @@ import byline from "byline";
169
206
  import chalk2 from "chalk";
170
207
  import yauzl from "yauzl";
171
208
  var doDebug2 = process.env.NODEOPCUAPKIDEBUG || false;
172
- async function execute(cmd, cwd) {
209
+ async function execute(file, args, cwd) {
173
210
  let output = "";
174
- const options = {
175
- cwd,
176
- windowsHide: true
177
- };
178
211
  return await new Promise((resolve, reject) => {
179
- const child = child_process.exec(
180
- cmd,
181
- options,
182
- (err) => {
183
- const exitCode = err === null ? 0 : typeof err.code === "number" ? err.code : 1;
184
- if (err) reject(err);
185
- else {
186
- resolve({ exitCode, output });
187
- }
188
- }
189
- );
212
+ const child = child_process.spawn(file, args, {
213
+ cwd,
214
+ windowsHide: true,
215
+ env: buildChildEnv(),
216
+ stdio: ["ignore", "pipe", "pipe"]
217
+ });
218
+ child.on("error", (err) => reject(err));
219
+ child.on("close", (code) => resolve({ exitCode: code ?? 1, output }));
190
220
  const stream1 = byline(child.stdout);
191
221
  stream1.on("data", (line) => {
192
222
  output += `${line}
@@ -198,16 +228,13 @@ async function execute(cmd, cwd) {
198
228
  });
199
229
  });
200
230
  }
201
- function quote2(str) {
202
- return `"${str.replace(/\\/g, "/")}"`;
203
- }
204
231
  function is_expected_openssl_version(strVersion) {
205
232
  return !!strVersion.match(/OpenSSL \d/);
206
233
  }
207
234
  async function getopensslExecPath() {
208
235
  let result1;
209
236
  try {
210
- result1 = await execute("which openssl");
237
+ result1 = await execute("which", ["openssl"]);
211
238
  } catch (err) {
212
239
  warningLog("warning: ", err.message);
213
240
  throw new Error("Cannot find openssl");
@@ -224,11 +251,10 @@ async function getopensslExecPath() {
224
251
  }
225
252
  async function check_system_openssl_version() {
226
253
  const opensslExecPath = await getopensslExecPath();
227
- const q_opensslExecPath = quote2(opensslExecPath);
228
254
  if (doDebug2) {
229
255
  warningLog(` OpenSSL found in : ${chalk2.yellow(opensslExecPath)}`);
230
256
  }
231
- const result = await execute(`${q_opensslExecPath} version`);
257
+ const result = await execute(opensslExecPath, ["version"]);
232
258
  const exitCode = result?.exitCode;
233
259
  const output = result?.output;
234
260
  const version = output.trim();
@@ -268,9 +294,8 @@ async function install_and_check_win32_openssl_version() {
268
294
  version: `cannot find file ${opensslExecPath2}`
269
295
  };
270
296
  } else {
271
- const q_openssl_exe_path = quote2(opensslExecPath2);
272
297
  const cwd = ".";
273
- const { exitCode, output } = await execute(`${q_openssl_exe_path} version`, cwd);
298
+ const { exitCode, output } = await execute(opensslExecPath2, ["version"], cwd);
274
299
  const version = output.trim();
275
300
  if (doDebug2) {
276
301
  warningLog(" Version = ", version);
@@ -283,7 +308,7 @@ async function install_and_check_win32_openssl_version() {
283
308
  }
284
309
  async function find_system_openssl_win32() {
285
310
  try {
286
- const result = await execute("where openssl");
311
+ const result = await execute("where", ["openssl"]);
287
312
  if (result.exitCode !== 0) {
288
313
  return void 0;
289
314
  }
@@ -291,8 +316,7 @@ async function install_and_check_win32_openssl_version() {
291
316
  if (!opensslPath2 || !fs2.existsSync(opensslPath2)) {
292
317
  return void 0;
293
318
  }
294
- const q4 = quote2(opensslPath2);
295
- const versionResult = await execute(`${q4} version`);
319
+ const versionResult = await execute(opensslPath2, ["version"]);
296
320
  const version = versionResult.output.trim();
297
321
  if (versionResult.exitCode === 0 && is_expected_openssl_version(version)) {
298
322
  warningLog(
@@ -455,36 +479,55 @@ async function get_openssl_exec_path() {
455
479
  // packages/node-opcua-pki/lib/toolbox/with_openssl/execute_openssl.ts
456
480
  var opensslPath;
457
481
  var n = makePath;
458
- async function execute2(cmd, options) {
482
+ var PASSIN_ENV_VAR = "NODE_OPCUA_PKI_OPENSSL_PASSIN";
483
+ var PASSOUT_ENV_VAR = "NODE_OPCUA_PKI_OPENSSL_PASSOUT";
484
+ function passinArg(passphrase = "") {
485
+ return { args: ["-passin", `env:${PASSIN_ENV_VAR}`], env: { [PASSIN_ENV_VAR]: passphrase } };
486
+ }
487
+ function passoutArg(passphrase = "") {
488
+ return { args: ["-passout", `env:${PASSOUT_ENV_VAR}`], env: { [PASSOUT_ENV_VAR]: passphrase } };
489
+ }
490
+ function renderForDisplay(file, args) {
491
+ return [file, ...args].map((a) => a === "" || /[\s"'`$\\]/.test(a) ? JSON.stringify(a) : a).join(" ");
492
+ }
493
+ async function execute2(file, args, options) {
459
494
  const from = new Error();
460
495
  options.cwd = options.cwd || process.cwd();
461
496
  if (!g_config.silent) {
462
497
  warningLog(chalk3.cyan(" CWD "), options.cwd);
463
498
  }
464
499
  const outputs = [];
500
+ const errorOutputs = [];
501
+ const display2 = renderForDisplay(file, args);
465
502
  return await new Promise((resolve, reject) => {
466
- const child = child_process2.exec(
467
- cmd,
468
- {
469
- cwd: options.cwd,
470
- windowsHide: true
471
- },
472
- (err) => {
473
- if (err) {
474
- if (!options.hideErrorMessage) {
475
- const fence = "###########################################";
476
- console.error(chalk3.bgWhiteBright.redBright(`${fence} OPENSSL ERROR ${fence}`));
477
- console.error(chalk3.bgWhiteBright.redBright(`CWD = ${options.cwd}`));
478
- console.error(chalk3.bgWhiteBright.redBright(err.message));
479
- console.error(chalk3.bgWhiteBright.redBright(`${fence} OPENSSL ERROR ${fence}`));
480
- console.error(from.stack);
481
- }
482
- reject(new Error(err.message));
483
- return;
484
- }
503
+ const child = child_process2.spawn(file, [...args], {
504
+ cwd: options.cwd,
505
+ windowsHide: true,
506
+ env: buildChildEnv(options.env),
507
+ stdio: ["ignore", "pipe", "pipe"]
508
+ });
509
+ const fail = (message) => {
510
+ if (!options.hideErrorMessage) {
511
+ const fence = "###########################################";
512
+ console.error(chalk3.bgWhiteBright.redBright(`${fence} OPENSSL ERROR ${fence}`));
513
+ console.error(chalk3.bgWhiteBright.redBright(`CWD = ${options.cwd}`));
514
+ console.error(chalk3.bgWhiteBright.redBright(message));
515
+ console.error(chalk3.bgWhiteBright.redBright(`${fence} OPENSSL ERROR ${fence}`));
516
+ console.error(from.stack);
517
+ }
518
+ reject(new Error(message));
519
+ };
520
+ child.on("error", (err) => fail(`Command failed: ${display2}
521
+ ${err.message}`));
522
+ child.on("close", (code, signal) => {
523
+ if (code === 0) {
485
524
  resolve(outputs.join(""));
525
+ return;
486
526
  }
487
- );
527
+ const why = signal ? `signal ${signal}` : `exit code ${code}`;
528
+ fail(`Command failed: ${display2}
529
+ ${errorOutputs.join("")}(${why})`);
530
+ });
488
531
  if (child.stdout) {
489
532
  const stream2 = byline2(child.stdout);
490
533
  stream2.on("data", (line) => {
@@ -501,17 +544,17 @@ async function execute2(cmd, options) {
501
544
  });
502
545
  }
503
546
  }
504
- if (!g_config.silent) {
505
- if (child.stderr) {
506
- const stream1 = byline2(child.stderr);
507
- stream1.on("data", (line) => {
508
- line = line.toString();
509
- if (displayError) {
510
- process.stdout.write(`${chalk3.white(" stderr ") + chalk3.red(line)}
547
+ if (child.stderr) {
548
+ const stream1 = byline2(child.stderr);
549
+ stream1.on("data", (line) => {
550
+ line = line.toString();
551
+ errorOutputs.push(`${line}
511
552
  `);
512
- }
513
- });
514
- }
553
+ if (!g_config.silent && displayError) {
554
+ process.stdout.write(`${chalk3.white(" stderr ") + chalk3.red(line)}
555
+ `);
556
+ }
557
+ });
515
558
  }
516
559
  });
517
560
  }
@@ -521,18 +564,18 @@ async function find_openssl() {
521
564
  async function ensure_openssl_installed() {
522
565
  if (!opensslPath) {
523
566
  opensslPath = await find_openssl();
524
- const outputs = await execute_openssl("version", { cwd: "." });
567
+ const outputs = await execute_openssl(["version"], { cwd: "." });
525
568
  g_config.opensslVersion = outputs.trim();
526
569
  if (doDebug) {
527
570
  warningLog("OpenSSL version : ", g_config.opensslVersion);
528
571
  }
529
572
  }
530
573
  }
531
- async function execute_openssl_no_failure(cmd, options) {
574
+ async function execute_openssl_no_failure(args, options) {
532
575
  options = options || {};
533
576
  options.hideErrorMessage = true;
534
577
  try {
535
- return await execute_openssl(cmd, options);
578
+ return await execute_openssl(args, options);
536
579
  } catch (err) {
537
580
  debugLog(" (ignored error = ERROR : )", err.message);
538
581
  }
@@ -540,62 +583,64 @@ async function execute_openssl_no_failure(cmd, options) {
540
583
  function getTempFolder() {
541
584
  return os2.tmpdir();
542
585
  }
543
- async function execute_openssl(cmd, options) {
544
- debugLog("execute_openssl", cmd, options);
586
+ async function execute_openssl(args, options) {
587
+ debugLog("execute_openssl", args, redactEnvForLog(options));
545
588
  const empty_config_file = n(getTempFolder(), "empty_config.cnf");
546
589
  if (!fs3.existsSync(empty_config_file)) {
547
590
  await fs3.promises.writeFile(empty_config_file, "# empty config file");
548
591
  }
549
592
  options = options || {};
550
593
  options.openssl_conf = options.openssl_conf || empty_config_file;
551
- assert3(options.openssl_conf);
594
+ assert2(options.openssl_conf);
552
595
  setEnv("OPENSSL_CONF", options.openssl_conf);
553
596
  if (!g_config.silent) {
554
597
  warningLog(chalk3.cyan(" OPENSSL_CONF"), process.env.OPENSSL_CONF);
555
598
  warningLog(chalk3.cyan(" RANDFILE "), process.env.RANDFILE);
556
- warningLog(chalk3.cyan(" CMD openssl "), chalk3.cyanBright(cmd));
599
+ warningLog(chalk3.cyan(" CMD "), chalk3.cyanBright(renderForDisplay("openssl", args)));
557
600
  }
558
601
  await ensure_openssl_installed();
559
- return await execute2(`${quote(opensslPath)} ${cmd}`, options);
602
+ return await execute2(opensslPath, args, options);
560
603
  }
561
604
 
562
605
  // packages/node-opcua-pki/lib/pki/toolbox_pfx.ts
563
- var q = quote;
564
606
  var n2 = makePath;
565
607
  async function createPFX(options) {
566
- const { certificateFile, privateKeyFile, outputFile, passphrase = "", caCertificateFiles } = options;
567
- assert4(fs4.existsSync(certificateFile), `Certificate file does not exist: ${certificateFile}`);
568
- assert4(fs4.existsSync(privateKeyFile), `Private key file does not exist: ${privateKeyFile}`);
569
- let cmd = `pkcs12 -export`;
570
- cmd += ` -in ${q(n2(certificateFile))}`;
571
- cmd += ` -inkey ${q(n2(privateKeyFile))}`;
608
+ const { certificateFile, privateKeyFile, privateKeyPassphrase, outputFile, passphrase = "", caCertificateFiles } = options;
609
+ assert3(fs4.existsSync(certificateFile), `Certificate file does not exist: ${certificateFile}`);
610
+ assert3(fs4.existsSync(privateKeyFile), `Private key file does not exist: ${privateKeyFile}`);
611
+ const args = ["pkcs12", "-export", "-in", n2(certificateFile), "-inkey", n2(privateKeyFile)];
572
612
  if (caCertificateFiles) {
573
613
  for (const caFile of caCertificateFiles) {
574
- assert4(fs4.existsSync(caFile), `CA certificate file does not exist: ${caFile}`);
575
- cmd += ` -certfile ${q(n2(caFile))}`;
614
+ assert3(fs4.existsSync(caFile), `CA certificate file does not exist: ${caFile}`);
615
+ args.push("-certfile", n2(caFile));
576
616
  }
577
617
  }
578
- cmd += ` -out ${q(n2(outputFile))}`;
579
- cmd += ` -passout pass:${passphrase}`;
580
- await execute_openssl(cmd, {});
618
+ const passin = passinArg(privateKeyPassphrase);
619
+ const passout = passoutArg(passphrase);
620
+ args.push("-out", n2(outputFile), ...passin.args, ...passout.args);
621
+ await execute_openssl(args, { env: { ...passin.env, ...passout.env } });
581
622
  }
582
623
  async function extractCertificateFromPFX(options) {
583
624
  const { pfxFile, passphrase = "" } = options;
584
- assert4(fs4.existsSync(pfxFile), `PFX file does not exist: ${pfxFile}`);
585
- const cmd = `pkcs12 -in ${q(n2(pfxFile))} -clcerts -nokeys -nodes -passin pass:${passphrase}`;
586
- return await execute_openssl(cmd, {});
625
+ assert3(fs4.existsSync(pfxFile), `PFX file does not exist: ${pfxFile}`);
626
+ const passin = passinArg(passphrase);
627
+ return await execute_openssl(["pkcs12", "-in", n2(pfxFile), "-clcerts", "-nokeys", "-nodes", ...passin.args], {
628
+ env: passin.env
629
+ });
587
630
  }
588
631
  async function extractPrivateKeyFromPFX(options) {
589
632
  const { pfxFile, passphrase = "" } = options;
590
- assert4(fs4.existsSync(pfxFile), `PFX file does not exist: ${pfxFile}`);
591
- const cmd = `pkcs12 -in ${q(n2(pfxFile))} -nocerts -nodes -passin pass:${passphrase}`;
592
- return await execute_openssl(cmd, {});
633
+ assert3(fs4.existsSync(pfxFile), `PFX file does not exist: ${pfxFile}`);
634
+ const passin = passinArg(passphrase);
635
+ return await execute_openssl(["pkcs12", "-in", n2(pfxFile), "-nocerts", "-nodes", ...passin.args], { env: passin.env });
593
636
  }
594
637
  async function extractCACertificatesFromPFX(options) {
595
638
  const { pfxFile, passphrase = "" } = options;
596
- assert4(fs4.existsSync(pfxFile), `PFX file does not exist: ${pfxFile}`);
597
- const cmd = `pkcs12 -in ${q(n2(pfxFile))} -cacerts -nokeys -nodes -passin pass:${passphrase}`;
598
- return await execute_openssl(cmd, {});
639
+ assert3(fs4.existsSync(pfxFile), `PFX file does not exist: ${pfxFile}`);
640
+ const passin = passinArg(passphrase);
641
+ return await execute_openssl(["pkcs12", "-in", n2(pfxFile), "-cacerts", "-nokeys", "-nodes", ...passin.args], {
642
+ env: passin.env
643
+ });
599
644
  }
600
645
  async function extractAllFromPFX(options) {
601
646
  const [certificate, privateKey, caCertificates] = await Promise.all([
@@ -606,14 +651,50 @@ async function extractAllFromPFX(options) {
606
651
  return { certificate, privateKey, caCertificates };
607
652
  }
608
653
  async function convertPFXtoPEM(pfxFile, pemFile, passphrase = "") {
609
- assert4(fs4.existsSync(pfxFile), `PFX file does not exist: ${pfxFile}`);
610
- const cmd = `pkcs12 -in ${q(n2(pfxFile))} -out ${q(n2(pemFile))} -nodes -passin pass:${passphrase}`;
611
- await execute_openssl(cmd, {});
654
+ assert3(fs4.existsSync(pfxFile), `PFX file does not exist: ${pfxFile}`);
655
+ const passin = passinArg(passphrase);
656
+ await execute_openssl(["pkcs12", "-in", n2(pfxFile), "-out", n2(pemFile), "-nodes", ...passin.args], { env: passin.env });
612
657
  }
613
658
  async function dumpPFX(pfxFile, passphrase = "") {
614
- assert4(fs4.existsSync(pfxFile), `PFX file does not exist: ${pfxFile}`);
615
- const cmd = `pkcs12 -in ${q(n2(pfxFile))} -info -nodes -passin pass:${passphrase}`;
616
- return await execute_openssl(cmd, {});
659
+ assert3(fs4.existsSync(pfxFile), `PFX file does not exist: ${pfxFile}`);
660
+ const passin = passinArg(passphrase);
661
+ return await execute_openssl(["pkcs12", "-in", n2(pfxFile), "-info", "-nodes", ...passin.args], { env: passin.env });
662
+ }
663
+
664
+ // packages/node-opcua-pki/lib/toolbox/common.ts
665
+ import assert4 from "assert";
666
+ function quote(str) {
667
+ return `"${str || ""}"`;
668
+ }
669
+ async function resolvePrivateKeyPassphrase(passphrase) {
670
+ if (passphrase === void 0) {
671
+ return void 0;
672
+ }
673
+ return typeof passphrase === "function" ? await passphrase() : passphrase;
674
+ }
675
+ function adjustDate(params) {
676
+ assert4(params instanceof Object);
677
+ params.startDate = params.startDate || /* @__PURE__ */ new Date();
678
+ assert4(params.startDate instanceof Date);
679
+ if (params.validityMs !== void 0) {
680
+ if (params.validityMs <= 0) {
681
+ throw new RangeError(`validityMs must be > 0 (got ${params.validityMs})`);
682
+ }
683
+ params.endDate = new Date(params.startDate.getTime() + params.validityMs);
684
+ params.validity = Math.ceil(params.validityMs / 864e5);
685
+ } else {
686
+ params.validity = params.validity || 365;
687
+ params.endDate = new Date(params.startDate.getTime());
688
+ params.endDate.setDate(params.startDate.getDate() + params.validity);
689
+ }
690
+ assert4(params.endDate instanceof Date);
691
+ assert4(params.startDate instanceof Date);
692
+ }
693
+ function adjustApplicationUri(params) {
694
+ const applicationUri = params.applicationUri || "";
695
+ if (applicationUri.length > 200) {
696
+ throw new Error(`Openssl doesn't support urn with length greater than 200${applicationUri}`);
697
+ }
617
698
  }
618
699
 
619
700
  // packages/node-opcua-pki/lib/toolbox/display.ts
@@ -705,7 +786,6 @@ function x509Date(date) {
705
786
  }
706
787
 
707
788
  // packages/node-opcua-pki/lib/toolbox/with_openssl/create_certificate_signing_request.ts
708
- var q2 = quote;
709
789
  var n3 = makePath;
710
790
  async function createCertificateSigningRequestWithOpenSSL(certificateSigningRequestFilename, params) {
711
791
  assert6(params);
@@ -720,12 +800,23 @@ async function createCertificateSigningRequestWithOpenSSL(certificateSigningRequ
720
800
  processAltNames(params);
721
801
  const configFile = generateStaticConfig(params.configFile, { cwd: params.rootDir });
722
802
  const options = { cwd: params.rootDir, openssl_conf: path4.relative(params.rootDir, configFile) };
723
- const configOption = ` -config ${q2(n3(configFile))}`;
724
803
  const subject = params.subject ? new Subject(params.subject).toString() : void 0;
725
- const subjectOptions = subject ? ` -subj "${subject}"` : "";
726
804
  displaySubtitle("- Creating a Certificate Signing Request with openssl");
727
805
  await execute_openssl(
728
- "req -new -sha256 -batch -text " + configOption + " -key " + q2(n3(params.privateKey)) + subjectOptions + " -out " + q2(n3(certificateSigningRequestFilename)),
806
+ [
807
+ "req",
808
+ "-new",
809
+ "-sha256",
810
+ "-batch",
811
+ "-text",
812
+ "-config",
813
+ n3(configFile),
814
+ "-key",
815
+ n3(params.privateKey),
816
+ ...subject ? ["-subj", subject] : [],
817
+ "-out",
818
+ n3(certificateSigningRequestFilename)
819
+ ],
729
820
  options
730
821
  );
731
822
  }
@@ -739,7 +830,7 @@ var config2 = `#.........DO NOT MODIFY BY HAND .........................
739
830
  [ ca ]
740
831
  default_ca = CA_default
741
832
  [ CA_default ]
742
- dir = %%ROOT_FOLDER%% # the main CA folder
833
+ dir = "%%ROOT_FOLDER%%" # the main CA folder (quoted: see renderCaConfig)
743
834
  certs = $dir/certs # where to store certificates
744
835
  new_certs_dir = $dir/certs #
745
836
  database = $dir/index.txt # the certificate database
@@ -872,13 +963,18 @@ var ca_config_template_cnf_default = config2;
872
963
  var defaultSubject = "/C=FR/ST=IDF/L=Paris/O=Local NODE-OPCUA Certificate Authority/CN=NodeOPCUA-CA";
873
964
  var configurationFileTemplate = ca_config_template_cnf_default;
874
965
  var configurationFileSimpleTemplate = simple_config_template_cnf_default;
966
+ function escapeOpensslConfDoubleQuoted(value) {
967
+ return value.replace(/\\/g, "\\\\").replace(/"/g, '\\"');
968
+ }
969
+ function renderCaConfig(caRootDir) {
970
+ return configurationFileTemplate.replace(/%%ROOT_FOLDER%%/, escapeOpensslConfDoubleQuoted(makePath(caRootDir)));
971
+ }
875
972
  var config3 = {
876
973
  certificateDir: "INVALID",
877
974
  forceCA: false,
878
975
  pkiDir: "INVALID"
879
976
  };
880
977
  var n4 = makePath;
881
- var q3 = quote;
882
978
  function octetStringToIpAddress(a) {
883
979
  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();
884
980
  }
@@ -888,7 +984,7 @@ async function construct_CertificateAuthority(certificateAuthority) {
888
984
  const caRootDir = path5.resolve(certificateAuthority.rootDir);
889
985
  async function make_folders() {
890
986
  mkdirRecursiveSync(caRootDir);
891
- mkdirRecursiveSync(path5.join(caRootDir, "private"));
987
+ ensurePrivateDirectory(path5.join(caRootDir, "private"));
892
988
  mkdirRecursiveSync(path5.join(caRootDir, "public"));
893
989
  mkdirRecursiveSync(path5.join(caRootDir, "certs"));
894
990
  mkdirRecursiveSync(path5.join(caRootDir, "crl"));
@@ -913,6 +1009,8 @@ async function construct_CertificateAuthority(certificateAuthority) {
913
1009
  const caKeyExists = fs7.existsSync(path5.join(caRootDir, "private/cakey.pem"));
914
1010
  const caCertExists = fs7.existsSync(path5.join(caRootDir, "public/cacert.pem"));
915
1011
  if (caKeyExists && caCertExists && !config3.forceCA) {
1012
+ restrictPrivateFilePermissions(path5.join(caRootDir, "private/cakey.pem"), 384);
1013
+ await certificateAuthority._ensurePrivateKeyProtection();
916
1014
  debugLog("CA private key and certificate already exist ... skipping");
917
1015
  return;
918
1016
  }
@@ -931,26 +1029,40 @@ async function construct_CertificateAuthority(certificateAuthority) {
931
1029
  }
932
1030
  const caConfigFile = certificateAuthority.configFile;
933
1031
  if (1) {
934
- let data = configurationFileTemplate;
935
- data = makePath(data.replace(/%%ROOT_FOLDER%%/, caRootDir));
936
- await fs7.promises.writeFile(caConfigFile, data);
1032
+ await fs7.promises.writeFile(caConfigFile, renderCaConfig(caRootDir));
937
1033
  }
938
- const subjectOpt = ` -subj "${subject.toString()}" `;
1034
+ const subjectOpt = ["-subj", subject.toString()];
939
1035
  const caCommonName = subject.commonName || "NodeOPCUA-CA";
940
1036
  setEnv("ALTNAME", `URI:urn:${caCommonName}`);
941
1037
  certificateAuthority._wireRevocationEnvVars();
942
1038
  const options = { cwd: caRootDir };
943
1039
  const configFile = generateStaticConfig("conf/caconfig.cnf", options);
944
- const configOption = ` -config ${q3(n4(configFile))}`;
1040
+ const configOption = ["-config", n4(configFile)];
945
1041
  const keySize = certificateAuthority.keySize;
946
1042
  const privateKeyFilename = path5.join(caRootDir, "private/cakey.pem");
947
1043
  const csrFilename = path5.join(caRootDir, "private/cakey.csr");
948
1044
  displayTitle(`Generate the CA private Key - ${keySize}`);
949
- await generatePrivateKeyFile(privateKeyFilename, keySize);
1045
+ const passin = await certificateAuthority._opensslPassin();
1046
+ await generatePrivateKeyFile(privateKeyFilename, keySize, { passphrase: await certificateAuthority._privateKeyPassphrase() });
1047
+ restrictPrivateFilePermissions(privateKeyFilename, 384);
950
1048
  displayTitle("Generate a certificate request for the CA key");
951
1049
  await execute_openssl(
952
- "req -new -sha256 -text -extensions v3_ca_req" + configOption + " -key " + q3(n4(privateKeyFilename)) + " -out " + q3(n4(csrFilename)) + " " + subjectOpt,
953
- options
1050
+ [
1051
+ "req",
1052
+ "-new",
1053
+ "-sha256",
1054
+ "-text",
1055
+ "-extensions",
1056
+ "v3_ca_req",
1057
+ ...configOption,
1058
+ "-key",
1059
+ n4(privateKeyFilename),
1060
+ "-out",
1061
+ n4(csrFilename),
1062
+ ...subjectOpt,
1063
+ ...passin.args
1064
+ ],
1065
+ { ...options, env: passin.env }
954
1066
  );
955
1067
  const issuerCA = certificateAuthority._issuerCA;
956
1068
  if (issuerCA) {
@@ -958,27 +1070,71 @@ async function construct_CertificateAuthority(certificateAuthority) {
958
1070
  const issuerCert = path5.resolve(issuerCA.caCertificate);
959
1071
  const issuerKey = path5.resolve(issuerCA.rootDir, "private/cakey.pem");
960
1072
  const issuerSerial = path5.resolve(issuerCA.rootDir, "serial");
1073
+ const issuerPassin = await issuerCA._opensslPassin();
961
1074
  await execute_openssl(
962
- " x509 -sha256 -req -days 3650 -text -extensions v3_ca -extfile " + q3(n4(configFile)) + " -in private/cakey.csr -CA " + q3(n4(issuerCert)) + " -CAkey " + q3(n4(issuerKey)) + " -CAserial " + q3(n4(issuerSerial)) + " -out public/cacert.pem",
963
- options
1075
+ [
1076
+ "x509",
1077
+ "-sha256",
1078
+ "-req",
1079
+ "-days",
1080
+ "3650",
1081
+ "-text",
1082
+ "-extensions",
1083
+ "v3_ca",
1084
+ "-extfile",
1085
+ n4(configFile),
1086
+ "-in",
1087
+ "private/cakey.csr",
1088
+ "-CA",
1089
+ n4(issuerCert),
1090
+ "-CAkey",
1091
+ n4(issuerKey),
1092
+ "-CAserial",
1093
+ n4(issuerSerial),
1094
+ "-out",
1095
+ "public/cacert.pem",
1096
+ ...issuerPassin.args
1097
+ ],
1098
+ { ...options, env: issuerPassin.env }
964
1099
  );
965
1100
  } else {
966
1101
  displayTitle("Generate CA Certificate (self-signed)");
967
1102
  await execute_openssl(
968
- " x509 -sha256 -req -days 3650 -text -extensions v3_ca -extfile " + q3(n4(configFile)) + " -in private/cakey.csr -signkey " + q3(n4(privateKeyFilename)) + " -out public/cacert.pem",
969
- options
1103
+ [
1104
+ "x509",
1105
+ "-sha256",
1106
+ "-req",
1107
+ "-days",
1108
+ "3650",
1109
+ "-text",
1110
+ "-extensions",
1111
+ "v3_ca",
1112
+ "-extfile",
1113
+ n4(configFile),
1114
+ "-in",
1115
+ "private/cakey.csr",
1116
+ "-signkey",
1117
+ n4(privateKeyFilename),
1118
+ "-out",
1119
+ "public/cacert.pem",
1120
+ ...passin.args
1121
+ ],
1122
+ { ...options, env: passin.env }
970
1123
  );
971
1124
  }
972
1125
  displaySubtitle("generate initial CRL (Certificate Revocation List)");
973
- await regenerateCrl(certificateAuthority.revocationList, configOption, options);
1126
+ await regenerateCrl(certificateAuthority.revocationList, configOption, options, passin);
974
1127
  displayTitle("Create Certificate Authority (CA) ---> DONE");
975
1128
  }
976
- async function regenerateCrl(revocationList, configOption, options) {
1129
+ async function regenerateCrl(revocationList, configOption, options, passin) {
977
1130
  displaySubtitle("regenerate CRL (Certificate Revocation List)");
978
- await execute_openssl(`ca -gencrl ${configOption} -out crl/revocation_list.crl`, options);
979
- await execute_openssl("crl -in crl/revocation_list.crl -out crl/revocation_list.der -outform der", options);
1131
+ await execute_openssl(["ca", "-gencrl", ...configOption, "-out", "crl/revocation_list.crl", ...passin.args], {
1132
+ ...options,
1133
+ env: passin.env
1134
+ });
1135
+ await execute_openssl(["crl", "-in", "crl/revocation_list.crl", "-out", "crl/revocation_list.der", "-outform", "der"], options);
980
1136
  displaySubtitle("Display (Certificate Revocation List)");
981
- await execute_openssl(`crl -in ${q3(n4(revocationList))} -text -noout`, options);
1137
+ await execute_openssl(["crl", "-in", n4(revocationList), "-text", "-noout"], options);
982
1138
  }
983
1139
  function parseOpenSSLDate(dateStr) {
984
1140
  const raw = dateStr?.split(",")[0] ?? "";
@@ -1032,6 +1188,10 @@ var CertificateAuthority = class {
1032
1188
  _crlDistributionUrl;
1033
1189
  _ocspResponderUrl;
1034
1190
  _caIssuersUrl;
1191
+ #privateKeyPassphrase;
1192
+ /** resolved once (see `privateKeyPassphrase`); `#passphraseResolved` distinguishes "none" from "not yet" */
1193
+ #resolvedPassphrase;
1194
+ #passphraseResolved = false;
1035
1195
  constructor(options) {
1036
1196
  assert7(Object.prototype.hasOwnProperty.call(options, "location"));
1037
1197
  assert7(Object.prototype.hasOwnProperty.call(options, "keySize"));
@@ -1039,6 +1199,7 @@ var CertificateAuthority = class {
1039
1199
  this.keySize = options.keySize || 2048;
1040
1200
  this.subject = new Subject2(options.subject || defaultSubject);
1041
1201
  this._issuerCA = options.issuerCA;
1202
+ this.#privateKeyPassphrase = options.privateKeyPassphrase;
1042
1203
  if (options.crlDistributionUrl !== void 0) {
1043
1204
  this.setCrlDistributionUrl(options.crlDistributionUrl);
1044
1205
  }
@@ -1135,6 +1296,69 @@ var CertificateAuthority = class {
1135
1296
  get configFile() {
1136
1297
  return path5.normalize(path5.join(this.rootDir, "./conf/caconfig.cnf"));
1137
1298
  }
1299
+ /** Path to the CA private key (`private/cakey.pem`); may be passphrase-encrypted, see {@link getPrivateKey}. */
1300
+ get privateKey() {
1301
+ return path5.join(path5.resolve(this.rootDir), "private/cakey.pem");
1302
+ }
1303
+ /**
1304
+ * The CA private key, decrypted with the configured `privateKeyPassphrase`
1305
+ * if it is encrypted. Fails closed (`PrivateKeyPassphraseRequiredError`)
1306
+ * on an encrypted key with no or the wrong passphrase.
1307
+ */
1308
+ async getPrivateKey() {
1309
+ return readPrivateKey(this.privateKey, await this._privateKeyPassphrase());
1310
+ }
1311
+ /**
1312
+ * Enable, disable, or rotate the passphrase protecting `private/cakey.pem`
1313
+ * (temp file + atomic rename, temp file removed on failure). Only
1314
+ * rewrites the file: construct a new `CertificateAuthority` with the new
1315
+ * passphrase to continue using it.
1316
+ */
1317
+ async reencryptPrivateKey(oldPassphrase, newPassphrase) {
1318
+ const oldPass = await resolvePrivateKeyPassphrase(oldPassphrase);
1319
+ const newPass = await resolvePrivateKeyPassphrase(newPassphrase);
1320
+ const key = readPrivateKey(this.privateKey, oldPass);
1321
+ await this.#rewritePrivateKeyFile(key, newPass);
1322
+ }
1323
+ async #rewritePrivateKeyFile(privateKey, passphrase) {
1324
+ const tmpFilename = `${this.privateKey}.${process.pid}-${Date.now()}.tmp`;
1325
+ try {
1326
+ await writePrivateKeyFile(tmpFilename, privateKey, { passphrase });
1327
+ await fs7.promises.rename(tmpFilename, this.privateKey);
1328
+ } finally {
1329
+ await fs7.promises.rm(tmpFilename, { force: true });
1330
+ }
1331
+ }
1332
+ /** @internal resolve the configured passphrase, at most once per instance */
1333
+ async _privateKeyPassphrase() {
1334
+ if (!this.#passphraseResolved) {
1335
+ this.#resolvedPassphrase = await resolvePrivateKeyPassphrase(this.#privateKeyPassphrase);
1336
+ this.#passphraseResolved = true;
1337
+ }
1338
+ return this.#resolvedPassphrase;
1339
+ }
1340
+ /** @internal `-passin env:` argv + env for every openssl call that loads this CA's key (always emitted, empty when none) */
1341
+ async _opensslPassin() {
1342
+ return passinArg(await this._privateKeyPassphrase());
1343
+ }
1344
+ /**
1345
+ * @internal On an existing key: encrypt it in place if a passphrase is
1346
+ * configured and it is still plaintext (secure by default: the option
1347
+ * means "protect this key", not "ignore me"), then read it back so a
1348
+ * wrong or missing passphrase fails initialize() closed rather than the
1349
+ * first signing operation.
1350
+ */
1351
+ async _ensurePrivateKeyProtection() {
1352
+ if (!fs7.existsSync(this.privateKey)) {
1353
+ return;
1354
+ }
1355
+ if (this.#privateKeyPassphrase !== void 0 && !isEncryptedPrivateKeyFile(this.privateKey)) {
1356
+ warningLog("CertificateAuthority: private key is plaintext but a passphrase is configured; encrypting it in place");
1357
+ const plaintextKey = readPrivateKey(this.privateKey);
1358
+ await this.#rewritePrivateKeyFile(plaintextKey, await this._privateKeyPassphrase());
1359
+ }
1360
+ await this.getPrivateKey();
1361
+ }
1138
1362
  /** Path to the CA certificate in PEM format (`public/cacert.pem`). */
1139
1363
  get caCertificate() {
1140
1364
  return makePath(this.rootDir, "./public/cacert.pem");
@@ -1592,12 +1816,17 @@ var CertificateAuthority = class {
1592
1816
  async initializeCSR() {
1593
1817
  const caRootDir = path5.resolve(this.rootDir);
1594
1818
  mkdirRecursiveSync(caRootDir);
1595
- for (const dir of ["private", "public", "certs", "crl", "conf"]) {
1819
+ for (const dir of ["public", "certs", "crl", "conf"]) {
1596
1820
  mkdirRecursiveSync(path5.join(caRootDir, dir));
1597
1821
  }
1822
+ ensurePrivateDirectory(path5.join(caRootDir, "private"));
1598
1823
  const caCertFile = this.caCertificate;
1599
1824
  const privateKeyFile = path5.join(caRootDir, "private/cakey.pem");
1600
1825
  const csrFile = path5.join(caRootDir, "private/cakey.csr");
1826
+ if (fs7.existsSync(privateKeyFile)) {
1827
+ restrictPrivateFilePermissions(privateKeyFile, 384);
1828
+ await this._ensurePrivateKeyProtection();
1829
+ }
1601
1830
  if (fs7.existsSync(caCertFile)) {
1602
1831
  const certDer = convertPEMtoDER(readCertificatePEM(caCertFile));
1603
1832
  const certInfo = exploreCertificate(certDer);
@@ -1631,11 +1860,10 @@ var CertificateAuthority = class {
1631
1860
  await fs7.promises.writeFile(indexFileAttr, "unique_subject = no");
1632
1861
  }
1633
1862
  const caConfigFile = this.configFile;
1634
- let data = configurationFileTemplate;
1635
- data = makePath(data.replace(/%%ROOT_FOLDER%%/, caRootDir));
1636
- await fs7.promises.writeFile(caConfigFile, data);
1863
+ await fs7.promises.writeFile(caConfigFile, renderCaConfig(caRootDir));
1637
1864
  if (!fs7.existsSync(privateKeyFile)) {
1638
- await generatePrivateKeyFile(privateKeyFile, this.keySize);
1865
+ await generatePrivateKeyFile(privateKeyFile, this.keySize, { passphrase: await this._privateKeyPassphrase() });
1866
+ restrictPrivateFilePermissions(privateKeyFile, 384);
1639
1867
  }
1640
1868
  await this._generateCSR(caRootDir, privateKeyFile, csrFile);
1641
1869
  return { status: "created", csrPath: csrFile };
@@ -1677,14 +1905,29 @@ var CertificateAuthority = class {
1677
1905
  * @internal
1678
1906
  */
1679
1907
  async _generateCSR(caRootDir, privateKeyFile, csrFile) {
1680
- const subjectOpt = ` -subj "${this.subject.toString()}" `;
1681
1908
  processAltNames({});
1682
1909
  const options = { cwd: caRootDir };
1683
1910
  const configFile = generateStaticConfig("conf/caconfig.cnf", options);
1684
- const configOption = ` -config ${q3(n4(configFile))}`;
1911
+ const passin = await this._opensslPassin();
1685
1912
  await execute_openssl(
1686
- "req -new -sha256 -text -extensions v3_ca_req" + configOption + " -key " + q3(n4(privateKeyFile)) + " -out " + q3(n4(csrFile)) + " " + subjectOpt,
1687
- options
1913
+ [
1914
+ "req",
1915
+ "-new",
1916
+ "-sha256",
1917
+ "-text",
1918
+ "-extensions",
1919
+ "v3_ca_req",
1920
+ "-config",
1921
+ n4(configFile),
1922
+ "-key",
1923
+ n4(privateKeyFile),
1924
+ "-out",
1925
+ n4(csrFile),
1926
+ "-subj",
1927
+ this.subject.toString(),
1928
+ ...passin.args
1929
+ ],
1930
+ { ...options, env: passin.env }
1688
1931
  );
1689
1932
  }
1690
1933
  /**
@@ -1706,7 +1949,6 @@ var CertificateAuthority = class {
1706
1949
  async installCACertificate(signedCertFile) {
1707
1950
  const caRootDir = path5.resolve(this.rootDir);
1708
1951
  const caCertFile = this.caCertificate;
1709
- const privateKeyFile = path5.join(caRootDir, "private/cakey.pem");
1710
1952
  const fullPem = await fs7.promises.readFile(signedCertFile, "utf8");
1711
1953
  const pemBlocks = fullPem.match(/-----BEGIN CERTIFICATE-----[\s\S]*?-----END CERTIFICATE-----/g);
1712
1954
  if (!pemBlocks || pemBlocks.length === 0) {
@@ -1717,7 +1959,7 @@ var CertificateAuthority = class {
1717
1959
  };
1718
1960
  }
1719
1961
  const certDer = convertPEMtoDER(pemBlocks[0]);
1720
- const privateKey = readPrivateKey(privateKeyFile);
1962
+ const privateKey = await this.getPrivateKey();
1721
1963
  if (!certificateMatchesPrivateKey(certDer, privateKey)) {
1722
1964
  return {
1723
1965
  status: "error",
@@ -1740,8 +1982,7 @@ var CertificateAuthority = class {
1740
1982
  }
1741
1983
  const options = { cwd: caRootDir };
1742
1984
  const configFile = generateStaticConfig("conf/caconfig.cnf", options);
1743
- const configOption = ` -config ${q3(n4(configFile))}`;
1744
- await regenerateCrl(this.revocationList, configOption, options);
1985
+ await regenerateCrl(this.revocationList, ["-config", n4(configFile)], options, await this._opensslPassin());
1745
1986
  return { status: "success" };
1746
1987
  }
1747
1988
  /**
@@ -1763,9 +2004,32 @@ var CertificateAuthority = class {
1763
2004
  this._wireRevocationEnvVars();
1764
2005
  const configFile = generateStaticConfig("conf/caconfig.cnf", options);
1765
2006
  const validity = params.validity ?? 3650;
2007
+ const passin = await this._opensslPassin();
1766
2008
  await execute_openssl(
1767
- ` x509 -sha256 -req -days ${validity} -text -extensions v3_ca -extfile ` + q3(n4(configFile)) + " -in " + q3(n4(csrFile)) + " -CA " + q3(n4(this.caCertificate)) + " -CAkey " + q3(n4(path5.join(caRootDir, "private/cakey.pem"))) + " -CAserial " + q3(n4(path5.join(caRootDir, "serial"))) + " -out " + q3(n4(certFile)),
1768
- options
2009
+ [
2010
+ "x509",
2011
+ "-sha256",
2012
+ "-req",
2013
+ "-days",
2014
+ String(validity),
2015
+ "-text",
2016
+ "-extensions",
2017
+ "v3_ca",
2018
+ "-extfile",
2019
+ n4(configFile),
2020
+ "-in",
2021
+ n4(csrFile),
2022
+ "-CA",
2023
+ n4(this.caCertificate),
2024
+ "-CAkey",
2025
+ n4(path5.join(caRootDir, "private/cakey.pem")),
2026
+ "-CAserial",
2027
+ n4(path5.join(caRootDir, "serial")),
2028
+ "-out",
2029
+ n4(certFile),
2030
+ ...passin.args
2031
+ ],
2032
+ { ...options, env: passin.env }
1769
2033
  );
1770
2034
  await this.constructCertificateChain(certFile);
1771
2035
  }
@@ -1827,23 +2091,36 @@ var CertificateAuthority = class {
1827
2091
  cwd: this.rootDir,
1828
2092
  openssl_conf: makePath(configFile)
1829
2093
  };
1830
- const configOption = "";
1831
2094
  const subject = params.subject ? new Subject2(params.subject).toString() : "";
1832
- const subjectOptions = subject && subject.length > 1 ? ` -subj ${subject} ` : "";
2095
+ const subjectOptions = subject && subject.length > 1 ? ["-subj", subject] : [];
1833
2096
  displaySubtitle("- the certificate signing request");
1834
2097
  await execute_openssl(
1835
- "req -new -sha256 -text " + configOption + subjectOptions + " -batch -key " + q3(n4(privateKey)) + " -out " + q3(n4(csrFile)),
2098
+ ["req", "-new", "-sha256", "-text", ...subjectOptions, "-batch", "-key", n4(privateKey), "-out", n4(csrFile)],
1836
2099
  options
1837
2100
  );
1838
2101
  displaySubtitle("- creating the self-signed certificate");
1839
2102
  await execute_openssl(
1840
- "ca -selfsign -keyfile " + q3(n4(privateKey)) + " -startdate " + x509Date(params.startDate) + " -enddate " + x509Date(params.endDate) + " -batch -out " + q3(n4(certificateFile)) + " -in " + q3(n4(csrFile)),
2103
+ [
2104
+ "ca",
2105
+ "-selfsign",
2106
+ "-keyfile",
2107
+ n4(privateKey),
2108
+ "-startdate",
2109
+ x509Date(params.startDate),
2110
+ "-enddate",
2111
+ x509Date(params.endDate),
2112
+ "-batch",
2113
+ "-out",
2114
+ n4(certificateFile),
2115
+ "-in",
2116
+ n4(csrFile)
2117
+ ],
1841
2118
  options
1842
2119
  );
1843
2120
  displaySubtitle("- dump the certificate for a check");
1844
- await execute_openssl(`x509 -in ${q3(n4(certificateFile))} -dates -fingerprint -purpose -noout`, {});
2121
+ await execute_openssl(["x509", "-in", n4(certificateFile), "-dates", "-fingerprint", "-purpose", "-noout"], {});
1845
2122
  displaySubtitle("- verify self-signed certificate");
1846
- await execute_openssl_no_failure(`verify -verbose -CAfile ${q3(n4(certificateFile))} ${q3(n4(certificateFile))}`, options);
2123
+ await execute_openssl_no_failure(["verify", "-verbose", "-CAfile", n4(certificateFile), n4(certificateFile)], options);
1847
2124
  await fs7.promises.unlink(csrFile);
1848
2125
  }
1849
2126
  /**
@@ -1873,22 +2150,41 @@ var CertificateAuthority = class {
1873
2150
  setEnv("ALTNAME", "");
1874
2151
  const randomFile = path5.join(this.rootDir, "random.rnd");
1875
2152
  setEnv("RANDFILE", randomFile);
1876
- const configOption = ` -config ${q3(n4(configFile))}`;
2153
+ const configOption = ["-config", n4(configFile)];
1877
2154
  const reason = params.reason || "keyCompromise";
1878
2155
  assert7(crlReasons.indexOf(reason) >= 0);
1879
2156
  displayTitle(`Revoking certificate ${certificate}`);
1880
2157
  displaySubtitle("Revoke certificate");
1881
- await execute_openssl_no_failure(`ca -verbose ${configOption} -revoke ${q3(certificate)} -crl_reason ${reason}`, options);
1882
- await regenerateCrl(this.revocationList, configOption, options);
2158
+ const passin = await this._opensslPassin();
2159
+ await execute_openssl_no_failure(
2160
+ ["ca", "-verbose", ...configOption, "-revoke", certificate, "-crl_reason", reason, ...passin.args],
2161
+ { ...options, env: passin.env }
2162
+ );
2163
+ await regenerateCrl(this.revocationList, configOption, options, passin);
1883
2164
  displaySubtitle("Verify that certificate is revoked");
1884
2165
  await execute_openssl_no_failure(
1885
- "verify -verbose -CRLfile " + q3(n4(this.revocationList)) + " -CAfile " + q3(n4(this.caCertificate)) + " -crl_check " + q3(n4(certificate)),
2166
+ [
2167
+ "verify",
2168
+ "-verbose",
2169
+ "-CRLfile",
2170
+ n4(this.revocationList),
2171
+ "-CAfile",
2172
+ n4(this.caCertificate),
2173
+ "-crl_check",
2174
+ n4(certificate)
2175
+ ],
1886
2176
  options
1887
2177
  );
1888
2178
  displaySubtitle("Produce CRL in DER form ");
1889
- await execute_openssl(`crl -in ${q3(n4(this.revocationList))} -out crl/revocation_list.der -outform der`, options);
2179
+ await execute_openssl(
2180
+ ["crl", "-in", n4(this.revocationList), "-out", "crl/revocation_list.der", "-outform", "der"],
2181
+ options
2182
+ );
1890
2183
  displaySubtitle("Produce CRL in PEM form ");
1891
- await execute_openssl(`crl -in ${q3(n4(this.revocationList))} -out crl/revocation_list.pem -outform pem -text `, options);
2184
+ await execute_openssl(
2185
+ ["crl", "-in", n4(this.revocationList), "-out", "crl/revocation_list.pem", "-outform", "pem", "-text"],
2186
+ options
2187
+ );
1892
2188
  }
1893
2189
  /**
1894
2190
  * Sign a Certificate Signing Request (CSR) with this CA.
@@ -1930,13 +2226,27 @@ var CertificateAuthority = class {
1930
2226
  this._wireRevocationEnvVars();
1931
2227
  const configFile = generateStaticConfig("conf/caconfig.cnf", options);
1932
2228
  displaySubtitle("- then we ask the authority to sign the certificate signing request");
1933
- const configOption = ` -config ${configFile}`;
2229
+ const passin = await this._opensslPassin();
1934
2230
  await execute_openssl(
1935
- "ca " + configOption + " -startdate " + x509Date(params1.startDate) + " -enddate " + x509Date(params1.endDate) + " -batch -out " + q3(n4(certificate)) + " -in " + q3(n4(certificateSigningRequestFilename)),
1936
- options
2231
+ [
2232
+ "ca",
2233
+ "-config",
2234
+ configFile,
2235
+ "-startdate",
2236
+ x509Date(params1.startDate),
2237
+ "-enddate",
2238
+ x509Date(params1.endDate),
2239
+ "-batch",
2240
+ "-out",
2241
+ n4(certificate),
2242
+ "-in",
2243
+ n4(certificateSigningRequestFilename),
2244
+ ...passin.args
2245
+ ],
2246
+ { ...options, env: passin.env }
1937
2247
  );
1938
2248
  displaySubtitle("- dump the certificate for a check");
1939
- await execute_openssl(`x509 -in ${q3(n4(certificate))} -dates -fingerprint -purpose -noout`, options);
2249
+ await execute_openssl(["x509", "-in", n4(certificate), "-dates", "-fingerprint", "-purpose", "-noout"], options);
1940
2250
  displaySubtitle("- construct CA certificate with CRL");
1941
2251
  await this.constructCACertificateWithCRL();
1942
2252
  displaySubtitle("- construct certificate chain");
@@ -1956,10 +2266,8 @@ var CertificateAuthority = class {
1956
2266
  const options = { cwd: this.rootDir };
1957
2267
  const configFile = generateStaticConfig("conf/caconfig.cnf", options);
1958
2268
  setEnv("OPENSSL_CONF", makePath(configFile));
1959
- const _configOption = ` -config ${configFile}`;
1960
- _configOption;
1961
2269
  await execute_openssl_no_failure(
1962
- `verify -verbose -CAfile ${q3(n4(this.caCertificateWithCrl))} ${q3(n4(certificate))}`,
2270
+ ["verify", "-verbose", "-CAfile", n4(this.caCertificateWithCrl), n4(certificate)],
1963
2271
  options
1964
2272
  );
1965
2273
  }
@@ -1982,27 +2290,30 @@ import {
1982
2290
  readCertificateChain,
1983
2291
  readCertificateChainAsync,
1984
2292
  readCertificateRevocationList,
2293
+ readPrivateKey as readPrivateKey2,
1985
2294
  split_der,
1986
2295
  toPem as toPem2,
1987
- verifyCertificateSignature
2296
+ verifyCertificateSignature,
2297
+ writePrivateKeyFile as writePrivateKeyFile2
1988
2298
  } from "node-opcua-crypto";
1989
2299
 
1990
2300
  // packages/node-opcua-pki/lib/toolbox/without_openssl/create_certificate_signing_request.ts
1991
2301
  import assert8 from "assert";
1992
2302
  import fs8 from "fs";
1993
- import { createCertificateSigningRequest, pemToPrivateKey, Subject as Subject3 } from "node-opcua-crypto";
2303
+ import { coercePrivateKeyPem, createCertificateSigningRequest, pemToPrivateKey, Subject as Subject3 } from "node-opcua-crypto";
1994
2304
  async function createCertificateSigningRequestAsync(certificateSigningRequestFilename, params) {
1995
2305
  assert8(params);
1996
2306
  assert8(params.rootDir);
1997
2307
  assert8(params.configFile);
1998
2308
  assert8(params.privateKey);
1999
- assert8(typeof params.privateKey === "string");
2000
- assert8(fs8.existsSync(params.privateKey), `Private key must exist${params.privateKey}`);
2309
+ if (typeof params.privateKey === "string") {
2310
+ assert8(fs8.existsSync(params.privateKey), `Private key must exist${params.privateKey}`);
2311
+ }
2001
2312
  assert8(fs8.existsSync(params.rootDir), "RootDir key must exist");
2002
2313
  assert8(typeof certificateSigningRequestFilename === "string");
2003
2314
  const subject = params.subject ? new Subject3(params.subject).toString() : void 0;
2004
2315
  displaySubtitle("- Creating a Certificate Signing Request with subtile");
2005
- const privateKeyPem = await fs8.promises.readFile(params.privateKey, "utf-8");
2316
+ const privateKeyPem = typeof params.privateKey === "string" ? await fs8.promises.readFile(params.privateKey, "utf-8") : coercePrivateKeyPem(params.privateKey);
2006
2317
  const privateKey = await pemToPrivateKey(privateKeyPem);
2007
2318
  const { csr } = await createCertificateSigningRequest({
2008
2319
  privateKey,
@@ -2013,7 +2324,7 @@ async function createCertificateSigningRequestAsync(certificateSigningRequestFil
2013
2324
  purpose: params.purpose
2014
2325
  });
2015
2326
  await fs8.promises.writeFile(certificateSigningRequestFilename, csr, "utf-8");
2016
- display(`- privateKey ${params.privateKey}`);
2327
+ display(`- privateKey ${typeof params.privateKey === "string" ? params.privateKey : "<in-memory>"}`);
2017
2328
  display(`- certificateSigningRequestFilename ${certificateSigningRequestFilename}`);
2018
2329
  }
2019
2330
 
@@ -2022,6 +2333,7 @@ import assert9 from "assert";
2022
2333
  import fs9 from "fs";
2023
2334
  import {
2024
2335
  CertificatePurpose as CertificatePurpose2,
2336
+ coercePrivateKeyPem as coercePrivateKeyPem2,
2025
2337
  createSelfSignedCertificate as createSelfSignedCertificate1,
2026
2338
  pemToPrivateKey as pemToPrivateKey2,
2027
2339
  Subject as Subject4
@@ -2031,7 +2343,9 @@ async function createSelfSignedCertificateAsync(certificate, params) {
2031
2343
  assert9(params.purpose, "Please provide a Certificate Purpose");
2032
2344
  assert9(fs9.existsSync(params.configFile));
2033
2345
  assert9(fs9.existsSync(params.rootDir));
2034
- assert9(fs9.existsSync(params.privateKey));
2346
+ if (typeof params.privateKey === "string") {
2347
+ assert9(fs9.existsSync(params.privateKey));
2348
+ }
2035
2349
  if (!params.subject) {
2036
2350
  throw Error("Missing subject");
2037
2351
  }
@@ -2043,7 +2357,7 @@ async function createSelfSignedCertificateAsync(certificate, params) {
2043
2357
  subject = subject.toString();
2044
2358
  const purpose = params.purpose;
2045
2359
  displayTitle("Generate a certificate request");
2046
- const privateKeyPem = await fs9.promises.readFile(params.privateKey, "utf-8");
2360
+ const privateKeyPem = typeof params.privateKey === "string" ? await fs9.promises.readFile(params.privateKey, "utf-8") : coercePrivateKeyPem2(params.privateKey);
2047
2361
  const privateKey = await pemToPrivateKey2(privateKeyPem);
2048
2362
  const { cert } = await createSelfSignedCertificate1({
2049
2363
  privateKey,
@@ -2310,6 +2624,15 @@ var CertificateManager = class _CertificateManager extends EventEmitter {
2310
2624
  #initializingPromise;
2311
2625
  #addCertValidation;
2312
2626
  #disableFileWatchers;
2627
+ #privateKeyPassphrase;
2628
+ #privateKeyProvider;
2629
+ /**
2630
+ * The on-disk key, decrypted once and kept for the instance's lifetime,
2631
+ * so the passphrase (or its resolver function) is consulted at most
2632
+ * once. Cleared by `dispose()`. Not used when a provider is configured:
2633
+ * the provider is the authority on the current key.
2634
+ */
2635
+ #cachedPrivateKey;
2313
2636
  #thumbs = {
2314
2637
  rejected: /* @__PURE__ */ new Map(),
2315
2638
  trusted: /* @__PURE__ */ new Map(),
@@ -2344,6 +2667,8 @@ var CertificateManager = class _CertificateManager extends EventEmitter {
2344
2667
  maxChainLength: v.maxChainLength ?? 5
2345
2668
  };
2346
2669
  this.#disableFileWatchers = options.disableFileWatchers ?? process.env.OPCUA_PKI_DISABLE_FILE_WATCHERS === "true";
2670
+ this.#privateKeyPassphrase = options.privateKeyPassphrase;
2671
+ this.#privateKeyProvider = options.privateKeyProvider;
2347
2672
  mkdirRecursiveSync(options.location);
2348
2673
  if (!fs10.existsSync(this.#location)) {
2349
2674
  throw new Error(`CertificateManager cannot access location ${this.#location}`);
@@ -2357,10 +2682,108 @@ var CertificateManager = class _CertificateManager extends EventEmitter {
2357
2682
  get rootDir() {
2358
2683
  return this.#location;
2359
2684
  }
2360
- /** Path to the private key file (`own/private/private_key.pem`). */
2685
+ /**
2686
+ * Path to the private key file (`own/private/private_key.pem`).
2687
+ *
2688
+ * Kept for backward compatibility with code that reads the key
2689
+ * directly from disk. When a passphrase or a `privateKeyProvider` is
2690
+ * configured, prefer {@link getPrivateKey} instead — this getter still
2691
+ * returns the on-disk path even if a provider is configured (there may
2692
+ * be no meaningful file in that case).
2693
+ */
2361
2694
  get privateKey() {
2362
2695
  return path6.join(this.rootDir, "own/private/private_key.pem");
2363
2696
  }
2697
+ /**
2698
+ * Resolve the private key: from `privateKeyProvider` if configured,
2699
+ * otherwise from disk (decrypting with `privateKeyPassphrase` if the
2700
+ * key is encrypted). Fails closed — throws
2701
+ * `PrivateKeyPassphraseRequiredError` — if the on-disk key is encrypted
2702
+ * and no passphrase is configured, or if the wrong passphrase is
2703
+ * configured.
2704
+ *
2705
+ * The on-disk key is read and decrypted once and then cached for the
2706
+ * lifetime of this instance, so a `privateKeyPassphrase` function is
2707
+ * called at most once (concurrent first calls share the same read). A
2708
+ * failed read is not cached, so a caller can fix the passphrase and
2709
+ * retry. A `privateKeyProvider` is consulted on every call: it is the
2710
+ * authority on what the current key is.
2711
+ */
2712
+ async getPrivateKey() {
2713
+ if (this.#privateKeyProvider) {
2714
+ return await this.#privateKeyProvider.getPrivateKey();
2715
+ }
2716
+ if (this.#cachedPrivateKey) {
2717
+ return this.#cachedPrivateKey;
2718
+ }
2719
+ if (!this.#privateKeyPromise) {
2720
+ this.#privateKeyPromise = (async () => {
2721
+ const passphrase = await resolvePrivateKeyPassphrase(this.#privateKeyPassphrase);
2722
+ return readPrivateKey2(this.privateKey, passphrase);
2723
+ })().then(
2724
+ (key) => {
2725
+ this.#cachedPrivateKey = key;
2726
+ return key;
2727
+ },
2728
+ (err) => {
2729
+ this.#privateKeyPromise = void 0;
2730
+ throw err;
2731
+ }
2732
+ );
2733
+ }
2734
+ return await this.#privateKeyPromise;
2735
+ }
2736
+ /** In-flight first read of the on-disk key, so concurrent callers share one passphrase resolution. */
2737
+ #privateKeyPromise;
2738
+ /**
2739
+ * Enable, disable, or rotate the passphrase protecting the on-disk
2740
+ * private key: decrypt with `oldPassphrase` (omit if the key is
2741
+ * currently unencrypted), then write back encrypted with
2742
+ * `newPassphrase` (omit to leave it unencrypted). The write goes to a
2743
+ * temporary file in the same directory and is atomically renamed into
2744
+ * place, so a crash mid-rotation cannot leave a partially-written key;
2745
+ * the temporary file is removed if anything fails, so a rotation *to*
2746
+ * plaintext can never leave a stray cleartext copy behind. Runs under
2747
+ * the same lock as `initialize()`.
2748
+ *
2749
+ * This only rewrites the on-disk file — it does not update this
2750
+ * instance's own `privateKeyPassphrase` (set at construction), and it
2751
+ * drops this instance's cached key so that disk stays the source of
2752
+ * truth. Construct a new `CertificateManager` with the new passphrase to
2753
+ * continue using it afterward.
2754
+ *
2755
+ * Not supported when a `privateKeyProvider` is configured (there is no
2756
+ * disk file for this method to rewrite).
2757
+ */
2758
+ async reencryptPrivateKey(oldPassphrase, newPassphrase) {
2759
+ if (this.#privateKeyProvider) {
2760
+ throw new Error("reencryptPrivateKey: not supported when a privateKeyProvider is configured");
2761
+ }
2762
+ const oldPass = await resolvePrivateKeyPassphrase(oldPassphrase);
2763
+ const newPass = await resolvePrivateKeyPassphrase(newPassphrase);
2764
+ await this.withLock2(async () => {
2765
+ const privateKey = readPrivateKey2(this.privateKey, oldPass);
2766
+ await this.#rewritePrivateKeyFile(privateKey, newPass);
2767
+ });
2768
+ this.#cachedPrivateKey = void 0;
2769
+ this.#privateKeyPromise = void 0;
2770
+ }
2771
+ /**
2772
+ * Atomically replace the on-disk private key with `privateKey`, written
2773
+ * as PKCS#8 (encrypted with `passphrase` if given). Temp file next to the
2774
+ * target, `0600`, renamed into place; the temp file is unlinked on any
2775
+ * failure so no partial or cleartext copy can be left behind.
2776
+ * Caller must hold the lock.
2777
+ */
2778
+ async #rewritePrivateKeyFile(privateKey, passphrase) {
2779
+ const tmpFilename = `${this.privateKey}.${process.pid}-${Date.now()}.tmp`;
2780
+ try {
2781
+ await writePrivateKeyFile2(tmpFilename, privateKey, { passphrase });
2782
+ await fs10.promises.rename(tmpFilename, this.privateKey);
2783
+ } finally {
2784
+ await fs10.promises.rm(tmpFilename, { force: true });
2785
+ }
2786
+ }
2364
2787
  /** Path to the OpenSSL random seed file. */
2365
2788
  get randomFile() {
2366
2789
  return path6.join(this.rootDir, "./random.rnd");
@@ -2660,19 +3083,24 @@ var CertificateManager = class _CertificateManager extends EventEmitter {
2660
3083
  }
2661
3084
  this.state = 1 /* Initializing */;
2662
3085
  this.#initializingPromise = this.#initialize();
2663
- await this.#initializingPromise;
3086
+ try {
3087
+ await this.#initializingPromise;
3088
+ } catch (err) {
3089
+ this.#initializingPromise = void 0;
3090
+ this.state = 0 /* Uninitialized */;
3091
+ throw err;
3092
+ }
2664
3093
  this.#initializingPromise = void 0;
2665
3094
  this.state = 2 /* Initialized */;
2666
3095
  _CertificateManager.#activeInstances.add(this);
2667
3096
  _CertificateManager.#installProcessCleanup();
2668
3097
  }
2669
3098
  async #initialize() {
2670
- this.state = 1 /* Initializing */;
2671
3099
  const pkiDir = this.#location;
2672
3100
  mkdirRecursiveSync(pkiDir);
2673
3101
  mkdirRecursiveSync(path6.join(pkiDir, "own"));
2674
3102
  mkdirRecursiveSync(path6.join(pkiDir, "own/certs"));
2675
- mkdirRecursiveSync(path6.join(pkiDir, "own/private"));
3103
+ ensurePrivateDirectory(path6.join(pkiDir, "own/private"));
2676
3104
  mkdirRecursiveSync(path6.join(pkiDir, "rejected"));
2677
3105
  mkdirRecursiveSync(path6.join(pkiDir, "trusted"));
2678
3106
  mkdirRecursiveSync(path6.join(pkiDir, "trusted/certs"));
@@ -2680,7 +3108,10 @@ var CertificateManager = class _CertificateManager extends EventEmitter {
2680
3108
  mkdirRecursiveSync(path6.join(pkiDir, "issuers"));
2681
3109
  mkdirRecursiveSync(path6.join(pkiDir, "issuers/certs"));
2682
3110
  mkdirRecursiveSync(path6.join(pkiDir, "issuers/crl"));
2683
- if (!fs10.existsSync(this.configFile) || !fs10.existsSync(this.privateKey)) {
3111
+ const ownsDiskKey = !this.#privateKeyProvider;
3112
+ const needsKeyGeneration = ownsDiskKey && !fs10.existsSync(this.privateKey);
3113
+ const needsKeyEncryption = ownsDiskKey && !needsKeyGeneration && this.#privateKeyPassphrase !== void 0 && !isEncryptedPrivateKeyFile(this.privateKey);
3114
+ if (!fs10.existsSync(this.configFile) || needsKeyGeneration || needsKeyEncryption) {
2684
3115
  return await this.withLock2(async () => {
2685
3116
  if (this.state === 3 /* Disposing */ || this.state === 4 /* Disposed */) {
2686
3117
  return;
@@ -2688,15 +3119,29 @@ var CertificateManager = class _CertificateManager extends EventEmitter {
2688
3119
  if (!fs10.existsSync(this.configFile)) {
2689
3120
  fs10.writeFileSync(this.configFile, configurationFileSimpleTemplate2);
2690
3121
  }
2691
- if (!fs10.existsSync(this.privateKey)) {
3122
+ if (ownsDiskKey && !fs10.existsSync(this.privateKey)) {
2692
3123
  debugLog("generating private key ...");
2693
- await generatePrivateKeyFile2(this.privateKey, this.keySize);
2694
- await this.#readCertificates();
2695
- } else {
2696
- await this.#readCertificates();
3124
+ const passphrase = await resolvePrivateKeyPassphrase(this.#privateKeyPassphrase);
3125
+ await generatePrivateKeyFile2(this.privateKey, this.keySize, { passphrase });
3126
+ this.#cachedPrivateKey = readPrivateKey2(this.privateKey, passphrase);
3127
+ } else if (ownsDiskKey && this.#privateKeyPassphrase !== void 0 && !isEncryptedPrivateKeyFile(this.privateKey)) {
3128
+ warningLog("initialize: private key is plaintext but a passphrase is configured; encrypting it in place");
3129
+ const passphrase = await resolvePrivateKeyPassphrase(this.#privateKeyPassphrase);
3130
+ const plaintextKey = readPrivateKey2(this.privateKey);
3131
+ await this.#rewritePrivateKeyFile(plaintextKey, passphrase);
3132
+ this.#cachedPrivateKey = plaintextKey;
3133
+ }
3134
+ if (ownsDiskKey) {
3135
+ restrictPrivateFilePermissions(this.privateKey, 384);
2697
3136
  }
3137
+ await this.getPrivateKey();
3138
+ await this.#readCertificates();
2698
3139
  });
2699
3140
  } else {
3141
+ if (ownsDiskKey) {
3142
+ restrictPrivateFilePermissions(this.privateKey, 384);
3143
+ }
3144
+ await this.getPrivateKey();
2700
3145
  await this.#readCertificates();
2701
3146
  }
2702
3147
  }
@@ -2732,6 +3177,8 @@ var CertificateManager = class _CertificateManager extends EventEmitter {
2732
3177
  this.#watchers.splice(0);
2733
3178
  } finally {
2734
3179
  this.state = 4 /* Disposed */;
3180
+ this.#cachedPrivateKey = void 0;
3181
+ this.#privateKeyPromise = void 0;
2735
3182
  _CertificateManager.#activeInstances.delete(this);
2736
3183
  }
2737
3184
  }
@@ -2778,16 +3225,18 @@ var CertificateManager = class _CertificateManager extends EventEmitter {
2778
3225
  if (typeof params.applicationUri !== "string") {
2779
3226
  throw new Error("createSelfSignedCertificate: expecting applicationUri to be a string");
2780
3227
  }
2781
- if (!fs10.existsSync(this.privateKey)) {
3228
+ if (!this.#privateKeyProvider && !fs10.existsSync(this.privateKey)) {
2782
3229
  throw new Error(`Cannot find private key ${this.privateKey}`);
2783
3230
  }
2784
3231
  let certificateFilename = path6.join(this.rootDir, "own/certs/self_signed_certificate.pem");
2785
3232
  certificateFilename = params.outputFile || certificateFilename;
2786
- const _params = params;
2787
- _params.rootDir = this.rootDir;
2788
- _params.configFile = this.configFile;
2789
- _params.privateKey = this.privateKey;
2790
- _params.subject = params.subject || "CN=FIXME";
3233
+ const _params = {
3234
+ ...params,
3235
+ rootDir: this.rootDir,
3236
+ configFile: this.configFile,
3237
+ privateKey: await this.getPrivateKey(),
3238
+ subject: params.subject || "CN=FIXME"
3239
+ };
2791
3240
  await this.withLock2(async () => {
2792
3241
  await createSelfSignedCertificate(certificateFilename, _params);
2793
3242
  });
@@ -2806,13 +3255,15 @@ var CertificateManager = class _CertificateManager extends EventEmitter {
2806
3255
  if (!params) {
2807
3256
  throw new Error("params is required");
2808
3257
  }
2809
- const _params = params;
2810
- if (Object.prototype.hasOwnProperty.call(_params, "rootDir")) {
3258
+ if (Object.prototype.hasOwnProperty.call(params, "rootDir")) {
2811
3259
  throw new Error("rootDir should not be specified ");
2812
3260
  }
2813
- _params.rootDir = path6.resolve(this.rootDir);
2814
- _params.configFile = path6.resolve(this.configFile);
2815
- _params.privateKey = path6.resolve(this.privateKey);
3261
+ const _params = {
3262
+ ...params,
3263
+ rootDir: path6.resolve(this.rootDir),
3264
+ configFile: path6.resolve(this.configFile),
3265
+ privateKey: await this.getPrivateKey()
3266
+ };
2816
3267
  return await this.withLock2(async () => {
2817
3268
  const now = /* @__PURE__ */ new Date();
2818
3269
  const today = `${now.toISOString().slice(0, 10)}_${now.getTime()}`;
@@ -3662,6 +4113,7 @@ export {
3662
4113
  CertificateManager,
3663
4114
  CertificateManagerState,
3664
4115
  ChainCompletionStatus,
4116
+ PrivateKeyPassphraseRequiredError,
3665
4117
  Subject,
3666
4118
  VerificationStatus,
3667
4119
  adjustApplicationUri,
@@ -3680,6 +4132,7 @@ export {
3680
4132
  isIssuer,
3681
4133
  isRootIssuer,
3682
4134
  makeFingerprint,
3683
- quote
4135
+ quote,
4136
+ resolvePrivateKeyPassphrase
3684
4137
  };
3685
4138
  //# sourceMappingURL=index.mjs.map