node-opcua-pki 6.20.0 → 6.22.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.mjs CHANGED
@@ -1,111 +1,37 @@
1
- // packages/node-opcua-pki/lib/index.ts
2
- import { PrivateKeyPassphraseRequiredError } from "node-opcua-crypto";
1
+ import {
2
+ CertificateAuthorityCore,
3
+ NativeCaBackend,
4
+ Subject,
5
+ adjustApplicationUri,
6
+ adjustDate,
7
+ debugLog,
8
+ display,
9
+ displayError,
10
+ displaySubtitle,
11
+ displayTitle,
12
+ doDebug,
13
+ ensurePrivateDirectory,
14
+ g_config,
15
+ isEncryptedPrivateKeyFile,
16
+ isOpaqueSigner,
17
+ makePath,
18
+ mkdirRecursiveSync,
19
+ quote,
20
+ resolvePrivateKeyPassphrase,
21
+ restrictPrivateFilePermissions,
22
+ warningLog
23
+ } from "./chunk-EQ4DGI4M.mjs";
3
24
 
4
- // packages/node-opcua-pki/lib/ca/certificate_authority.ts
5
- import assert7 from "assert";
6
- import fs7 from "fs";
7
- import os3 from "os";
8
- import path5 from "path";
9
- import chalk5 from "chalk";
25
+ // packages/node-opcua-pki/lib/index.ts
10
26
  import {
11
- CertificatePurpose,
12
- certificateMatchesPrivateKey,
13
- convertPEMtoDER,
14
- exploreCertificate,
15
- exploreCertificateSigningRequest,
16
- generatePrivateKeyFile,
17
- readCertificatePEM,
18
- readCertificateSigningRequest,
19
- readPrivateKey,
20
- Subject as Subject2,
21
- toPem,
22
- writePrivateKeyFile
27
+ PrivateKeyPassphraseRequiredError,
28
+ PrivateKeyUnavailableError as PrivateKeyUnavailableError2
23
29
  } from "node-opcua-crypto";
24
30
 
25
- // packages/node-opcua-pki/lib/pki/toolbox_pfx.ts
26
- import assert3 from "assert";
31
+ // packages/node-opcua-pki/lib/ca/backends/openssl_ca_backend.ts
27
32
  import fs4 from "fs";
28
-
29
- // packages/node-opcua-pki/lib/toolbox/common2.ts
30
- import assert from "assert";
31
- import fs from "fs";
32
- import path from "path";
33
- import chalk from "chalk";
34
-
35
- // packages/node-opcua-pki/lib/toolbox/config.ts
36
- var g_config = {
37
- opensslVersion: "unset",
38
- silent: process.env.VERBOSE ? !process.env.VERBOSE : true,
39
- force: false
40
- };
41
-
42
- // packages/node-opcua-pki/lib/toolbox/debug.ts
43
- var doDebug = process.env.NODEOPCUAPKIDEBUG || false;
44
- var displayError = true;
45
- var displayDebug = !!process.env.NODEOPCUAPKIDEBUG || false;
46
- function debugLog(...args) {
47
- if (displayDebug) {
48
- console.log.apply(null, args);
49
- }
50
- }
51
- function warningLog(...args) {
52
- console.log.apply(null, args);
53
- }
54
-
55
- // packages/node-opcua-pki/lib/toolbox/common2.ts
56
- function certificateFileExist(certificateFile) {
57
- if (fs.existsSync(certificateFile) && !g_config.force) {
58
- warningLog(
59
- chalk.yellow(" certificate ") + chalk.cyan(certificateFile) + chalk.yellow(" already exists => do not overwrite")
60
- );
61
- return false;
62
- }
63
- return true;
64
- }
65
- function mkdirRecursiveSync(folder) {
66
- if (!fs.existsSync(folder)) {
67
- debugLog(chalk.white(" .. constructing "), folder);
68
- fs.mkdirSync(folder, { recursive: true });
69
- }
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
- }
90
- function makePath(folderName, filename) {
91
- let s;
92
- if (filename) {
93
- s = path.join(path.normalize(folderName), filename);
94
- } else {
95
- assert(folderName);
96
- s = folderName;
97
- }
98
- s = s.replace(/\\/g, "/");
99
- return s;
100
- }
101
-
102
- // packages/node-opcua-pki/lib/toolbox/with_openssl/execute_openssl.ts
103
- import assert2 from "assert";
104
- import child_process2 from "child_process";
105
- import fs3 from "fs";
106
- import os2 from "os";
107
- import byline2 from "byline";
108
- import chalk3 from "chalk";
33
+ import path3 from "path";
34
+ import { Subject as Subject2 } from "node-opcua-crypto";
109
35
 
110
36
  // packages/node-opcua-pki/lib/toolbox/with_openssl/_env.ts
111
37
  var SAFE_ENV_PASSTHROUGH = /* @__PURE__ */ new Set([
@@ -179,31 +105,30 @@ function getEnvironmentVarNames() {
179
105
  return { key: varName, pattern: `\\$ENV\\:\\:${varName}` };
180
106
  });
181
107
  }
182
- function processAltNames(params) {
183
- params.dns = params.dns || [];
184
- params.ip = params.ip || [];
185
- let subjectAltName = [];
186
- subjectAltName.push(`URI:${params.applicationUri}`);
187
- subjectAltName = [].concat(
188
- subjectAltName,
189
- params.dns.map((d) => `DNS:${d}`)
190
- );
191
- subjectAltName = [].concat(
192
- subjectAltName,
193
- params.ip.map((d) => `IP:${d}`)
194
- );
195
- const subjectAltNameString = subjectAltName.join(", ");
196
- setEnv("ALTNAME", subjectAltNameString);
108
+ function buildSubjectAltNameString(params) {
109
+ return [
110
+ `URI:${params.applicationUri}`,
111
+ ...(params.dns ?? []).map((d) => `DNS:${d}`),
112
+ ...(params.ip ?? []).map((d) => `IP:${d}`)
113
+ ].join(", ");
197
114
  }
198
115
 
116
+ // packages/node-opcua-pki/lib/toolbox/with_openssl/execute_openssl.ts
117
+ import assert from "assert";
118
+ import child_process2 from "child_process";
119
+ import fs2 from "fs";
120
+ import os2 from "os";
121
+ import byline2 from "byline";
122
+ import chalk2 from "chalk";
123
+
199
124
  // packages/node-opcua-pki/lib/toolbox/with_openssl/install_prerequisite.ts
200
125
  import child_process from "child_process";
201
- import fs2 from "fs";
126
+ import fs from "fs";
202
127
  import os from "os";
203
- import path2 from "path";
128
+ import path from "path";
204
129
  import { pipeline } from "stream/promises";
205
130
  import byline from "byline";
206
- import chalk2 from "chalk";
131
+ import chalk from "chalk";
207
132
  import yauzl from "yauzl";
208
133
  var doDebug2 = process.env.NODEOPCUAPKIDEBUG || false;
209
134
  async function execute(file, args, cwd) {
@@ -222,7 +147,7 @@ async function execute(file, args, cwd) {
222
147
  output += `${line}
223
148
  `;
224
149
  if (doDebug2) {
225
- process.stdout.write(` stdout ${chalk2.yellow(line)}
150
+ process.stdout.write(` stdout ${chalk.yellow(line)}
226
151
  `);
227
152
  }
228
153
  });
@@ -242,8 +167,8 @@ async function getopensslExecPath() {
242
167
  const exitCode = result1?.exitCode;
243
168
  const output = result1?.output;
244
169
  if (exitCode !== 0) {
245
- warningLog(chalk2.yellow(" it seems that ") + chalk2.cyan("openssl") + chalk2.yellow(" is not installed on your computer "));
246
- warningLog(chalk2.yellow("Please install it before running this programs"));
170
+ warningLog(chalk.yellow(" it seems that ") + chalk.cyan("openssl") + chalk.yellow(" is not installed on your computer "));
171
+ warningLog(chalk.yellow("Please install it before running this programs"));
247
172
  throw new Error("Cannot find openssl");
248
173
  }
249
174
  const opensslExecPath = output.replace(/\n\r/g, "").trim();
@@ -252,7 +177,7 @@ async function getopensslExecPath() {
252
177
  async function check_system_openssl_version() {
253
178
  const opensslExecPath = await getopensslExecPath();
254
179
  if (doDebug2) {
255
- warningLog(` OpenSSL found in : ${chalk2.yellow(opensslExecPath)}`);
180
+ warningLog(` OpenSSL found in : ${chalk.yellow(opensslExecPath)}`);
256
181
  }
257
182
  const result = await execute(opensslExecPath, ["version"]);
258
183
  const exitCode = result?.exitCode;
@@ -260,35 +185,35 @@ async function check_system_openssl_version() {
260
185
  const version = output.trim();
261
186
  const versionOK = exitCode === 0 && is_expected_openssl_version(version);
262
187
  if (!versionOK) {
263
- let message = chalk2.whiteBright("Warning !!!!!!!!!!!! ") + "\nyour version of openssl is " + version + ". It doesn't match the expected version";
188
+ let message = chalk.whiteBright("Warning !!!!!!!!!!!! ") + "\nyour version of openssl is " + version + ". It doesn't match the expected version";
264
189
  if (process.platform === "darwin") {
265
- message += chalk2.cyan("\nplease refer to :") + chalk2.yellow(" https://github.com/node-opcua/node-opcua/wiki/installing-node-opcua-or-node-red-on-MacOS");
190
+ message += chalk.cyan("\nplease refer to :") + chalk.yellow(" https://github.com/node-opcua/node-opcua/wiki/installing-node-opcua-or-node-red-on-MacOS");
266
191
  }
267
192
  console.log(message);
268
193
  }
269
194
  return output;
270
195
  }
271
196
  async function install_and_check_win32_openssl_version() {
272
- const downloadFolder = path2.join(os.tmpdir(), ".");
197
+ const downloadFolder = path.join(os.tmpdir(), ".");
273
198
  function get_openssl_folder_win32() {
274
199
  if (process.env.LOCALAPPDATA) {
275
- const userProgramFolder = path2.join(process.env.LOCALAPPDATA, "Programs");
276
- if (fs2.existsSync(userProgramFolder)) {
277
- return path2.join(userProgramFolder, "openssl");
200
+ const userProgramFolder = path.join(process.env.LOCALAPPDATA, "Programs");
201
+ if (fs.existsSync(userProgramFolder)) {
202
+ return path.join(userProgramFolder, "openssl");
278
203
  }
279
204
  }
280
- return path2.join(process.cwd(), "openssl");
205
+ return path.join(process.cwd(), "openssl");
281
206
  }
282
207
  function get_openssl_exec_path_win32() {
283
208
  const opensslFolder2 = get_openssl_folder_win32();
284
- return path2.join(opensslFolder2, "openssl.exe");
209
+ return path.join(opensslFolder2, "openssl.exe");
285
210
  }
286
211
  async function check_openssl_win32() {
287
212
  const opensslExecPath2 = get_openssl_exec_path_win32();
288
- const exists = fs2.existsSync(opensslExecPath2);
213
+ const exists = fs.existsSync(opensslExecPath2);
289
214
  if (!exists) {
290
215
  warningLog("checking presence of ", opensslExecPath2);
291
- warningLog(chalk2.red(" cannot find file ") + opensslExecPath2);
216
+ warningLog(chalk.red(" cannot find file ") + opensslExecPath2);
292
217
  return {
293
218
  opensslOk: false,
294
219
  version: `cannot find file ${opensslExecPath2}`
@@ -313,18 +238,18 @@ async function install_and_check_win32_openssl_version() {
313
238
  return void 0;
314
239
  }
315
240
  const opensslPath2 = result.output.split(/\r?\n/)[0].trim();
316
- if (!opensslPath2 || !fs2.existsSync(opensslPath2)) {
241
+ if (!opensslPath2 || !fs.existsSync(opensslPath2)) {
317
242
  return void 0;
318
243
  }
319
244
  const versionResult = await execute(opensslPath2, ["version"]);
320
245
  const version = versionResult.output.trim();
321
246
  if (versionResult.exitCode === 0 && is_expected_openssl_version(version)) {
322
247
  warningLog(
323
- chalk2.green("Using system OpenSSL: ") + chalk2.cyan(version) + chalk2.green(" at ") + chalk2.cyan(opensslPath2)
248
+ chalk.green("Using system OpenSSL: ") + chalk.cyan(version) + chalk.green(" at ") + chalk.cyan(opensslPath2)
324
249
  );
325
250
  return opensslPath2;
326
251
  }
327
- warningLog(chalk2.yellow("System OpenSSL found but version not accepted: ") + version);
252
+ warningLog(chalk.yellow("System OpenSSL found but version not accepted: ") + version);
328
253
  return void 0;
329
254
  } catch (_err) {
330
255
  return void 0;
@@ -344,9 +269,9 @@ async function install_and_check_win32_openssl_version() {
344
269
  }
345
270
  async function download_openssl() {
346
271
  const url = win32or64() === 64 ? "https://github.com/node-opcua/node-opcua-pki/releases/download/2.14.2/openssl-1.0.2u-x64_86-win64.zip" : "https://github.com/node-opcua/node-opcua-pki/releases/download/2.14.2/openssl-1.0.2u-i386-win32.zip";
347
- const outputFilename = path2.join(downloadFolder, path2.basename(url));
348
- warningLog(`downloading ${chalk2.yellow(url)} to ${outputFilename}`);
349
- if (fs2.existsSync(outputFilename)) {
272
+ const outputFilename = path.join(downloadFolder, path.basename(url));
273
+ warningLog(`downloading ${chalk.yellow(url)} to ${outputFilename}`);
274
+ if (fs.existsSync(outputFilename)) {
350
275
  return { downloadedFile: outputFilename };
351
276
  }
352
277
  const response = await fetch(url, { redirect: "follow" });
@@ -356,7 +281,7 @@ async function install_and_check_win32_openssl_version() {
356
281
  const contentLength = parseInt(response.headers.get("content-length") || "0", 10);
357
282
  let downloaded = 0;
358
283
  let lastPercent = -1;
359
- const fileStream = fs2.createWriteStream(outputFilename);
284
+ const fileStream = fs.createWriteStream(outputFilename);
360
285
  const body = response.body;
361
286
  body.on("data", (chunk) => {
362
287
  downloaded += chunk.length;
@@ -369,12 +294,12 @@ async function install_and_check_win32_openssl_version() {
369
294
  }
370
295
  });
371
296
  await pipeline(body, fileStream);
372
- const stat = fs2.statSync(outputFilename);
297
+ const stat = fs.statSync(outputFilename);
373
298
  if (stat.size === 0) {
374
- fs2.unlinkSync(outputFilename);
299
+ fs.unlinkSync(outputFilename);
375
300
  throw new Error(`Downloaded file is empty: ${outputFilename}`);
376
301
  }
377
- warningLog(chalk2.green("Download complete: ") + `${stat.size} bytes`);
302
+ warningLog(chalk.green("Download complete: ") + `${stat.size} bytes`);
378
303
  return { downloadedFile: outputFilename };
379
304
  }
380
305
  async function unzip_openssl(zipFilename) {
@@ -411,11 +336,11 @@ async function install_and_check_win32_openssl_version() {
411
336
  if (err) {
412
337
  return reject(err);
413
338
  }
414
- const file = path2.join(opensslFolder2, entry.fileName);
339
+ const file = path.join(opensslFolder2, entry.fileName);
415
340
  if (doDebug2) {
416
341
  warningLog(" unzipping :", file);
417
342
  }
418
- const writeStream = fs2.createWriteStream(file, "binary");
343
+ const writeStream = fs.createWriteStream(file, "binary");
419
344
  readStream?.pipe(writeStream);
420
345
  writeStream.on("close", () => {
421
346
  zipFile.readEntry();
@@ -430,29 +355,29 @@ async function install_and_check_win32_openssl_version() {
430
355
  }
431
356
  const opensslFolder = get_openssl_folder_win32();
432
357
  const opensslExecPath = get_openssl_exec_path_win32();
433
- if (!fs2.existsSync(opensslFolder)) {
358
+ if (!fs.existsSync(opensslFolder)) {
434
359
  if (doDebug2) {
435
360
  warningLog("creating openssl_folder", opensslFolder);
436
361
  }
437
- fs2.mkdirSync(opensslFolder);
362
+ fs.mkdirSync(opensslFolder);
438
363
  }
439
364
  const { opensslOk, version: _version } = await check_openssl_win32();
440
365
  if (!opensslOk) {
441
- warningLog(chalk2.yellow("openssl seems to be missing and need to be installed"));
366
+ warningLog(chalk.yellow("openssl seems to be missing and need to be installed"));
442
367
  const { downloadedFile } = await download_openssl();
443
368
  if (doDebug2) {
444
- warningLog("deflating ", chalk2.yellow(downloadedFile));
369
+ warningLog("deflating ", chalk.yellow(downloadedFile));
445
370
  }
446
371
  await unzip_openssl(downloadedFile);
447
- const opensslExists = !!fs2.existsSync(opensslExecPath);
372
+ const opensslExists = !!fs.existsSync(opensslExecPath);
448
373
  if (doDebug2) {
449
- warningLog("verifying ", opensslExists, opensslExists ? chalk2.green("OK ") : chalk2.red(" Error"), opensslExecPath);
374
+ warningLog("verifying ", opensslExists, opensslExists ? chalk.green("OK ") : chalk.red(" Error"), opensslExecPath);
450
375
  }
451
376
  const _opensslExecPath2 = await check_openssl_win32();
452
377
  return opensslExecPath;
453
378
  } else {
454
379
  if (doDebug2) {
455
- warningLog(chalk2.green("openssl is already installed and have the expected version."));
380
+ warningLog(chalk.green("openssl is already installed and have the expected version."));
456
381
  }
457
382
  return opensslExecPath;
458
383
  }
@@ -467,7 +392,7 @@ async function install_prerequisite() {
467
392
  async function get_openssl_exec_path() {
468
393
  if (process.platform === "win32") {
469
394
  const opensslExecPath = await install_prerequisite();
470
- if (!fs2.existsSync(opensslExecPath)) {
395
+ if (!fs.existsSync(opensslExecPath)) {
471
396
  throw new Error(`internal error cannot find ${opensslExecPath}`);
472
397
  }
473
398
  return opensslExecPath;
@@ -480,13 +405,9 @@ async function get_openssl_exec_path() {
480
405
  var opensslPath;
481
406
  var n = makePath;
482
407
  var PASSIN_ENV_VAR = "NODE_OPCUA_PKI_OPENSSL_PASSIN";
483
- var PASSOUT_ENV_VAR = "NODE_OPCUA_PKI_OPENSSL_PASSOUT";
484
408
  function passinArg(passphrase = "") {
485
409
  return { args: ["-passin", `env:${PASSIN_ENV_VAR}`], env: { [PASSIN_ENV_VAR]: passphrase } };
486
410
  }
487
- function passoutArg(passphrase = "") {
488
- return { args: ["-passout", `env:${PASSOUT_ENV_VAR}`], env: { [PASSOUT_ENV_VAR]: passphrase } };
489
- }
490
411
  function renderForDisplay(file, args) {
491
412
  return [file, ...args].map((a) => a === "" || /[\s"'`$\\]/.test(a) ? JSON.stringify(a) : a).join(" ");
492
413
  }
@@ -494,7 +415,7 @@ async function execute2(file, args, options) {
494
415
  const from = new Error();
495
416
  options.cwd = options.cwd || process.cwd();
496
417
  if (!g_config.silent) {
497
- warningLog(chalk3.cyan(" CWD "), options.cwd);
418
+ warningLog(chalk2.cyan(" CWD "), options.cwd);
498
419
  }
499
420
  const outputs = [];
500
421
  const errorOutputs = [];
@@ -507,12 +428,12 @@ async function execute2(file, args, options) {
507
428
  stdio: ["ignore", "pipe", "pipe"]
508
429
  });
509
430
  const fail = (message) => {
510
- if (!options.hideErrorMessage) {
431
+ if (!options.hideErrorMessage && !g_config.silent) {
511
432
  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}`));
433
+ console.error(chalk2.bgWhiteBright.redBright(`${fence} OPENSSL ERROR ${fence}`));
434
+ console.error(chalk2.bgWhiteBright.redBright(`CWD = ${options.cwd}`));
435
+ console.error(chalk2.bgWhiteBright.redBright(message));
436
+ console.error(chalk2.bgWhiteBright.redBright(`${fence} OPENSSL ERROR ${fence}`));
516
437
  console.error(from.stack);
517
438
  }
518
439
  reject(new Error(message));
@@ -538,7 +459,7 @@ ${errorOutputs.join("")}(${why})`);
538
459
  stream2.on("data", (line) => {
539
460
  line = line.toString();
540
461
  if (doDebug) {
541
- process.stdout.write(`${chalk3.white(" stdout ") + chalk3.whiteBright(line)}
462
+ process.stdout.write(`${chalk2.white(" stdout ") + chalk2.whiteBright(line)}
542
463
  `);
543
464
  }
544
465
  });
@@ -551,7 +472,7 @@ ${errorOutputs.join("")}(${why})`);
551
472
  errorOutputs.push(`${line}
552
473
  `);
553
474
  if (!g_config.silent && displayError) {
554
- process.stdout.write(`${chalk3.white(" stderr ") + chalk3.red(line)}
475
+ process.stdout.write(`${chalk2.white(" stderr ") + chalk2.red(line)}
555
476
  `);
556
477
  }
557
478
  });
@@ -586,154 +507,26 @@ function getTempFolder() {
586
507
  async function execute_openssl(args, options) {
587
508
  debugLog("execute_openssl", args, redactEnvForLog(options));
588
509
  const empty_config_file = n(getTempFolder(), "empty_config.cnf");
589
- if (!fs3.existsSync(empty_config_file)) {
590
- await fs3.promises.writeFile(empty_config_file, "# empty config file");
510
+ if (!fs2.existsSync(empty_config_file)) {
511
+ await fs2.promises.writeFile(empty_config_file, "# empty config file");
591
512
  }
592
513
  options = options || {};
593
514
  options.openssl_conf = options.openssl_conf || empty_config_file;
594
- assert2(options.openssl_conf);
515
+ assert(options.openssl_conf);
595
516
  setEnv("OPENSSL_CONF", options.openssl_conf);
596
517
  if (!g_config.silent) {
597
- warningLog(chalk3.cyan(" OPENSSL_CONF"), process.env.OPENSSL_CONF);
598
- warningLog(chalk3.cyan(" RANDFILE "), process.env.RANDFILE);
599
- warningLog(chalk3.cyan(" CMD "), chalk3.cyanBright(renderForDisplay("openssl", args)));
518
+ warningLog(chalk2.cyan(" OPENSSL_CONF"), process.env.OPENSSL_CONF);
519
+ warningLog(chalk2.cyan(" RANDFILE "), process.env.RANDFILE);
520
+ warningLog(chalk2.cyan(" CMD "), chalk2.cyanBright(renderForDisplay("openssl", args)));
600
521
  }
601
522
  await ensure_openssl_installed();
602
523
  return await execute2(opensslPath, args, options);
603
524
  }
604
525
 
605
- // packages/node-opcua-pki/lib/pki/toolbox_pfx.ts
606
- var n2 = makePath;
607
- async function createPFX(options) {
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)];
612
- if (caCertificateFiles) {
613
- for (const caFile of caCertificateFiles) {
614
- assert3(fs4.existsSync(caFile), `CA certificate file does not exist: ${caFile}`);
615
- args.push("-certfile", n2(caFile));
616
- }
617
- }
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 } });
622
- }
623
- async function extractCertificateFromPFX(options) {
624
- const { pfxFile, passphrase = "" } = options;
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
- });
630
- }
631
- async function extractPrivateKeyFromPFX(options) {
632
- const { pfxFile, passphrase = "" } = options;
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 });
636
- }
637
- async function extractCACertificatesFromPFX(options) {
638
- const { pfxFile, passphrase = "" } = options;
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
- });
644
- }
645
- async function extractAllFromPFX(options) {
646
- const [certificate, privateKey, caCertificates] = await Promise.all([
647
- extractCertificateFromPFX(options),
648
- extractPrivateKeyFromPFX(options),
649
- extractCACertificatesFromPFX(options)
650
- ]);
651
- return { certificate, privateKey, caCertificates };
652
- }
653
- async function convertPFXtoPEM(pfxFile, pemFile, passphrase = "") {
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 });
657
- }
658
- async function dumpPFX(pfxFile, passphrase = "") {
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
- }
698
- }
699
-
700
- // packages/node-opcua-pki/lib/toolbox/display.ts
701
- import chalk4 from "chalk";
702
- function displayTitle(str) {
703
- if (!g_config.silent) {
704
- warningLog("");
705
- warningLog(chalk4.yellowBright(str));
706
- warningLog(chalk4.yellow(new Array(str.length + 1).join("=")), "\n");
707
- }
708
- }
709
- function displaySubtitle(str) {
710
- if (!g_config.silent) {
711
- warningLog("");
712
- warningLog(` ${chalk4.yellowBright(str)}`);
713
- warningLog(` ${chalk4.white(new Array(str.length + 1).join("-"))}`, "\n");
714
- }
715
- }
716
- function display(str) {
717
- if (!g_config.silent) {
718
- warningLog(` ${str}`);
719
- }
720
- }
721
-
722
- // packages/node-opcua-pki/lib/toolbox/with_openssl/index.ts
723
- import exp from "constants";
724
-
725
- // packages/node-opcua-pki/lib/toolbox/with_openssl/create_certificate_signing_request.ts
726
- import assert6 from "assert";
727
- import fs6 from "fs";
728
- import path4 from "path";
729
-
730
- // packages/node-opcua-pki/lib/misc/subject.ts
731
- import { Subject } from "node-opcua-crypto";
732
-
733
526
  // packages/node-opcua-pki/lib/toolbox/with_openssl/toolbox.ts
734
- import assert5 from "assert";
735
- import fs5 from "fs";
736
- import path3 from "path";
527
+ import assert2 from "assert";
528
+ import fs3 from "fs";
529
+ import path2 from "path";
737
530
  function openssl_require2DigitYearInDate() {
738
531
  if (!g_config.opensslVersion) {
739
532
  throw new Error(
@@ -744,29 +537,40 @@ function openssl_require2DigitYearInDate() {
744
537
  }
745
538
  g_config.opensslVersion = "";
746
539
  var _counter = 0;
747
- function stripConditionalBlocks(template) {
540
+ function stripConditionalBlocks(template, envOverrides) {
748
541
  return template.replace(/\{\{#([A-Z_][A-Z0-9_]*)\}\}([\s\S]*?)\{\{\/\1\}\}\r?\n?/g, (_match, key, content) => {
749
- const keep = hasEnv(key) && getEnv(key) !== "";
542
+ const keep = envOverrides && Object.prototype.hasOwnProperty.call(envOverrides, key) ? envOverrides[key] !== "" : hasEnv(key) && getEnv(key) !== "";
750
543
  return keep ? content : "";
751
544
  });
752
545
  }
753
- function generateStaticConfig(configPath, options) {
546
+ function generateStaticConfig(configPath, options, envOverrides) {
754
547
  const prePath = options?.cwd || "";
755
- const originalFilename = !path3.isAbsolute(configPath) ? path3.join(prePath, configPath) : configPath;
756
- let staticConfig = fs5.readFileSync(originalFilename, { encoding: "utf8" });
757
- staticConfig = stripConditionalBlocks(staticConfig);
548
+ const originalFilename = !path2.isAbsolute(configPath) ? path2.join(prePath, configPath) : configPath;
549
+ let staticConfig = fs3.readFileSync(originalFilename, { encoding: "utf8" });
550
+ staticConfig = stripConditionalBlocks(staticConfig, envOverrides);
758
551
  for (const envVar of getEnvironmentVarNames()) {
759
- staticConfig = staticConfig.replace(new RegExp(envVar.pattern, "gi"), getEnv(envVar.key));
552
+ if (envOverrides && Object.prototype.hasOwnProperty.call(envOverrides, envVar.key)) {
553
+ continue;
554
+ }
555
+ staticConfig = staticConfig.replace(new RegExp(envVar.pattern, "gi"), () => getEnv(envVar.key));
556
+ }
557
+ if (envOverrides) {
558
+ for (const [key, value] of Object.entries(envOverrides)) {
559
+ staticConfig = staticConfig.replace(new RegExp(`\\$ENV\\:\\:${key}`, "gi"), () => value);
560
+ }
760
561
  }
761
562
  const staticConfigPath = `${configPath}.${process.pid}-${_counter++}.tmp`;
762
- const temporaryConfigPath = !path3.isAbsolute(configPath) ? path3.join(prePath, staticConfigPath) : staticConfigPath;
763
- fs5.writeFileSync(temporaryConfigPath, staticConfig);
563
+ const temporaryConfigPath = !path2.isAbsolute(configPath) ? path2.join(prePath, staticConfigPath) : staticConfigPath;
564
+ fs3.writeFileSync(temporaryConfigPath, staticConfig);
764
565
  if (options?.cwd) {
765
- return path3.relative(options.cwd, temporaryConfigPath);
566
+ return path2.relative(options.cwd, temporaryConfigPath);
766
567
  } else {
767
568
  return temporaryConfigPath;
768
569
  }
769
570
  }
571
+ async function cleanupStaticConfig(configFile, options) {
572
+ await fs3.promises.rm(path2.resolve(options?.cwd ?? "", configFile), { force: true });
573
+ }
770
574
  function x509Date(date) {
771
575
  date = date || /* @__PURE__ */ new Date();
772
576
  const Y = date.getUTCFullYear();
@@ -785,1536 +589,433 @@ function x509Date(date) {
785
589
  }
786
590
  }
787
591
 
788
- // packages/node-opcua-pki/lib/toolbox/with_openssl/create_certificate_signing_request.ts
789
- var n3 = makePath;
790
- async function createCertificateSigningRequestWithOpenSSL(certificateSigningRequestFilename, params) {
791
- assert6(params);
792
- assert6(params.rootDir);
793
- assert6(params.configFile);
794
- assert6(params.privateKey);
795
- assert6(typeof params.privateKey === "string");
796
- assert6(fs6.existsSync(params.configFile), `config file must exist ${params.configFile}`);
797
- assert6(fs6.existsSync(params.privateKey), `Private key must exist${params.privateKey}`);
798
- assert6(fs6.existsSync(params.rootDir), "RootDir key must exist");
799
- assert6(typeof certificateSigningRequestFilename === "string");
800
- processAltNames(params);
801
- const configFile = generateStaticConfig(params.configFile, { cwd: params.rootDir });
802
- const options = { cwd: params.rootDir, openssl_conf: path4.relative(params.rootDir, configFile) };
803
- const subject = params.subject ? new Subject(params.subject).toString() : void 0;
804
- displaySubtitle("- Creating a Certificate Signing Request with openssl");
805
- await execute_openssl(
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
- ],
820
- options
821
- );
822
- }
823
-
824
- // packages/node-opcua-pki/lib/pki/templates/simple_config_template.cnf.ts
825
- var config = '##################################################################################################\n## SIMPLE OPENSSL CONFIG FILE FOR SELF-SIGNED CERTIFICATE GENERATION\n################################################################################################################\n\ndistinguished_name = req_distinguished_name\ndefault_md = sha1\n\ndefault_md = sha256 # The default digest algorithm\n\n[ v3_ca ]\nsubjectKeyIdentifier = hash\nauthorityKeyIdentifier = keyid:always,issuer:always\n\n# authorityKeyIdentifier = keyid\nbasicConstraints = CA:TRUE\nkeyUsage = critical, cRLSign, keyCertSign\nnsComment = "Self-signed Certificate for CA generated by Node-OPCUA Certificate utility"\n#nsCertType = sslCA, emailCA\n#subjectAltName = email:copy\n#issuerAltName = issuer:copy\n#obj = DER:02:03\n# crlDistributionPoints = @crl_info\n# [ crl_info ]\n# URI.0 = http://localhost:8900/crl.pem\nsubjectAltName = $ENV::ALTNAME\n\n[ req ]\ndays = 390\nreq_extensions = v3_req\nx509_extensions = v3_ca\n\n[v3_req]\nbasicConstraints = CA:false\nkeyUsage = critical, nonRepudiation, digitalSignature, keyEncipherment, dataEncipherment\nsubjectAltName = $ENV::ALTNAME\n\n[ v3_ca_signed]\nsubjectKeyIdentifier = hash\nauthorityKeyIdentifier = keyid,issuer\nbasicConstraints = critical, CA:FALSE\nkeyUsage = nonRepudiation, digitalSignature, keyEncipherment, dataEncipherment\nextendedKeyUsage = clientAuth,serverAuth \nnsComment = "certificate generated by Node-OPCUA Certificate utility and signed by a CA"\nsubjectAltName = $ENV::ALTNAME\n[ v3_selfsigned]\nsubjectKeyIdentifier = hash\nauthorityKeyIdentifier = keyid,issuer\nbasicConstraints = critical, CA:FALSE\nkeyUsage = nonRepudiation, digitalSignature, keyEncipherment, dataEncipherment\nextendedKeyUsage = clientAuth,serverAuth \nnsComment = "Self-signed certificate generated by Node-OPCUA Certificate utility"\nsubjectAltName = $ENV::ALTNAME\n[ req_distinguished_name ]\ncountryName = Country Name (2 letter code)\ncountryName_default = FR\ncountryName_min = 2\ncountryName_max = 2\n# stateOrProvinceName = State or Province Name (full name)\n# stateOrProvinceName_default = Ile de France\n# localityName = Locality Name (city, district)\n# localityName_default = Paris\norganizationName = Organization Name (company)\norganizationName_default = NodeOPCUA\n# organizationalUnitName = Organizational Unit Name (department, division)\n# organizationalUnitName_default = R&D\ncommonName = Common Name (hostname, FQDN, IP, or your name)\ncommonName_max = 256\ncommonName_default = NodeOPCUA\n# emailAddress = Email Address\n# emailAddress_max = 40\n# emailAddress_default = node-opcua (at) node-opcua (dot) com\nsubjectAltName = $ENV::ALTNAME';
826
- var simple_config_template_cnf_default = config;
827
-
828
- // packages/node-opcua-pki/lib/ca/templates/ca_config_template.cnf.ts
829
- var config2 = `#.........DO NOT MODIFY BY HAND .........................
830
- [ ca ]
831
- default_ca = CA_default
832
- [ CA_default ]
833
- dir = "%%ROOT_FOLDER%%" # the main CA folder (quoted: see renderCaConfig)
834
- certs = $dir/certs # where to store certificates
835
- new_certs_dir = $dir/certs #
836
- database = $dir/index.txt # the certificate database
837
- serial = $dir/serial # the serial number counter
838
- certificate = $dir/public/cacert.pem # The root CA certificate
839
- private_key = $dir/private/cakey.pem # the CA private key
840
- x509_extensions = usr_cert #
841
- default_days = 3650 # default validity : 10 years
842
-
843
- # default_md = sha1
844
-
845
- default_md = sha256 # The default digest algorithm
846
-
847
- preserve = no
848
- policy = policy_match
849
- # randfile = $dir/random.rnd
850
- # default_startdate = YYMMDDHHMMSSZ
851
- # default_enddate = YYMMDDHHMMSSZ
852
- crl_dir = $dir/crl
853
- crl_extensions = crl_ext
854
- crl = $dir/revocation_list.crl # the Revocation list
855
- crlnumber = $dir/crlnumber # CRL number file
856
- default_crl_days = 30
857
- default_crl_hours = 24
858
- #msie_hack
859
-
860
- [ policy_match ]
861
- countryName = optional
862
- stateOrProvinceName = optional
863
- localityName = optional
864
- organizationName = optional
865
- organizationalUnitName = optional
866
- commonName = optional
867
- emailAddress = optional
868
-
869
- [ req ]
870
- default_bits = 4096 # Size of keys
871
- default_keyfile = key.pem # name of generated keys
872
- distinguished_name = req_distinguished_name
873
- attributes = req_attributes
874
- x509_extensions = v3_ca
875
- #input_password
876
- #output_password
877
- string_mask = nombstr # permitted characters
878
- req_extensions = v3_req
879
-
880
- [ req_distinguished_name ]
881
-
882
- #0 countryName = Country Name (2 letter code)
883
- # countryName_default = FR
884
- # countryName_min = 2
885
- # countryName_max = 2
886
- # stateOrProvinceName = State or Province Name (full name)
887
- # stateOrProvinceName_default = Ile de France
888
- # localityName = Locality Name (city, district)
889
- # localityName_default = Paris
890
- organizationName = Organization Name (company)
891
- organizationName_default = NodeOPCUA
892
- # organizationalUnitName = Organizational Unit Name (department, division)
893
- # organizationalUnitName_default = R&D
894
- commonName = Common Name (hostname, FQDN, IP, or your name)
895
- commonName_max = 256
896
- commonName_default = NodeOPCUA
897
- # emailAddress = Email Address
898
- # emailAddress_max = 40
899
- # emailAddress_default = node-opcua (at) node-opcua (dot) com
900
-
901
- [ req_attributes ]
902
- #challengePassword = A challenge password
903
- #challengePassword_min = 4
904
- #challengePassword_max = 20
905
- #unstructuredName = An optional company name
906
- [ usr_cert ]
907
- basicConstraints = critical, CA:FALSE
908
- subjectKeyIdentifier = hash
909
- authorityKeyIdentifier = keyid,issuer:always
910
- #authorityKeyIdentifier = keyid
911
- subjectAltName = $ENV::ALTNAME
912
- # issuerAltName = issuer:copy
913
- nsComment = ''OpenSSL Generated Certificate''
914
- #nsCertType = client, email, objsign for ''everything including object signing''
915
- #nsCaRevocationUrl = http://www.domain.dom/ca-crl.pem
916
- #nsBaseUrl =
917
- #nsRenewalUrl =
918
- #nsCaPolicyUrl =
919
- #nsSslServerName =
920
- keyUsage = critical, digitalSignature, nonRepudiation, keyEncipherment, dataEncipherment, keyAgreement
921
- extendedKeyUsage = critical,serverAuth ,clientAuth
922
- {{#CDP_URL}}crlDistributionPoints = URI:$ENV::CDP_URL
923
- {{/CDP_URL}}{{#AIA_VALUE}}authorityInfoAccess = $ENV::AIA_VALUE
924
- {{/AIA_VALUE}}
925
- [ v3_req ]
926
- basicConstraints = critical, CA:FALSE
927
- keyUsage = nonRepudiation, digitalSignature, keyEncipherment, dataEncipherment, keyAgreement
928
- extendedKeyUsage = critical,serverAuth ,clientAuth
929
- subjectAltName = $ENV::ALTNAME
930
- nsComment = "CA Generated by Node-OPCUA Certificate utility using openssl"
931
- [ v3_ca_req ]
932
- subjectKeyIdentifier = hash
933
- basicConstraints = CA:TRUE
934
- keyUsage = critical, cRLSign, keyCertSign
935
- subjectAltName = $ENV::ALTNAME
936
- nsComment = "CA CSR generated by Node-OPCUA Certificate utility using openssl"
937
- [ v3_ca ]
938
- subjectKeyIdentifier = hash
939
- authorityKeyIdentifier = keyid:always,issuer:always
940
- basicConstraints = CA:TRUE
941
- keyUsage = critical, cRLSign, keyCertSign
942
- subjectAltName = $ENV::ALTNAME
943
- nsComment = "CA Certificate generated by Node-OPCUA Certificate utility using openssl"
944
- #nsCertType = sslCA, emailCA
945
- #issuerAltName = issuer:copy
946
- #obj = DER:02:03
947
- {{#CDP_URL}}crlDistributionPoints = URI:$ENV::CDP_URL
948
- {{/CDP_URL}}{{#AIA_VALUE}}authorityInfoAccess = $ENV::AIA_VALUE
949
- {{/AIA_VALUE}}[ v3_selfsigned]
950
- basicConstraints = critical, CA:FALSE
951
- keyUsage = nonRepudiation, digitalSignature, keyEncipherment, dataEncipherment, keyAgreement
952
- extendedKeyUsage = critical,serverAuth ,clientAuth
953
- nsComment = "Self-signed certificate, generated by NodeOPCUA"
954
- subjectAltName = $ENV::ALTNAME
955
-
956
- [ crl_ext ]
957
- #issuerAltName = issuer:copy
958
- authorityKeyIdentifier = keyid:always,issuer:always
959
- #authorityInfoAccess = @issuer_info`;
960
- var ca_config_template_cnf_default = config2;
961
-
962
- // packages/node-opcua-pki/lib/ca/certificate_authority.ts
963
- var defaultSubject = "/C=FR/ST=IDF/L=Paris/O=Local NODE-OPCUA Certificate Authority/CN=NodeOPCUA-CA";
964
- var configurationFileTemplate = ca_config_template_cnf_default;
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
- }
972
- var config3 = {
973
- certificateDir: "INVALID",
974
- forceCA: false,
975
- pkiDir: "INVALID"
976
- };
977
- var n4 = makePath;
978
- function octetStringToIpAddress(a) {
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();
980
- }
981
- assert7(octetStringToIpAddress("c07b9179") === "192.123.145.121");
982
- async function construct_CertificateAuthority(certificateAuthority) {
983
- const subject = certificateAuthority.subject;
984
- const caRootDir = path5.resolve(certificateAuthority.rootDir);
985
- async function make_folders() {
986
- mkdirRecursiveSync(caRootDir);
987
- ensurePrivateDirectory(path5.join(caRootDir, "private"));
988
- mkdirRecursiveSync(path5.join(caRootDir, "public"));
989
- mkdirRecursiveSync(path5.join(caRootDir, "certs"));
990
- mkdirRecursiveSync(path5.join(caRootDir, "crl"));
991
- mkdirRecursiveSync(path5.join(caRootDir, "conf"));
992
- }
993
- await make_folders();
994
- async function construct_default_files() {
995
- const serial = path5.join(caRootDir, "serial");
996
- if (!fs7.existsSync(serial)) {
997
- await fs7.promises.writeFile(serial, "1000");
998
- }
999
- const crlNumber = path5.join(caRootDir, "crlnumber");
1000
- if (!fs7.existsSync(crlNumber)) {
1001
- await fs7.promises.writeFile(crlNumber, "1000");
1002
- }
1003
- const indexFile = path5.join(caRootDir, "index.txt");
1004
- if (!fs7.existsSync(indexFile)) {
1005
- await fs7.promises.writeFile(indexFile, "");
1006
- }
1007
- }
1008
- await construct_default_files();
1009
- const caKeyExists = fs7.existsSync(path5.join(caRootDir, "private/cakey.pem"));
1010
- const caCertExists = fs7.existsSync(path5.join(caRootDir, "public/cacert.pem"));
1011
- if (caKeyExists && caCertExists && !config3.forceCA) {
1012
- restrictPrivateFilePermissions(path5.join(caRootDir, "private/cakey.pem"), 384);
1013
- await certificateAuthority._ensurePrivateKeyProtection();
1014
- debugLog("CA private key and certificate already exist ... skipping");
1015
- return;
1016
- }
1017
- if (caKeyExists && !caCertExists) {
1018
- debugLog("CA private key exists but cacert.pem is missing \u2014 rebuilding CA");
1019
- fs7.unlinkSync(path5.join(caRootDir, "private/cakey.pem"));
1020
- const staleCsr = path5.join(caRootDir, "private/cakey.csr");
1021
- if (fs7.existsSync(staleCsr)) {
1022
- fs7.unlinkSync(staleCsr);
1023
- }
1024
- }
1025
- displayTitle("Create Certificate Authority (CA)");
1026
- const indexFileAttr = path5.join(caRootDir, "index.txt.attr");
1027
- if (!fs7.existsSync(indexFileAttr)) {
1028
- await fs7.promises.writeFile(indexFileAttr, "unique_subject = no");
1029
- }
1030
- const caConfigFile = certificateAuthority.configFile;
1031
- if (1) {
1032
- await fs7.promises.writeFile(caConfigFile, renderCaConfig(caRootDir));
1033
- }
1034
- const subjectOpt = ["-subj", subject.toString()];
1035
- const caCommonName = subject.commonName || "NodeOPCUA-CA";
1036
- setEnv("ALTNAME", `URI:urn:${caCommonName}`);
1037
- certificateAuthority._wireRevocationEnvVars();
1038
- const options = { cwd: caRootDir };
1039
- const configFile = generateStaticConfig("conf/caconfig.cnf", options);
1040
- const configOption = ["-config", n4(configFile)];
1041
- const keySize = certificateAuthority.keySize;
1042
- const privateKeyFilename = path5.join(caRootDir, "private/cakey.pem");
1043
- const csrFilename = path5.join(caRootDir, "private/cakey.csr");
1044
- displayTitle(`Generate the CA private Key - ${keySize}`);
1045
- const passin = await certificateAuthority._opensslPassin();
1046
- await generatePrivateKeyFile(privateKeyFilename, keySize, { passphrase: await certificateAuthority._privateKeyPassphrase() });
1047
- restrictPrivateFilePermissions(privateKeyFilename, 384);
1048
- displayTitle("Generate a certificate request for the CA key");
1049
- await execute_openssl(
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 }
1066
- );
1067
- const issuerCA = certificateAuthority._issuerCA;
1068
- if (issuerCA) {
1069
- displayTitle("Generate CA Certificate (signed by issuer CA)");
1070
- const issuerCert = path5.resolve(issuerCA.caCertificate);
1071
- const issuerKey = path5.resolve(issuerCA.rootDir, "private/cakey.pem");
1072
- const issuerSerial = path5.resolve(issuerCA.rootDir, "serial");
1073
- const issuerPassin = await issuerCA._opensslPassin();
1074
- await execute_openssl(
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 }
1099
- );
1100
- } else {
1101
- displayTitle("Generate CA Certificate (self-signed)");
1102
- await execute_openssl(
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 }
1123
- );
1124
- }
1125
- displaySubtitle("generate initial CRL (Certificate Revocation List)");
1126
- await regenerateCrl(certificateAuthority.revocationList, configOption, options, passin);
1127
- displayTitle("Create Certificate Authority (CA) ---> DONE");
1128
- }
1129
- async function regenerateCrl(revocationList, configOption, options, passin) {
1130
- displaySubtitle("regenerate CRL (Certificate Revocation List)");
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);
1136
- displaySubtitle("Display (Certificate Revocation List)");
1137
- await execute_openssl(["crl", "-in", n4(revocationList), "-text", "-noout"], options);
1138
- }
1139
- function parseOpenSSLDate(dateStr) {
1140
- const raw = dateStr?.split(",")[0] ?? "";
1141
- if (raw.length < 12) return "";
1142
- const yy = parseInt(raw.substring(0, 2), 10);
1143
- const year = yy >= 70 ? 1900 + yy : 2e3 + yy;
1144
- const month = raw.substring(2, 4);
1145
- const day = raw.substring(4, 6);
1146
- const hour = raw.substring(6, 8);
1147
- const min = raw.substring(8, 10);
1148
- const sec = raw.substring(10, 12);
1149
- return `${year}-${month}-${day}T${hour}:${min}:${sec}Z`;
1150
- }
1151
- function validateRevocationUrl(url, fieldName) {
1152
- if (url === void 0) {
1153
- return void 0;
1154
- }
1155
- if (url === "") {
1156
- throw new Error(`${fieldName} must not be empty \u2014 pass undefined to disable the extension`);
1157
- }
1158
- let parsed;
1159
- try {
1160
- parsed = new URL(url);
1161
- } catch {
1162
- throw new Error(`${fieldName} is not a valid URL: ${url}`);
1163
- }
1164
- if (parsed.protocol !== "http:" && parsed.protocol !== "https:") {
1165
- throw new Error(`${fieldName} must use http: or https: (got ${parsed.protocol} in ${url})`);
1166
- }
1167
- if (!parsed.pathname || parsed.pathname === "/") {
1168
- throw new Error(`${fieldName} must include a path component (got ${url})`);
1169
- }
1170
- const isLoopback = parsed.hostname === "localhost" || parsed.hostname === "::1" || parsed.hostname.startsWith("127.");
1171
- if (isLoopback) {
1172
- console.warn(
1173
- `[node-opcua-pki] ${fieldName} points at loopback (${url}) \u2014 certificates issued with this URL will be unreachable from any other host.`
1174
- );
1175
- }
1176
- return url;
1177
- }
1178
- var CertificateAuthority = class {
1179
- /** RSA key size used when generating the CA private key. */
1180
- keySize;
1181
- /** Root filesystem path of the CA directory structure. */
1182
- location;
1183
- /** X.500 subject of the CA certificate. */
1184
- subject;
1185
- /** @internal Parent CA (undefined for root CAs). */
1186
- _issuerCA;
1187
- /** @internal Configured CDP / AIA URLs (US-202). */
1188
- _crlDistributionUrl;
1189
- _ocspResponderUrl;
1190
- _caIssuersUrl;
1191
- #privateKeyPassphrase;
1192
- /** resolved once (see `privateKeyPassphrase`); `#passphraseResolved` distinguishes "none" from "not yet" */
1193
- #resolvedPassphrase;
1194
- #passphraseResolved = false;
1195
- constructor(options) {
1196
- assert7(Object.prototype.hasOwnProperty.call(options, "location"));
1197
- assert7(Object.prototype.hasOwnProperty.call(options, "keySize"));
1198
- this.location = options.location;
1199
- this.keySize = options.keySize || 2048;
1200
- this.subject = new Subject2(options.subject || defaultSubject);
1201
- this._issuerCA = options.issuerCA;
1202
- this.#privateKeyPassphrase = options.privateKeyPassphrase;
1203
- if (options.crlDistributionUrl !== void 0) {
1204
- this.setCrlDistributionUrl(options.crlDistributionUrl);
1205
- }
1206
- if (options.ocspResponderUrl !== void 0) {
1207
- this.setOcspResponderUrl(options.ocspResponderUrl);
1208
- }
1209
- if (options.caIssuersUrl !== void 0) {
1210
- this.setCaIssuersUrl(options.caIssuersUrl);
1211
- }
1212
- }
1213
- /**
1214
- * Public URL where the CRL produced by this CA is reachable, or
1215
- * `undefined` if no CDP extension should be emitted on issued certs.
1216
- */
1217
- get crlDistributionUrl() {
1218
- return this._crlDistributionUrl;
1219
- }
1220
- /**
1221
- * Public URL of the OCSP responder, or `undefined` if no AIA OCSP
1222
- * leg should be emitted on issued certs.
1223
- */
1224
- get ocspResponderUrl() {
1225
- return this._ocspResponderUrl;
1226
- }
1227
- /**
1228
- * Public URL where the issuer's certificate can be fetched, or
1229
- * `undefined` if no AIA caIssuers leg should be emitted.
1230
- */
1231
- get caIssuersUrl() {
1232
- return this._caIssuersUrl;
1233
- }
1234
- /**
1235
- * Configure the URL embedded as `crlDistributionPoints` in every
1236
- * subsequently-issued certificate. Pass `undefined` to disable
1237
- * the extension entirely. Validated synchronously — throws on
1238
- * empty string, non-http(s) protocol, missing path. Warns (does
1239
- * not throw) when the URL points at loopback.
1240
- *
1241
- * @see US-202
1242
- */
1243
- setCrlDistributionUrl(url) {
1244
- this._crlDistributionUrl = validateRevocationUrl(url, "crlDistributionUrl");
1245
- }
1246
- /**
1247
- * Configure the OCSP responder URL embedded as the `OCSP` leg of
1248
- * the `authorityInfoAccess` extension on every subsequently-issued
1249
- * certificate. Pass `undefined` to disable.
1250
- *
1251
- * @see US-202
1252
- */
1253
- setOcspResponderUrl(url) {
1254
- this._ocspResponderUrl = validateRevocationUrl(url, "ocspResponderUrl");
1255
- }
1256
- /**
1257
- * Configure the caIssuers URL embedded as the `caIssuers` leg of
1258
- * the `authorityInfoAccess` extension on every subsequently-issued
1259
- * certificate. Pass `undefined` to disable.
1260
- *
1261
- * @see US-202
1262
- */
1263
- setCaIssuersUrl(url) {
1264
- this._caIssuersUrl = validateRevocationUrl(url, "caIssuersUrl");
1265
- }
1266
- /**
1267
- * @internal
1268
- * Populate the OpenSSL config substitution env vars (`CDP_URL` and
1269
- * `AIA_VALUE`) from the configured URLs, or unset them so the
1270
- * matching `{{#KEY}}...{{/KEY}}` blocks in the templates are
1271
- * stripped. MUST be called before every `generateStaticConfig`
1272
- * invocation that signs a certificate.
1273
- */
1274
- _wireRevocationEnvVars() {
1275
- unsetEnv("CDP_URL");
1276
- unsetEnv("AIA_VALUE");
1277
- if (this._crlDistributionUrl) {
1278
- setEnv("CDP_URL", this._crlDistributionUrl);
1279
- }
1280
- const aiaLegs = [];
1281
- if (this._ocspResponderUrl) {
1282
- aiaLegs.push(`OCSP;URI:${this._ocspResponderUrl}`);
1283
- }
1284
- if (this._caIssuersUrl) {
1285
- aiaLegs.push(`caIssuers;URI:${this._caIssuersUrl}`);
1286
- }
1287
- if (aiaLegs.length > 0) {
1288
- setEnv("AIA_VALUE", aiaLegs.join(","));
1289
- }
1290
- }
1291
- /** Absolute path to the CA root directory (alias for {@link location}). */
1292
- get rootDir() {
1293
- return this.location;
1294
- }
1295
- /** Path to the OpenSSL configuration file (`conf/caconfig.cnf`). */
1296
- get configFile() {
1297
- return path5.normalize(path5.join(this.rootDir, "./conf/caconfig.cnf"));
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
- }
1362
- /** Path to the CA certificate in PEM format (`public/cacert.pem`). */
1363
- get caCertificate() {
1364
- return makePath(this.rootDir, "./public/cacert.pem");
1365
- }
1366
- /**
1367
- * Path to the issuer certificate chain (`public/issuer_chain.pem`).
1368
- *
1369
- * This file is created by {@link installCACertificate} when the
1370
- * provided cert file contains additional issuer certificates
1371
- * (e.g. intermediate + root). It is appended to signed certs
1372
- * by {@link constructCertificateChain} to produce a full chain
1373
- * per OPC UA Part 6 §6.2.6.
1374
- */
1375
- get issuerCertificateChain() {
1376
- return makePath(this.rootDir, "./public/issuer_chain.pem");
1377
- }
1378
- /**
1379
- * Path to the current Certificate Revocation List in DER format.
1380
- * (`crl/revocation_list.der`)
1381
- */
1382
- get revocationListDER() {
1383
- return makePath(this.rootDir, "./crl/revocation_list.der");
1384
- }
1385
- /**
1386
- * Path to the current Certificate Revocation List in PEM format.
1387
- * (`crl/revocation_list.crl`)
1388
- */
1389
- get revocationList() {
1390
- return makePath(this.rootDir, "./crl/revocation_list.crl");
1391
- }
1392
- /**
1393
- * Path to the concatenated CA certificate + CRL file.
1394
- * Used by OpenSSL for CRL-based verification.
1395
- */
1396
- get caCertificateWithCrl() {
1397
- return makePath(this.rootDir, "./public/cacertificate_with_crl.pem");
1398
- }
1399
- // ---------------------------------------------------------------
1400
- // Buffer-based accessors (US-059)
1401
- // ---------------------------------------------------------------
1402
- /**
1403
- * Return the CA certificate as a DER-encoded buffer.
1404
- *
1405
- * @throws if the CA certificate file does not exist
1406
- * (call {@link initialize} first).
1407
- */
1408
- getCACertificateDER() {
1409
- const pem = readCertificatePEM(this.caCertificate);
1410
- return convertPEMtoDER(pem);
1411
- }
1412
- /**
1413
- * Return the CA certificate as a PEM-encoded string.
1414
- *
1415
- * @throws if the CA certificate file does not exist
1416
- * (call {@link initialize} first).
1417
- */
1418
- getCACertificatePEM() {
1419
- const raw = readCertificatePEM(this.caCertificate);
1420
- const beginMarker = "-----BEGIN CERTIFICATE-----";
1421
- const idx = raw.indexOf(beginMarker);
1422
- if (idx > 0) {
1423
- return raw.substring(idx);
1424
- }
1425
- return raw;
1426
- }
1427
- /**
1428
- * Return the current Certificate Revocation List as a
1429
- * DER-encoded buffer.
1430
- *
1431
- * Returns an empty buffer if no CRL has been generated yet.
1432
- */
1433
- getCRLDER() {
1434
- const crlPath = this.revocationListDER;
1435
- if (!fs7.existsSync(crlPath)) {
1436
- return Buffer.alloc(0);
1437
- }
1438
- return fs7.readFileSync(crlPath);
1439
- }
1440
- /**
1441
- * Return the current Certificate Revocation List as a
1442
- * PEM-encoded string.
1443
- *
1444
- * Returns an empty string if no CRL has been generated yet.
1445
- */
1446
- getCRLPEM() {
1447
- const crlPath = this.revocationList;
1448
- if (!fs7.existsSync(crlPath)) {
1449
- return "";
1450
- }
1451
- const raw = fs7.readFileSync(crlPath, "utf-8");
1452
- const beginMarker = "-----BEGIN X509 CRL-----";
1453
- const idx = raw.indexOf(beginMarker);
1454
- if (idx > 0) {
1455
- return raw.substring(idx);
1456
- }
1457
- return raw;
1458
- }
1459
- // ---------------------------------------------------------------
1460
- // Certificate database API (US-057)
1461
- // ---------------------------------------------------------------
1462
- /**
1463
- * Return a list of all issued certificates recorded in the
1464
- * OpenSSL `index.txt` database.
1465
- *
1466
- * Each entry includes the serial number, subject, status,
1467
- * expiry date, and (for revoked certs) the revocation date.
1468
- */
1469
- getIssuedCertificates() {
1470
- return this._parseIndexTxt();
1471
- }
1472
- /**
1473
- * Return the total number of certificates recorded in
1474
- * `index.txt`.
1475
- */
1476
- getIssuedCertificateCount() {
1477
- return this._parseIndexTxt().length;
1478
- }
1479
- /**
1480
- * Return the status of a certificate by its serial number.
1481
- *
1482
- * @param serial - hex-encoded serial number (e.g. `"1000"`)
1483
- * @returns `"valid"`, `"revoked"`, `"expired"`, or
1484
- * `undefined` if not found
1485
- */
1486
- getCertificateStatus(serial) {
1487
- const upper = serial.toUpperCase();
1488
- const record = this._parseIndexTxt().find((r) => r.serial.toUpperCase() === upper);
1489
- return record?.status;
1490
- }
1491
- /**
1492
- * Read a specific issued certificate by serial number and
1493
- * return its content as a DER-encoded buffer.
1494
- *
1495
- * OpenSSL stores signed certificates in the `certs/`
1496
- * directory using the naming convention `<SERIAL>.pem`.
1497
- *
1498
- * @param serial - hex-encoded serial number (e.g. `"1000"`)
1499
- * @returns the DER buffer, or `undefined` if not found
1500
- */
1501
- getCertificateBySerial(serial) {
1502
- const upper = serial.toUpperCase();
1503
- const certFile = path5.join(this.rootDir, "certs", `${upper}.pem`);
1504
- if (!fs7.existsSync(certFile)) {
1505
- return void 0;
1506
- }
1507
- const pem = readCertificatePEM(certFile);
1508
- return convertPEMtoDER(pem);
1509
- }
1510
- /**
1511
- * Path to the OpenSSL certificate database file.
1512
- */
1513
- get indexFile() {
1514
- return path5.join(this.rootDir, "index.txt");
1515
- }
1516
- /**
1517
- * Parse the OpenSSL `index.txt` certificate database.
1518
- *
1519
- * Each line has tab-separated fields:
1520
- * ```
1521
- * status expiry [revocationDate] serial unknown subject
1522
- * ```
1523
- *
1524
- * - status: `V` (valid), `R` (revoked), `E` (expired)
1525
- * - expiry: `YYMMDDHHmmssZ`
1526
- * - revocationDate: present only for revoked certs
1527
- * - serial: hex string
1528
- * - unknown: always `"unknown"`
1529
- * - subject: X.500 slash-delimited string
1530
- */
1531
- _parseIndexTxt() {
1532
- const indexPath = this.indexFile;
1533
- if (!fs7.existsSync(indexPath)) {
1534
- return [];
1535
- }
1536
- const content = fs7.readFileSync(indexPath, "utf-8");
1537
- const lines = content.split("\n").filter((l) => l.trim().length > 0);
1538
- const records = [];
1539
- for (const line of lines) {
1540
- const fields = line.split(" ");
1541
- if (fields.length < 4) continue;
1542
- const statusChar = fields[0];
1543
- const expiryStr = fields[1];
1544
- let serial;
1545
- let subject;
1546
- let revocationDate;
1547
- if (statusChar === "R") {
1548
- revocationDate = fields[2];
1549
- serial = fields[3];
1550
- subject = fields.length >= 6 ? fields[5] : "";
1551
- } else {
1552
- serial = fields[3];
1553
- subject = fields.length >= 6 ? fields[5] : "";
1554
- }
1555
- let status;
1556
- switch (statusChar) {
1557
- case "V":
1558
- status = "valid";
1559
- break;
1560
- case "R":
1561
- status = "revoked";
1562
- break;
1563
- case "E":
1564
- status = "expired";
1565
- break;
1566
- default:
1567
- continue;
1568
- }
1569
- records.push({
1570
- serial,
1571
- status,
1572
- subject,
1573
- expiryDate: parseOpenSSLDate(expiryStr),
1574
- revocationDate: revocationDate ? parseOpenSSLDate(revocationDate) : void 0
1575
- });
1576
- }
1577
- return records;
1578
- }
1579
- // ---------------------------------------------------------------
1580
- // Buffer-based CA operations (US-058)
1581
- // ---------------------------------------------------------------
1582
- /**
1583
- * Sign a DER-encoded Certificate Signing Request and return
1584
- * the signed certificate as a DER buffer.
1585
- *
1586
- * This method handles temp-file creation and cleanup
1587
- * internally so that callers can work with in-memory
1588
- * buffers only.
1589
- *
1590
- * The CA can override fields from the CSR by passing
1591
- * `options.dns`, `options.ip`, `options.applicationUri`,
1592
- * `options.startDate`, or `options.subject`.
1593
- *
1594
- * @param csrDer - the CSR as a DER-encoded buffer
1595
- * @param options - signing options and CA overrides
1596
- * @returns the signed certificate as a DER-encoded buffer
1597
- */
1598
- async signCertificateRequestFromDER(csrDer, options) {
1599
- const tmpDir = await fs7.promises.mkdtemp(path5.join(os3.tmpdir(), "pki-sign-"));
1600
- try {
1601
- const csrFile = path5.join(tmpDir, "request.csr");
1602
- const certFile = path5.join(tmpDir, "certificate.pem");
1603
- const csrPem = toPem(csrDer, "CERTIFICATE REQUEST");
1604
- await fs7.promises.writeFile(csrFile, csrPem, "utf-8");
1605
- const signingParams = {};
1606
- if (options?.validityMs !== void 0) signingParams.validityMs = options.validityMs;
1607
- else signingParams.validity = options?.validity ?? 365;
1608
- if (options?.startDate) signingParams.startDate = options.startDate;
1609
- if (options?.dns) signingParams.dns = options.dns;
1610
- if (options?.ip) signingParams.ip = options.ip;
1611
- if (options?.applicationUri) signingParams.applicationUri = options.applicationUri;
1612
- if (options?.subject) signingParams.subject = options.subject;
1613
- await this.signCertificateRequest(certFile, csrFile, signingParams);
1614
- const certPem = readCertificatePEM(certFile);
1615
- return convertPEMtoDER(certPem);
1616
- } finally {
1617
- await fs7.promises.rm(tmpDir, {
1618
- recursive: true,
1619
- force: true
1620
- });
1621
- }
1622
- }
1623
- /**
1624
- * Advertise the validity limits this CA can honor.
1625
- *
1626
- * Consumers (notably the GDS server in [`cert_auth.ts`](https://github.com/sterfive/node-opcua-gds))
1627
- * clamp a requested validity against these bounds before calling
1628
- * {@link signCertificateRequestFromDER}, so a misconfigured
1629
- * `defaultCertValidity` cannot ask the CA for something it cannot
1630
- * produce.
1631
- *
1632
- * Defaults match the OpenSSL-backed implementation:
1633
- * - `minValidityMs = 60_000` (1 minute) — practical floor; the
1634
- * X.509 spec floor is 1 second but very short certs are rarely
1635
- * useful and pathological for any real deployment.
1636
- * - `maxValidityMs = 10 * 365 * 86_400_000` (≈ 10 years) — long
1637
- * enough for root CAs.
1638
- * - `validityGranularityMs = 1_000` (1 second) — RFC 5280 §4.1.2.5
1639
- * floor on `notBefore` / `notAfter`.
1640
- * - `nativeUnit = "second"` — what `x509Date()` actually encodes.
1641
- *
1642
- * @see US-208 — the consumer-side capability story.
1643
- */
1644
- getCapabilities() {
1645
- return {
1646
- minValidityMs: 6e4,
1647
- maxValidityMs: 10 * 365 * 864e5,
1648
- validityGranularityMs: 1e3,
1649
- nativeUnit: "second"
1650
- };
1651
- }
1652
- /**
1653
- * Generate a new RSA key pair, create an internal CSR, sign it
1654
- * with this CA, and return both the certificate and private key
1655
- * as DER-encoded buffers.
1656
- *
1657
- * The private key is **never stored** by the CA — it exists only
1658
- * in a temporary directory that is cleaned up after the operation.
1659
- *
1660
- * This is used by `StartNewKeyPairRequest` (OPC UA Part 12) for
1661
- * constrained devices that cannot generate their own keys.
1662
- *
1663
- * @param options - key generation and certificate parameters
1664
- * @returns `{ certificateDer, privateKey }` — certificate as DER,
1665
- * private key as a branded `PrivateKey` buffer
1666
- */
1667
- async generateKeyPairAndSignDER(options) {
1668
- const keySize = options.keySize ?? 2048;
1669
- const startDate = options.startDate ?? /* @__PURE__ */ new Date();
1670
- const tmpDir = await fs7.promises.mkdtemp(path5.join(os3.tmpdir(), "pki-keygen-"));
1671
- try {
1672
- const privateKeyFile = path5.join(tmpDir, "private_key.pem");
1673
- await generatePrivateKeyFile(privateKeyFile, keySize);
1674
- const configFile = path5.join(tmpDir, "openssl.cnf");
1675
- await fs7.promises.writeFile(configFile, configurationFileSimpleTemplate, "utf-8");
1676
- const csrFile = path5.join(tmpDir, "request.csr");
1677
- await createCertificateSigningRequestWithOpenSSL(csrFile, {
1678
- rootDir: tmpDir,
1679
- configFile,
1680
- privateKey: privateKeyFile,
1681
- applicationUri: options.applicationUri,
1682
- subject: options.subject,
1683
- dns: options.dns ?? [],
1684
- ip: options.ip ?? [],
1685
- purpose: CertificatePurpose.ForApplication
1686
- });
1687
- const certFile = path5.join(tmpDir, "certificate.pem");
1688
- const signingParams = {
1689
- applicationUri: options.applicationUri,
1690
- dns: options.dns,
1691
- ip: options.ip,
1692
- startDate
1693
- };
1694
- if (options.validityMs !== void 0) signingParams.validityMs = options.validityMs;
1695
- else signingParams.validity = options.validity ?? 365;
1696
- await this.signCertificateRequest(certFile, csrFile, signingParams);
1697
- const certPem = readCertificatePEM(certFile);
1698
- const certificateDer = convertPEMtoDER(certPem);
1699
- const privateKey = readPrivateKey(privateKeyFile);
1700
- return { certificateDer, privateKey };
1701
- } finally {
1702
- await fs7.promises.rm(tmpDir, {
1703
- recursive: true,
1704
- force: true
1705
- });
1706
- }
1707
- }
1708
- /**
1709
- * Generate a new RSA key pair, create an internal CSR, sign it
1710
- * with this CA, and return the result as a PKCS#12 (PFX)
1711
- * buffer bundling the certificate, private key, and CA chain.
1712
- *
1713
- * The private key is **never stored** by the CA — it exists only
1714
- * in a temporary directory that is cleaned up after the operation.
1715
- *
1716
- * @param options - key generation, certificate, and PFX options
1717
- * @returns the PFX as a `Buffer`
1718
- */
1719
- async generateKeyPairAndSignPFX(options) {
1720
- const keySize = options.keySize ?? 2048;
1721
- const startDate = options.startDate ?? /* @__PURE__ */ new Date();
1722
- const passphrase = options.passphrase ?? "";
1723
- const tmpDir = await fs7.promises.mkdtemp(path5.join(os3.tmpdir(), "pki-keygen-pfx-"));
1724
- try {
1725
- const privateKeyFile = path5.join(tmpDir, "private_key.pem");
1726
- await generatePrivateKeyFile(privateKeyFile, keySize);
1727
- const configFile = path5.join(tmpDir, "openssl.cnf");
1728
- await fs7.promises.writeFile(configFile, configurationFileSimpleTemplate, "utf-8");
1729
- const csrFile = path5.join(tmpDir, "request.csr");
1730
- await createCertificateSigningRequestWithOpenSSL(csrFile, {
1731
- rootDir: tmpDir,
1732
- configFile,
1733
- privateKey: privateKeyFile,
1734
- applicationUri: options.applicationUri,
1735
- subject: options.subject,
1736
- dns: options.dns ?? [],
1737
- ip: options.ip ?? [],
1738
- purpose: CertificatePurpose.ForApplication
1739
- });
1740
- const certFile = path5.join(tmpDir, "certificate.pem");
1741
- const signingParams = {
1742
- applicationUri: options.applicationUri,
1743
- dns: options.dns,
1744
- ip: options.ip,
1745
- startDate
1746
- };
1747
- if (options.validityMs !== void 0) signingParams.validityMs = options.validityMs;
1748
- else signingParams.validity = options.validity ?? 365;
1749
- await this.signCertificateRequest(certFile, csrFile, signingParams);
1750
- const pfxFile = path5.join(tmpDir, "bundle.pfx");
1751
- await createPFX({
1752
- certificateFile: certFile,
1753
- privateKeyFile,
1754
- outputFile: pfxFile,
1755
- passphrase,
1756
- caCertificateFiles: [this.caCertificate]
1757
- });
1758
- return await fs7.promises.readFile(pfxFile);
1759
- } finally {
1760
- await fs7.promises.rm(tmpDir, {
1761
- recursive: true,
1762
- force: true
1763
- });
1764
- }
1765
- }
1766
- /**
1767
- * Revoke a DER-encoded certificate and regenerate the CRL.
1768
- *
1769
- * Extracts the serial number from the certificate, then
1770
- * uses the stored cert file at `certs/<serial>.pem` for
1771
- * revocation — avoiding temp-file PEM format mismatches.
1772
- *
1773
- * @param certDer - the certificate as a DER-encoded buffer
1774
- * @param reason - CRL reason code
1775
- * (default: `"keyCompromise"`)
1776
- * @throws if the certificate's serial is not found in the CA
1777
- */
1778
- async revokeCertificateDER(certDer, reason) {
1779
- const info = exploreCertificate(certDer);
1780
- const serial = info.tbsCertificate.serialNumber.replace(/:/g, "").toUpperCase();
1781
- const storedCertFile = path5.join(this.rootDir, "certs", `${serial}.pem`);
1782
- if (!fs7.existsSync(storedCertFile)) {
1783
- throw new Error(`Cannot revoke: no stored certificate found for serial ${serial} at ${storedCertFile}`);
1784
- }
1785
- await this.revokeCertificate(storedCertFile, {
1786
- reason: reason ?? "keyCompromise"
1787
- });
592
+ // packages/node-opcua-pki/lib/ca/backends/openssl_ca_backend.ts
593
+ var n2 = makePath;
594
+ function caAltName(ca) {
595
+ return `URI:urn:${ca.subject.commonName || "NodeOPCUA-CA"}`;
596
+ }
597
+ function caConfigEnvOverrides(ca, altName = caAltName(ca)) {
598
+ const aiaLegs = [];
599
+ if (ca.ocspResponderUrl) {
600
+ aiaLegs.push(`OCSP;URI:${ca.ocspResponderUrl}`);
1788
601
  }
1789
- /**
1790
- * Initialize the CA directory structure, generate the CA
1791
- * private key and self-signed certificate if they do not
1792
- * already exist.
1793
- */
1794
- async initialize() {
1795
- await construct_CertificateAuthority(this);
602
+ if (ca.caIssuersUrl) {
603
+ aiaLegs.push(`caIssuers;URI:${ca.caIssuersUrl}`);
1796
604
  }
605
+ return {
606
+ ALTNAME: altName,
607
+ CDP_URL: ca.crlDistributionUrl ?? "",
608
+ AIA_VALUE: aiaLegs.join(",")
609
+ };
610
+ }
611
+ var OpenSslCaBackend = class {
612
+ /** This backend is the `openssl` executable, so it has to be there. */
1797
613
  /**
1798
- * Initialize the CA directory structure and generate the
1799
- * private key + CSR **without signing**.
1800
- *
1801
- * Use this when the CA certificate will be signed by an
1802
- * external (third-party) root CA. After receiving the signed
1803
- * certificate, call {@link installCACertificate} to complete
1804
- * the setup.
1805
- *
1806
- * **Idempotent / restart-safe:**
1807
- * - If the CA certificate exists and is valid → `{ status: "ready" }`
1808
- * - If the CA certificate has expired → `{ status: "expired", csrPath, expiryDate }`
1809
- * (a new CSR is generated, preserving the existing private key)
1810
- * - If key + CSR exist but no cert (restart before install) →
1811
- * `{ status: "pending", csrPath }` without regenerating
1812
- * - Otherwise → generates key + CSR → `{ status: "created", csrPath }`
1813
- *
1814
- * @returns an {@link InitializeCSRResult} describing the CA state
614
+ * `-passin env:` argv and env for an openssl call that has to load this
615
+ * CA's key. Built here rather than on the CA: the passphrase is the
616
+ * CA's business, but expressing it as an openssl argument is this
617
+ * backend's, and the core has no reason to know the flag exists.
1815
618
  */
1816
- async initializeCSR() {
1817
- const caRootDir = path5.resolve(this.rootDir);
1818
- mkdirRecursiveSync(caRootDir);
1819
- for (const dir of ["public", "certs", "crl", "conf"]) {
1820
- mkdirRecursiveSync(path5.join(caRootDir, dir));
1821
- }
1822
- ensurePrivateDirectory(path5.join(caRootDir, "private"));
1823
- const caCertFile = this.caCertificate;
1824
- const privateKeyFile = path5.join(caRootDir, "private/cakey.pem");
1825
- const csrFile = path5.join(caRootDir, "private/cakey.csr");
1826
- if (fs7.existsSync(privateKeyFile)) {
1827
- restrictPrivateFilePermissions(privateKeyFile, 384);
1828
- await this._ensurePrivateKeyProtection();
1829
- }
1830
- if (fs7.existsSync(caCertFile)) {
1831
- const certDer = convertPEMtoDER(readCertificatePEM(caCertFile));
1832
- const certInfo = exploreCertificate(certDer);
1833
- const notAfter = certInfo.tbsCertificate.validity.notAfter;
1834
- if (notAfter.getTime() < Date.now()) {
1835
- debugLog("CA certificate has expired \u2014 generating renewal CSR");
1836
- await this._generateCSR(caRootDir, privateKeyFile, csrFile);
1837
- return { status: "expired", csrPath: csrFile, expiryDate: notAfter };
1838
- }
1839
- debugLog("CA certificate already exists and is valid \u2014 ready");
1840
- return { status: "ready" };
1841
- }
1842
- if (fs7.existsSync(privateKeyFile) && fs7.existsSync(csrFile)) {
1843
- debugLog("CA key + CSR already exist \u2014 pending external signing");
1844
- return { status: "pending", csrPath: csrFile };
1845
- }
1846
- const serial = path5.join(caRootDir, "serial");
1847
- if (!fs7.existsSync(serial)) {
1848
- await fs7.promises.writeFile(serial, "1000");
1849
- }
1850
- const crlNumber = path5.join(caRootDir, "crlnumber");
1851
- if (!fs7.existsSync(crlNumber)) {
1852
- await fs7.promises.writeFile(crlNumber, "1000");
1853
- }
1854
- const indexFile = path5.join(caRootDir, "index.txt");
1855
- if (!fs7.existsSync(indexFile)) {
1856
- await fs7.promises.writeFile(indexFile, "");
1857
- }
1858
- const indexFileAttr = path5.join(caRootDir, "index.txt.attr");
1859
- if (!fs7.existsSync(indexFileAttr)) {
1860
- await fs7.promises.writeFile(indexFileAttr, "unique_subject = no");
1861
- }
1862
- const caConfigFile = this.configFile;
1863
- await fs7.promises.writeFile(caConfigFile, renderCaConfig(caRootDir));
1864
- if (!fs7.existsSync(privateKeyFile)) {
1865
- await generatePrivateKeyFile(privateKeyFile, this.keySize, { passphrase: await this._privateKeyPassphrase() });
1866
- restrictPrivateFilePermissions(privateKeyFile, 384);
1867
- }
1868
- await this._generateCSR(caRootDir, privateKeyFile, csrFile);
1869
- return { status: "created", csrPath: csrFile };
619
+ async #passin(ca) {
620
+ return passinArg(await ca._privateKeyPassphrase());
1870
621
  }
1871
- /**
1872
- * Check whether the CA certificate needs renewal and, if so,
1873
- * generate a new CSR for re-signing by the external root CA.
1874
- *
1875
- * Use this while the CA is running to detect upcoming expiry
1876
- * **before** it actually expires. The existing private key is
1877
- * preserved so previously issued certs remain valid.
1878
- *
1879
- * @param thresholdDays - number of days before expiry at which
1880
- * to trigger renewal (default: 30)
1881
- * @returns an {@link InitializeCSRResult} — `"expired"` if
1882
- * renewal is needed, `"ready"` if the cert is still valid
1883
- */
1884
- async renewCSR(thresholdDays = 30) {
1885
- const caRootDir = path5.resolve(this.rootDir);
1886
- const caCertFile = this.caCertificate;
1887
- const privateKeyFile = path5.join(caRootDir, "private/cakey.pem");
1888
- const csrFile = path5.join(caRootDir, "private/cakey.csr");
1889
- if (!fs7.existsSync(caCertFile)) {
1890
- return this.initializeCSR();
1891
- }
1892
- const certDer = convertPEMtoDER(readCertificatePEM(caCertFile));
1893
- const certInfo = exploreCertificate(certDer);
1894
- const notAfter = certInfo.tbsCertificate.validity.notAfter;
1895
- const thresholdMs = thresholdDays * 24 * 60 * 60 * 1e3;
1896
- if (notAfter.getTime() - Date.now() < thresholdMs) {
1897
- debugLog(`CA certificate expires within ${thresholdDays} days \u2014 generating renewal CSR`);
1898
- await this._generateCSR(caRootDir, privateKeyFile, csrFile);
1899
- return { status: "expired", csrPath: csrFile, expiryDate: notAfter };
1900
- }
1901
- return { status: "ready" };
622
+ /** The openssl CLI loads its key from a file, so it cannot call out to an HSM. */
623
+ supportsExternalSigner = false;
624
+ async preflight() {
625
+ await ensure_openssl_installed();
1902
626
  }
1903
627
  /**
1904
- * Generate a CSR using the existing private key.
1905
- * @internal
628
+ * Render `conf/caconfig.cnf` once with explicit overrides, run `fn`
629
+ * with the rendered path, and always remove the temp file. Structural
630
+ * guarantee that (a) every render carries the three required env
631
+ * values and (b) no rendered `<name>.<pid>-<n>.tmp` file is ever left
632
+ * behind — each was previously a per-method discipline.
1906
633
  */
1907
- async _generateCSR(caRootDir, privateKeyFile, csrFile) {
1908
- processAltNames({});
634
+ async #withConfig(ca, altName, fn) {
635
+ const caRootDir = path3.resolve(ca.rootDir);
1909
636
  const options = { cwd: caRootDir };
1910
- const configFile = generateStaticConfig("conf/caconfig.cnf", options);
1911
- const passin = await this._opensslPassin();
637
+ const configFile = generateStaticConfig("conf/caconfig.cnf", options, caConfigEnvOverrides(ca, altName));
638
+ try {
639
+ return await fn(configFile, options);
640
+ } finally {
641
+ await cleanupStaticConfig(configFile, options);
642
+ }
643
+ }
644
+ async #generateCaCsrWith(ca, configFile, options, privateKeyFile, csrFile) {
645
+ const passin = await this.#passin(ca);
646
+ displayTitle("Generate a certificate request for the CA key");
1912
647
  await execute_openssl(
1913
648
  [
1914
649
  "req",
1915
650
  "-new",
1916
651
  "-sha256",
1917
- "-text",
1918
652
  "-extensions",
1919
653
  "v3_ca_req",
1920
654
  "-config",
1921
- n4(configFile),
655
+ n2(configFile),
1922
656
  "-key",
1923
- n4(privateKeyFile),
657
+ n2(privateKeyFile),
1924
658
  "-out",
1925
- n4(csrFile),
659
+ n2(csrFile),
1926
660
  "-subj",
1927
- this.subject.toString(),
661
+ ca.subject.toString(),
1928
662
  ...passin.args
1929
663
  ],
1930
664
  { ...options, env: passin.env }
1931
665
  );
1932
666
  }
1933
667
  /**
1934
- * Install an externally-signed CA certificate and generate
1935
- * the initial CRL.
1936
- *
1937
- * Call this after {@link initializeCSR} once the external
1938
- * root CA has signed the CSR.
1939
- *
1940
- * **Safety checks:**
1941
- * - Verifies that the certificate's public key matches the
1942
- * CA private key before installing.
1943
- *
1944
- * @param signedCertFile - path to the PEM-encoded signed
1945
- * CA certificate (issued by the external root CA)
1946
- * @returns an {@link InstallCACertificateResult} with
1947
- * `status: "success"` or `status: "error"` and a `reason`
1948
- */
1949
- async installCACertificate(signedCertFile) {
1950
- const caRootDir = path5.resolve(this.rootDir);
1951
- const caCertFile = this.caCertificate;
1952
- const fullPem = await fs7.promises.readFile(signedCertFile, "utf8");
1953
- const pemBlocks = fullPem.match(/-----BEGIN CERTIFICATE-----[\s\S]*?-----END CERTIFICATE-----/g);
1954
- if (!pemBlocks || pemBlocks.length === 0) {
1955
- return {
1956
- status: "error",
1957
- reason: "no_certificate_found",
1958
- message: "The provided file does not contain any PEM-encoded certificate."
1959
- };
1960
- }
1961
- const certDer = convertPEMtoDER(pemBlocks[0]);
1962
- const privateKey = await this.getPrivateKey();
1963
- if (!certificateMatchesPrivateKey(certDer, privateKey)) {
1964
- return {
1965
- status: "error",
1966
- reason: "certificate_key_mismatch",
1967
- message: "The provided certificate does not match the CA private key. Ensure the certificate was signed from the CSR generated by initializeCSR()."
1968
- };
1969
- }
1970
- await fs7.promises.writeFile(caCertFile, `${pemBlocks[0]}
1971
- `);
1972
- const issuerChainFile = this.issuerCertificateChain;
1973
- if (pemBlocks.length > 1) {
1974
- const issuerPem = `${pemBlocks.slice(1).join("\n")}
1975
- `;
1976
- await fs7.promises.writeFile(issuerChainFile, issuerPem);
1977
- debugLog(`Stored ${pemBlocks.length - 1} issuer certificate(s) in issuer_chain.pem`);
1978
- } else {
1979
- if (fs7.existsSync(issuerChainFile)) {
1980
- await fs7.promises.unlink(issuerChainFile);
1981
- }
1982
- }
1983
- const options = { cwd: caRootDir };
1984
- const configFile = generateStaticConfig("conf/caconfig.cnf", options);
1985
- await regenerateCrl(this.revocationList, ["-config", n4(configFile)], options, await this._opensslPassin());
1986
- return { status: "success" };
1987
- }
1988
- /**
1989
- * Sign a CSR with CA extensions (`v3_ca`), producing a
1990
- * subordinate CA certificate.
1991
- *
1992
- * Unlike {@link signCertificateRequest} which signs with
1993
- * end-entity extensions (SANs, etc.), this method signs
1994
- * with `basicConstraints = CA:TRUE` and `keyUsage =
1995
- * keyCertSign, cRLSign`.
1996
- *
1997
- * @param certFile - output path for the signed CA cert (PEM)
1998
- * @param csrFile - path to the subordinate CA's CSR
1999
- * @param params - signing parameters
668
+ * `openssl ca -gencrl` signs the CRL with the CA key it finds through the
669
+ * config file (`private_key = $dir/private/cakey.pem`), so it needs the
670
+ * CA passphrase like every other `openssl ca` invocation.
2000
671
  */
2001
- async signCACertificateRequest(certFile, csrFile, params) {
2002
- const caRootDir = path5.resolve(this.rootDir);
2003
- const options = { cwd: caRootDir };
2004
- this._wireRevocationEnvVars();
2005
- const configFile = generateStaticConfig("conf/caconfig.cnf", options);
2006
- const validity = params.validity ?? 3650;
2007
- const passin = await this._opensslPassin();
672
+ async #regenerateCrlWith(ca, configFile, options) {
673
+ const passin = await this.#passin(ca);
674
+ displaySubtitle("regenerate CRL (Certificate Revocation List)");
675
+ await execute_openssl(["ca", "-gencrl", "-config", n2(configFile), "-out", "crl/revocation_list.crl", ...passin.args], {
676
+ ...options,
677
+ env: passin.env
678
+ });
2008
679
  await execute_openssl(
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 }
680
+ ["crl", "-in", "crl/revocation_list.crl", "-out", "crl/revocation_list.der", "-outform", "der"],
681
+ options
682
+ );
683
+ displaySubtitle("Display (Certificate Revocation List)");
684
+ await execute_openssl(["crl", "-in", n2(ca.revocationList), "-text", "-noout"], options);
685
+ }
686
+ async generateCaCsr(ca, _caRootDir, privateKeyFile, csrFile) {
687
+ await this.#withConfig(
688
+ ca,
689
+ caAltName(ca),
690
+ (configFile, options) => this.#generateCaCsrWith(ca, configFile, options, privateKeyFile, csrFile)
2033
691
  );
2034
- await this.constructCertificateChain(certFile);
2035
692
  }
2036
- /**
2037
- * Rebuild the combined CA certificate + CRL file.
2038
- *
2039
- * This concatenates the CA certificate with the current
2040
- * revocation list so that OpenSSL can verify certificates
2041
- * with CRL checking enabled.
2042
- */
2043
- async constructCACertificateWithCRL() {
2044
- const cacertWithCRL = this.caCertificateWithCrl;
2045
- if (fs7.existsSync(this.revocationList)) {
2046
- await fs7.promises.writeFile(
2047
- cacertWithCRL,
2048
- fs7.readFileSync(this.caCertificate, "utf8") + fs7.readFileSync(this.revocationList, "utf8")
693
+ async bootstrap(ca) {
694
+ const caRootDir = path3.resolve(ca.rootDir);
695
+ const privateKeyFilename = path3.join(caRootDir, "private/cakey.pem");
696
+ const csrFilename = path3.join(caRootDir, "private/cakey.csr");
697
+ await this.#withConfig(ca, caAltName(ca), async (configFile, options) => {
698
+ await this.#generateCaCsrWith(ca, configFile, options, privateKeyFilename, csrFilename);
699
+ const issuerCA = ca._issuerCA;
700
+ if (issuerCA) {
701
+ displayTitle("Generate CA Certificate (signed by issuer CA)");
702
+ const issuerCert = path3.resolve(issuerCA.caCertificate);
703
+ const issuerKey = path3.resolve(issuerCA.rootDir, "private/cakey.pem");
704
+ const issuerSerial = path3.resolve(issuerCA.rootDir, "serial");
705
+ const issuerPassin = await this.#passin(issuerCA);
706
+ const signWithIssuer = async () => {
707
+ await execute_openssl(
708
+ [
709
+ "x509",
710
+ "-sha256",
711
+ "-req",
712
+ "-days",
713
+ "3650",
714
+ "-extensions",
715
+ "v3_ca",
716
+ "-extfile",
717
+ n2(configFile),
718
+ "-in",
719
+ "private/cakey.csr",
720
+ "-CA",
721
+ n2(issuerCert),
722
+ "-CAkey",
723
+ n2(issuerKey),
724
+ "-CAserial",
725
+ n2(issuerSerial),
726
+ "-out",
727
+ "public/cacert.pem",
728
+ ...issuerPassin.args
729
+ ],
730
+ { ...options, env: issuerPassin.env }
731
+ );
732
+ };
733
+ if (path3.resolve(issuerCA.rootDir) === caRootDir) {
734
+ await signWithIssuer();
735
+ } else {
736
+ await issuerCA._withCaDirectoryLock(signWithIssuer);
737
+ }
738
+ } else {
739
+ displayTitle("Generate CA Certificate (self-signed)");
740
+ const passin = await this.#passin(ca);
741
+ await execute_openssl(
742
+ [
743
+ "x509",
744
+ "-sha256",
745
+ "-req",
746
+ "-days",
747
+ "3650",
748
+ "-extensions",
749
+ "v3_ca",
750
+ "-extfile",
751
+ n2(configFile),
752
+ "-in",
753
+ "private/cakey.csr",
754
+ "-signkey",
755
+ n2(privateKeyFilename),
756
+ "-out",
757
+ "public/cacert.pem",
758
+ ...passin.args
759
+ ],
760
+ { ...options, env: passin.env }
761
+ );
762
+ }
763
+ displaySubtitle("generate initial CRL (Certificate Revocation List)");
764
+ await this.#regenerateCrlWith(ca, configFile, options);
765
+ });
766
+ displayTitle("Create Certificate Authority (CA) ---> DONE");
767
+ }
768
+ async regenerateCrl(ca) {
769
+ await this.#withConfig(ca, caAltName(ca), (configFile, options) => this.#regenerateCrlWith(ca, configFile, options));
770
+ }
771
+ async signSubordinateCsr(ca, csrFile, certFile, validityDays) {
772
+ await this.#withConfig(ca, caAltName(ca), async (configFile, options) => {
773
+ const caRootDir = options.cwd;
774
+ const passin = await this.#passin(ca);
775
+ await execute_openssl(
776
+ [
777
+ "x509",
778
+ "-sha256",
779
+ "-req",
780
+ "-days",
781
+ String(validityDays),
782
+ "-extensions",
783
+ "v3_ca",
784
+ "-extfile",
785
+ n2(configFile),
786
+ "-in",
787
+ n2(csrFile),
788
+ "-CA",
789
+ n2(ca.caCertificate),
790
+ "-CAkey",
791
+ n2(path3.join(caRootDir, "private/cakey.pem")),
792
+ "-CAserial",
793
+ n2(path3.join(caRootDir, "serial")),
794
+ "-out",
795
+ n2(certFile),
796
+ ...passin.args
797
+ ],
798
+ { ...options, env: passin.env }
2049
799
  );
2050
- } else {
2051
- await fs7.promises.writeFile(cacertWithCRL, fs7.readFileSync(this.caCertificate));
2052
- }
800
+ });
2053
801
  }
2054
- /**
2055
- * Append the CA certificate to a signed certificate file,
2056
- * creating a PEM certificate chain.
2057
- *
2058
- * @param certificate - path to the certificate file to extend
2059
- */
2060
- async constructCertificateChain(certificate) {
2061
- assert7(fs7.existsSync(certificate));
2062
- assert7(fs7.existsSync(this.caCertificate));
2063
- debugLog(chalk5.yellow(" certificate file :"), chalk5.cyan(certificate));
2064
- let chain = await fs7.promises.readFile(certificate, "utf8");
2065
- chain += await fs7.promises.readFile(this.caCertificate, "utf8");
2066
- if (fs7.existsSync(this.issuerCertificateChain)) {
2067
- chain += await fs7.promises.readFile(this.issuerCertificateChain, "utf8");
2068
- }
2069
- await fs7.promises.writeFile(certificate, chain);
802
+ async signEndEntityCsr(ca, certificate, csr, params1, sanOverride) {
803
+ await this.#withConfig(ca, buildSubjectAltNameString(sanOverride), async (configFile, options) => {
804
+ displaySubtitle("- then we ask the authority to sign the certificate signing request");
805
+ const passin = await this.#passin(ca);
806
+ await execute_openssl(
807
+ [
808
+ "ca",
809
+ "-config",
810
+ configFile,
811
+ "-startdate",
812
+ x509Date(params1.startDate),
813
+ "-enddate",
814
+ x509Date(params1.endDate),
815
+ "-batch",
816
+ "-out",
817
+ n2(certificate),
818
+ "-in",
819
+ n2(csr),
820
+ ...passin.args
821
+ ],
822
+ { ...options, env: passin.env }
823
+ );
824
+ displaySubtitle("- dump the certificate for a check");
825
+ await execute_openssl(["x509", "-in", n2(certificate), "-dates", "-fingerprint", "-purpose", "-noout"], options);
826
+ });
2070
827
  }
2071
- /**
2072
- * Create a self-signed certificate using OpenSSL.
2073
- *
2074
- * @param certificateFile - output path for the signed certificate
2075
- * @param privateKey - path to the private key file
2076
- * @param params - certificate parameters (subject, validity, SANs)
2077
- */
2078
- async createSelfSignedCertificate(certificateFile, privateKey, params) {
2079
- assert7(typeof privateKey === "string");
2080
- assert7(fs7.existsSync(privateKey));
2081
- if (!certificateFileExist(certificateFile)) {
2082
- return;
2083
- }
2084
- adjustDate(params);
2085
- adjustApplicationUri(params);
2086
- processAltNames(params);
2087
- const csrFile = `${certificateFile}_csr`;
2088
- assert7(csrFile);
2089
- const configFile = generateStaticConfig(this.configFile, { cwd: this.rootDir });
2090
- const options = {
2091
- cwd: this.rootDir,
2092
- openssl_conf: makePath(configFile)
2093
- };
2094
- const subject = params.subject ? new Subject2(params.subject).toString() : "";
2095
- const subjectOptions = subject && subject.length > 1 ? ["-subj", subject] : [];
2096
- displaySubtitle("- the certificate signing request");
2097
- await execute_openssl(
2098
- ["req", "-new", "-sha256", "-text", ...subjectOptions, "-batch", "-key", n4(privateKey), "-out", n4(csrFile)],
2099
- options
2100
- );
2101
- displaySubtitle("- creating the self-signed certificate");
2102
- await execute_openssl(
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
- ],
2118
- options
2119
- );
2120
- displaySubtitle("- dump the certificate for a check");
2121
- await execute_openssl(["x509", "-in", n4(certificateFile), "-dates", "-fingerprint", "-purpose", "-noout"], {});
2122
- displaySubtitle("- verify self-signed certificate");
2123
- await execute_openssl_no_failure(["verify", "-verbose", "-CAfile", n4(certificateFile), n4(certificateFile)], options);
2124
- await fs7.promises.unlink(csrFile);
828
+ async revoke(ca, certificate, reason) {
829
+ setEnv("RANDFILE", path3.join(ca.rootDir, "random.rnd"));
830
+ await this.#withConfig(ca, caAltName(ca), async (configFile, options) => {
831
+ displaySubtitle("Revoke certificate");
832
+ const passin = await this.#passin(ca);
833
+ await execute_openssl_no_failure(
834
+ ["ca", "-verbose", "-config", n2(configFile), "-revoke", certificate, "-crl_reason", reason, ...passin.args],
835
+ { ...options, env: passin.env }
836
+ );
837
+ await this.#regenerateCrlWith(ca, configFile, options);
838
+ displaySubtitle("Verify that certificate is revoked");
839
+ await execute_openssl_no_failure(
840
+ [
841
+ "verify",
842
+ "-verbose",
843
+ "-CRLfile",
844
+ n2(ca.revocationList),
845
+ "-CAfile",
846
+ n2(ca.caCertificate),
847
+ "-crl_check",
848
+ n2(certificate)
849
+ ],
850
+ options
851
+ );
852
+ displaySubtitle("Produce CRL in DER form ");
853
+ await execute_openssl(
854
+ ["crl", "-in", n2(ca.revocationList), "-out", "crl/revocation_list.der", "-outform", "der"],
855
+ options
856
+ );
857
+ displaySubtitle("Produce CRL in PEM form ");
858
+ await execute_openssl(
859
+ ["crl", "-in", n2(ca.revocationList), "-out", "crl/revocation_list.pem", "-outform", "pem", "-text"],
860
+ options
861
+ );
862
+ });
2125
863
  }
2126
- /**
2127
- * Revoke a certificate and regenerate the CRL.
2128
- *
2129
- * @param certificate - path to the certificate file to revoke
2130
- * @param params - revocation parameters
2131
- * @param params.reason - CRL reason code
2132
- * (default `"keyCompromise"`)
2133
- */
2134
- async revokeCertificate(certificate, params) {
2135
- const crlReasons = [
2136
- "unspecified",
2137
- "keyCompromise",
2138
- "CACompromise",
2139
- "affiliationChanged",
2140
- "superseded",
2141
- "cessationOfOperation",
2142
- "certificateHold",
2143
- "removeFromCRL"
2144
- ];
2145
- const configFile = generateStaticConfig("conf/caconfig.cnf", { cwd: this.rootDir });
864
+ async createSelfSignedCertificate(ca, certificateFile, privateKeyFile, params) {
865
+ const envOverrides = caConfigEnvOverrides(ca, buildSubjectAltNameString(params));
866
+ const configFile = generateStaticConfig(ca.configFile, { cwd: ca.rootDir }, envOverrides);
2146
867
  const options = {
2147
- cwd: this.rootDir,
868
+ cwd: ca.rootDir,
2148
869
  openssl_conf: makePath(configFile)
2149
870
  };
2150
- setEnv("ALTNAME", "");
2151
- const randomFile = path5.join(this.rootDir, "random.rnd");
2152
- setEnv("RANDFILE", randomFile);
2153
- const configOption = ["-config", n4(configFile)];
2154
- const reason = params.reason || "keyCompromise";
2155
- assert7(crlReasons.indexOf(reason) >= 0);
2156
- displayTitle(`Revoking certificate ${certificate}`);
2157
- displaySubtitle("Revoke certificate");
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);
2164
- displaySubtitle("Verify that certificate is revoked");
2165
- await execute_openssl_no_failure(
2166
- [
2167
- "verify",
2168
- "-verbose",
2169
- "-CRLfile",
2170
- n4(this.revocationList),
2171
- "-CAfile",
2172
- n4(this.caCertificate),
2173
- "-crl_check",
2174
- n4(certificate)
2175
- ],
2176
- options
2177
- );
2178
- displaySubtitle("Produce CRL in DER form ");
2179
- await execute_openssl(
2180
- ["crl", "-in", n4(this.revocationList), "-out", "crl/revocation_list.der", "-outform", "der"],
2181
- options
2182
- );
2183
- displaySubtitle("Produce CRL in PEM form ");
2184
- await execute_openssl(
2185
- ["crl", "-in", n4(this.revocationList), "-out", "crl/revocation_list.pem", "-outform", "pem", "-text"],
2186
- options
2187
- );
871
+ try {
872
+ const subject = params.subject ? new Subject2(params.subject).toString() : "";
873
+ const subjectOptions = subject && subject.length > 1 ? ["-subj", subject] : [];
874
+ const csrFile = `${certificateFile}_csr`;
875
+ const passin = await this.#passin(ca);
876
+ displaySubtitle("- the certificate signing request");
877
+ await execute_openssl(
878
+ [
879
+ "req",
880
+ "-new",
881
+ "-sha256",
882
+ ...subjectOptions,
883
+ "-batch",
884
+ "-key",
885
+ n2(privateKeyFile),
886
+ "-out",
887
+ n2(csrFile),
888
+ ...passin.args
889
+ ],
890
+ { ...options, env: passin.env }
891
+ );
892
+ displaySubtitle("- creating the self-signed certificate");
893
+ await execute_openssl(
894
+ [
895
+ "ca",
896
+ "-selfsign",
897
+ "-keyfile",
898
+ n2(privateKeyFile),
899
+ "-startdate",
900
+ x509Date(params.startDate),
901
+ "-enddate",
902
+ x509Date(params.endDate),
903
+ "-batch",
904
+ "-out",
905
+ n2(certificateFile),
906
+ "-in",
907
+ n2(csrFile),
908
+ ...passin.args
909
+ ],
910
+ { ...options, env: passin.env }
911
+ );
912
+ displaySubtitle("- dump the certificate for a check");
913
+ await execute_openssl(["x509", "-in", n2(certificateFile), "-dates", "-fingerprint", "-purpose", "-noout"], {});
914
+ displaySubtitle("- verify self-signed certificate");
915
+ await execute_openssl_no_failure(["verify", "-verbose", "-CAfile", n2(certificateFile), n2(certificateFile)], options);
916
+ await fs4.promises.unlink(csrFile);
917
+ } finally {
918
+ await cleanupStaticConfig(configFile, { cwd: ca.rootDir });
919
+ }
920
+ }
921
+ };
922
+
923
+ // packages/node-opcua-pki/lib/ca/certificate_authority.ts
924
+ var CertificateAuthority = class extends CertificateAuthorityCore {
925
+ constructor(options) {
926
+ if (options.signer && options.backend === "openssl") {
927
+ throw new Error(
928
+ "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."
929
+ );
930
+ }
931
+ const backend = options.signer || options.backend === "native" ? new NativeCaBackend() : new OpenSslCaBackend();
932
+ super({ ...options, backend });
2188
933
  }
2189
934
  /**
2190
- * Sign a Certificate Signing Request (CSR) with this CA.
935
+ * @internal `-passin env:` argv + env for an openssl call that loads
936
+ * this CA's key (always emitted, empty when none).
2191
937
  *
2192
- * The signed certificate is written to `certificate`, and the
2193
- * CA certificate chain plus CRL are appended to form a
2194
- * complete certificate chain.
2195
- *
2196
- * @param certificate - output path for the signed certificate
2197
- * @param certificateSigningRequestFilename - path to the CSR
2198
- * @param params1 - signing parameters (validity, dates, SANs)
2199
- * @returns the path to the signed certificate
938
+ * The openssl backend builds its own now; this remains so that external
939
+ * code calling it keeps working, and lives here rather than on the core
940
+ * because the flag means nothing to a backend that spawns nothing.
2200
941
  */
2201
- async signCertificateRequest(certificate, certificateSigningRequestFilename, params1) {
2202
- await ensure_openssl_installed();
2203
- assert7(fs7.existsSync(certificateSigningRequestFilename));
2204
- if (!certificateFileExist(certificate)) {
2205
- return "";
2206
- }
2207
- adjustDate(params1);
2208
- adjustApplicationUri(params1);
2209
- processAltNames(params1);
2210
- const options = { cwd: this.rootDir };
2211
- const csr = await readCertificateSigningRequest(certificateSigningRequestFilename);
2212
- const csrInfo = exploreCertificateSigningRequest(csr);
2213
- const applicationUri = csrInfo.extensionRequest.subjectAltName.uniformResourceIdentifier ? csrInfo.extensionRequest.subjectAltName.uniformResourceIdentifier[0] : void 0;
2214
- if (typeof applicationUri !== "string") {
2215
- throw new Error("Cannot find applicationUri in CSR");
2216
- }
2217
- const dns = csrInfo.extensionRequest.subjectAltName.dNSName || [];
2218
- let ip = csrInfo.extensionRequest.subjectAltName.iPAddress || [];
2219
- ip = ip.map(octetStringToIpAddress);
2220
- const params = {
2221
- applicationUri,
2222
- dns,
2223
- ip
2224
- };
2225
- processAltNames(params);
2226
- this._wireRevocationEnvVars();
2227
- const configFile = generateStaticConfig("conf/caconfig.cnf", options);
2228
- displaySubtitle("- then we ask the authority to sign the certificate signing request");
2229
- const passin = await this._opensslPassin();
2230
- await execute_openssl(
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 }
2247
- );
2248
- displaySubtitle("- dump the certificate for a check");
2249
- await execute_openssl(["x509", "-in", n4(certificate), "-dates", "-fingerprint", "-purpose", "-noout"], options);
2250
- displaySubtitle("- construct CA certificate with CRL");
2251
- await this.constructCACertificateWithCRL();
2252
- displaySubtitle("- construct certificate chain");
2253
- await this.constructCertificateChain(certificate);
2254
- displaySubtitle("- verify certificate against the root CA");
2255
- await this.verifyCertificate(certificate);
2256
- return certificate;
942
+ async _opensslPassin() {
943
+ return passinArg(await this._privateKeyPassphrase());
2257
944
  }
2258
945
  /**
2259
- * Verify a certificate against this CA.
2260
- *
2261
- * @param certificate - path to the certificate file to verify
946
+ * @internal
947
+ * Legacy shim: publish the `CDP_URL` / `AIA_VALUE` config substitution
948
+ * values to the shared env registry, or unset them so the matching
949
+ * `{{#KEY}}...{{/KEY}}` blocks are stripped. Nothing in this package
950
+ * reads the registry any more - every openssl config render receives
951
+ * these values explicitly, from the same {@link caConfigEnvOverrides}
952
+ * builder this delegates to, so the two cannot drift - kept only for
953
+ * external code that renders openssl config templates against the
954
+ * registry directly.
955
+ *
956
+ * It lives on this class rather than the core because it is meaningful
957
+ * only to the openssl backend.
2262
958
  */
2263
- async verifyCertificate(certificate) {
2264
- const isImplemented = false;
2265
- if (isImplemented) {
2266
- const options = { cwd: this.rootDir };
2267
- const configFile = generateStaticConfig("conf/caconfig.cnf", options);
2268
- setEnv("OPENSSL_CONF", makePath(configFile));
2269
- await execute_openssl_no_failure(
2270
- ["verify", "-verbose", "-CAfile", n4(this.caCertificateWithCrl), n4(certificate)],
2271
- options
2272
- );
959
+ _wireRevocationEnvVars() {
960
+ const overrides = caConfigEnvOverrides(this);
961
+ if (overrides.CDP_URL) {
962
+ setEnv("CDP_URL", overrides.CDP_URL);
963
+ } else {
964
+ unsetEnv("CDP_URL");
965
+ }
966
+ if (overrides.AIA_VALUE) {
967
+ setEnv("AIA_VALUE", overrides.AIA_VALUE);
968
+ } else {
969
+ unsetEnv("AIA_VALUE");
2273
970
  }
2274
971
  }
2275
972
  };
2276
973
 
2277
974
  // packages/node-opcua-pki/lib/pki/certificate_manager.ts
2278
975
  import { EventEmitter } from "events";
2279
- import fs10 from "fs";
2280
- import path6 from "path";
976
+ import fs7 from "fs";
977
+ import path4 from "path";
2281
978
  import { drainPendingLocks, withLock } from "@ster5/global-mutex";
2282
- import chalk6 from "chalk";
979
+ import chalk3 from "chalk";
2283
980
  import chokidar from "chokidar";
2284
981
  import {
2285
- exploreCertificate as exploreCertificate2,
982
+ caSignerFromKeyOperations,
983
+ exploreCertificate,
2286
984
  exploreCertificateInfo,
2287
985
  exploreCertificateRevocationList,
2288
- generatePrivateKeyFile as generatePrivateKeyFile2,
986
+ generatePrivateKeyFile,
987
+ keyOperationsFromPrivateKey,
2289
988
  makeSHA1Thumbprint,
989
+ PrivateKeyUnavailableError,
2290
990
  readCertificateChain,
2291
991
  readCertificateChainAsync,
2292
992
  readCertificateRevocationList,
2293
- readPrivateKey as readPrivateKey2,
993
+ readPrivateKey,
2294
994
  split_der,
2295
- toPem as toPem2,
995
+ toPem,
2296
996
  verifyCertificateSignature,
2297
- writePrivateKeyFile as writePrivateKeyFile2
997
+ writePrivateKeyFile
2298
998
  } from "node-opcua-crypto";
2299
999
 
2300
1000
  // packages/node-opcua-pki/lib/toolbox/without_openssl/create_certificate_signing_request.ts
2301
- import assert8 from "assert";
2302
- import fs8 from "fs";
1001
+ import assert3 from "assert";
1002
+ import fs5 from "fs";
2303
1003
  import { coercePrivateKeyPem, createCertificateSigningRequest, pemToPrivateKey, Subject as Subject3 } from "node-opcua-crypto";
2304
1004
  async function createCertificateSigningRequestAsync(certificateSigningRequestFilename, params) {
2305
- assert8(params);
2306
- assert8(params.rootDir);
2307
- assert8(params.configFile);
2308
- assert8(params.privateKey);
1005
+ assert3(params);
1006
+ assert3(params.rootDir);
1007
+ assert3(params.configFile);
1008
+ assert3(params.privateKey);
2309
1009
  if (typeof params.privateKey === "string") {
2310
- assert8(fs8.existsSync(params.privateKey), `Private key must exist${params.privateKey}`);
1010
+ assert3(fs5.existsSync(params.privateKey), `Private key must exist${params.privateKey}`);
2311
1011
  }
2312
- assert8(fs8.existsSync(params.rootDir), "RootDir key must exist");
2313
- assert8(typeof certificateSigningRequestFilename === "string");
1012
+ assert3(fs5.existsSync(params.rootDir), "RootDir key must exist");
1013
+ assert3(typeof certificateSigningRequestFilename === "string");
2314
1014
  const subject = params.subject ? new Subject3(params.subject).toString() : void 0;
2315
1015
  displaySubtitle("- Creating a Certificate Signing Request with subtile");
2316
- const privateKeyPem = typeof params.privateKey === "string" ? await fs8.promises.readFile(params.privateKey, "utf-8") : coercePrivateKeyPem(params.privateKey);
2317
- const privateKey = await pemToPrivateKey(privateKeyPem);
1016
+ const privateKey = isOpaqueSigner(params.privateKey) ? params.privateKey : await pemToPrivateKey(
1017
+ typeof params.privateKey === "string" ? await fs5.promises.readFile(params.privateKey, "utf-8") : coercePrivateKeyPem(params.privateKey)
1018
+ );
2318
1019
  const { csr } = await createCertificateSigningRequest({
2319
1020
  privateKey,
2320
1021
  dns: params.dns,
@@ -2323,42 +1024,45 @@ async function createCertificateSigningRequestAsync(certificateSigningRequestFil
2323
1024
  applicationUri: params.applicationUri,
2324
1025
  purpose: params.purpose
2325
1026
  });
2326
- await fs8.promises.writeFile(certificateSigningRequestFilename, csr, "utf-8");
2327
- display(`- privateKey ${typeof params.privateKey === "string" ? params.privateKey : "<in-memory>"}`);
1027
+ await fs5.promises.writeFile(certificateSigningRequestFilename, csr, "utf-8");
1028
+ display(
1029
+ `- privateKey ${typeof params.privateKey === "string" ? params.privateKey : isOpaqueSigner(params.privateKey) ? "<opaque-signer>" : "<in-memory>"}`
1030
+ );
2328
1031
  display(`- certificateSigningRequestFilename ${certificateSigningRequestFilename}`);
2329
1032
  }
2330
1033
 
2331
1034
  // packages/node-opcua-pki/lib/toolbox/without_openssl/create_self_signed_certificate.ts
2332
- import assert9 from "assert";
2333
- import fs9 from "fs";
1035
+ import assert4 from "assert";
1036
+ import fs6 from "fs";
2334
1037
  import {
2335
- CertificatePurpose as CertificatePurpose2,
1038
+ CertificatePurpose,
2336
1039
  coercePrivateKeyPem as coercePrivateKeyPem2,
2337
1040
  createSelfSignedCertificate as createSelfSignedCertificate1,
2338
1041
  pemToPrivateKey as pemToPrivateKey2,
2339
1042
  Subject as Subject4
2340
1043
  } from "node-opcua-crypto";
2341
1044
  async function createSelfSignedCertificateAsync(certificate, params) {
2342
- params.purpose = params.purpose || CertificatePurpose2.ForApplication;
2343
- assert9(params.purpose, "Please provide a Certificate Purpose");
2344
- assert9(fs9.existsSync(params.configFile));
2345
- assert9(fs9.existsSync(params.rootDir));
1045
+ params.purpose = params.purpose || CertificatePurpose.ForApplication;
1046
+ assert4(params.purpose, "Please provide a Certificate Purpose");
1047
+ assert4(fs6.existsSync(params.configFile));
1048
+ assert4(fs6.existsSync(params.rootDir));
2346
1049
  if (typeof params.privateKey === "string") {
2347
- assert9(fs9.existsSync(params.privateKey));
1050
+ assert4(fs6.existsSync(params.privateKey));
2348
1051
  }
2349
1052
  if (!params.subject) {
2350
1053
  throw Error("Missing subject");
2351
1054
  }
2352
- assert9(typeof params.applicationUri === "string");
2353
- assert9(Array.isArray(params.dns));
1055
+ assert4(typeof params.applicationUri === "string");
1056
+ assert4(Array.isArray(params.dns));
2354
1057
  adjustDate(params);
2355
- assert9(Object.prototype.hasOwnProperty.call(params, "validity"));
1058
+ assert4(Object.prototype.hasOwnProperty.call(params, "validity"));
2356
1059
  let subject = new Subject4(params.subject);
2357
1060
  subject = subject.toString();
2358
1061
  const purpose = params.purpose;
2359
1062
  displayTitle("Generate a certificate request");
2360
- const privateKeyPem = typeof params.privateKey === "string" ? await fs9.promises.readFile(params.privateKey, "utf-8") : coercePrivateKeyPem2(params.privateKey);
2361
- const privateKey = await pemToPrivateKey2(privateKeyPem);
1063
+ const privateKey = isOpaqueSigner(params.privateKey) ? params.privateKey : await pemToPrivateKey2(
1064
+ typeof params.privateKey === "string" ? await fs6.promises.readFile(params.privateKey, "utf-8") : coercePrivateKeyPem2(params.privateKey)
1065
+ );
2362
1066
  const { cert } = await createSelfSignedCertificate1({
2363
1067
  privateKey,
2364
1068
  notBefore: params.startDate,
@@ -2370,18 +1074,22 @@ async function createSelfSignedCertificateAsync(certificate, params) {
2370
1074
  applicationUri: params.applicationUri,
2371
1075
  purpose
2372
1076
  });
2373
- await fs9.promises.writeFile(certificate, cert, "utf-8");
1077
+ await fs6.promises.writeFile(certificate, cert, "utf-8");
2374
1078
  }
2375
1079
  async function createSelfSignedCertificate(certificate, params) {
2376
1080
  await createSelfSignedCertificateAsync(certificate, params);
2377
1081
  }
2378
1082
 
1083
+ // packages/node-opcua-pki/lib/pki/templates/simple_config_template.cnf.ts
1084
+ var config = '##################################################################################################\n## SIMPLE OPENSSL CONFIG FILE FOR SELF-SIGNED CERTIFICATE GENERATION\n################################################################################################################\n\ndistinguished_name = req_distinguished_name\ndefault_md = sha1\n\ndefault_md = sha256 # The default digest algorithm\n\n[ v3_ca ]\nsubjectKeyIdentifier = hash\nauthorityKeyIdentifier = keyid:always,issuer:always\n\n# authorityKeyIdentifier = keyid\nbasicConstraints = CA:TRUE\nkeyUsage = critical, cRLSign, keyCertSign\nnsComment = "Self-signed Certificate for CA generated by Node-OPCUA Certificate utility"\n#nsCertType = sslCA, emailCA\n#subjectAltName = email:copy\n#issuerAltName = issuer:copy\n#obj = DER:02:03\n# crlDistributionPoints = @crl_info\n# [ crl_info ]\n# URI.0 = http://localhost:8900/crl.pem\nsubjectAltName = $ENV::ALTNAME\n\n[ req ]\ndays = 390\nreq_extensions = v3_req\nx509_extensions = v3_ca\n\n[v3_req]\nbasicConstraints = CA:false\nkeyUsage = critical, nonRepudiation, digitalSignature, keyEncipherment, dataEncipherment\nsubjectAltName = $ENV::ALTNAME\n\n[ v3_ca_signed]\nsubjectKeyIdentifier = hash\nauthorityKeyIdentifier = keyid,issuer\nbasicConstraints = critical, CA:FALSE\nkeyUsage = nonRepudiation, digitalSignature, keyEncipherment, dataEncipherment\nextendedKeyUsage = clientAuth,serverAuth \nnsComment = "certificate generated by Node-OPCUA Certificate utility and signed by a CA"\nsubjectAltName = $ENV::ALTNAME\n[ v3_selfsigned]\nsubjectKeyIdentifier = hash\nauthorityKeyIdentifier = keyid,issuer\nbasicConstraints = critical, CA:FALSE\nkeyUsage = nonRepudiation, digitalSignature, keyEncipherment, dataEncipherment\nextendedKeyUsage = clientAuth,serverAuth \nnsComment = "Self-signed certificate generated by Node-OPCUA Certificate utility"\nsubjectAltName = $ENV::ALTNAME\n[ req_distinguished_name ]\ncountryName = Country Name (2 letter code)\ncountryName_default = FR\ncountryName_min = 2\ncountryName_max = 2\n# stateOrProvinceName = State or Province Name (full name)\n# stateOrProvinceName_default = Ile de France\n# localityName = Locality Name (city, district)\n# localityName_default = Paris\norganizationName = Organization Name (company)\norganizationName_default = NodeOPCUA\n# organizationalUnitName = Organizational Unit Name (department, division)\n# organizationalUnitName_default = R&D\ncommonName = Common Name (hostname, FQDN, IP, or your name)\ncommonName_max = 256\ncommonName_default = NodeOPCUA\n# emailAddress = Email Address\n# emailAddress_max = 40\n# emailAddress_default = node-opcua (at) node-opcua (dot) com\nsubjectAltName = $ENV::ALTNAME';
1085
+ var simple_config_template_cnf_default = config;
1086
+
2379
1087
  // packages/node-opcua-pki/lib/pki/certificate_manager.ts
2380
- var configurationFileSimpleTemplate2 = simple_config_template_cnf_default;
2381
- var fsWriteFile = fs10.promises.writeFile;
1088
+ var configurationFileSimpleTemplate = simple_config_template_cnf_default;
1089
+ var fsWriteFile = fs7.promises.writeFile;
2382
1090
  function getOrComputeInfo(entry) {
2383
1091
  if (!entry.info) {
2384
- entry.info = exploreCertificate2(entry.certificate);
1092
+ entry.info = exploreCertificate(entry.certificate);
2385
1093
  }
2386
1094
  return entry.info;
2387
1095
  }
@@ -2425,7 +1133,7 @@ function buildIdealCertificateName(certificate) {
2425
1133
  const chain = coerceCertificateChain(certificate);
2426
1134
  const fingerprint = makeFingerprint(chain);
2427
1135
  try {
2428
- const commonName = exploreCertificate2(chain[0]).tbsCertificate.subject.commonName || "";
1136
+ const commonName = exploreCertificate(chain[0]).tbsCertificate.subject.commonName || "";
2429
1137
  const sanitizedCommonName = commonName.replace(forbiddenChars, "_");
2430
1138
  return `${sanitizedCommonName}[${fingerprint}]`;
2431
1139
  } catch (_err) {
@@ -2442,7 +1150,7 @@ function isSelfSigned2(info) {
2442
1150
  return info.tbsCertificate.extensions?.subjectKeyIdentifier === info.tbsCertificate.extensions?.authorityKeyIdentifier?.keyIdentifier;
2443
1151
  }
2444
1152
  function isSelfSigned3(certificate) {
2445
- const info = exploreCertificate2(certificate);
1153
+ const info = exploreCertificate(certificate);
2446
1154
  return isSelfSigned2(info);
2447
1155
  }
2448
1156
  function _isIssuerInfo(info) {
@@ -2458,7 +1166,7 @@ function _isIssuerInfo(info) {
2458
1166
  }
2459
1167
  function isIssuer(certificate) {
2460
1168
  try {
2461
- const info = exploreCertificate2(certificate);
1169
+ const info = exploreCertificate(certificate);
2462
1170
  return _isIssuerInfo(info);
2463
1171
  } catch (_err) {
2464
1172
  return false;
@@ -2466,7 +1174,7 @@ function isIssuer(certificate) {
2466
1174
  }
2467
1175
  function isIntermediateIssuer(certificate) {
2468
1176
  try {
2469
- const info = exploreCertificate2(certificate);
1177
+ const info = exploreCertificate(certificate);
2470
1178
  if (!_isIssuerInfo(info)) {
2471
1179
  return false;
2472
1180
  }
@@ -2477,7 +1185,7 @@ function isIntermediateIssuer(certificate) {
2477
1185
  }
2478
1186
  function isRootIssuer(certificate) {
2479
1187
  try {
2480
- const info = exploreCertificate2(certificate);
1188
+ const info = exploreCertificate(certificate);
2481
1189
  if (!_isIssuerInfo(info)) {
2482
1190
  return false;
2483
1191
  }
@@ -2492,7 +1200,7 @@ function findIssuerCertificateInChain(certificate, chain) {
2492
1200
  if (!firstCertificate) {
2493
1201
  return null;
2494
1202
  }
2495
- const certInfo = exploreCertificate2(firstCertificate);
1203
+ const certInfo = exploreCertificate(firstCertificate);
2496
1204
  if (isSelfSigned2(certInfo)) {
2497
1205
  return firstCertificate;
2498
1206
  }
@@ -2503,7 +1211,7 @@ function findIssuerCertificateInChain(certificate, chain) {
2503
1211
  }
2504
1212
  const coercedChain = coerceCertificateChain(chain);
2505
1213
  const potentialIssuers = coercedChain.filter((c) => {
2506
- const info = exploreCertificate2(c);
1214
+ const info = exploreCertificate(c);
2507
1215
  return info.tbsCertificate.extensions && info.tbsCertificate.extensions.subjectKeyIdentifier === wantedIssuerKey;
2508
1216
  });
2509
1217
  if (potentialIssuers.length === 1) {
@@ -2538,7 +1246,31 @@ var CertificateManager = class _CertificateManager extends EventEmitter {
2538
1246
  // even if the consumer forgets to call dispose().
2539
1247
  static #activeInstances = /* @__PURE__ */ new Set();
2540
1248
  static #cleanupInstalled = false;
2541
- static #installProcessCleanup() {
1249
+ static #exitHandler;
1250
+ /**
1251
+ * Install a best-effort `exit` hook that closes any watcher
1252
+ * still open when the process terminates.
1253
+ *
1254
+ * **This library never terminates the host process.** No
1255
+ * SIGINT/SIGTERM handler is installed: deciding how (and
1256
+ * whether) to shut down on a signal is the application's
1257
+ * responsibility, and a listener registered here would both
1258
+ * pre-empt the application's own graceful shutdown and
1259
+ * silently suppress Node's default signal behaviour.
1260
+ *
1261
+ * Nothing here is load-bearing for process exit. The native
1262
+ * `fs.watch` handles are `unref()`'d when the watchers are
1263
+ * created (see `#readCertificates`), so an undisposed
1264
+ * CertificateManager never keeps the event loop alive. This
1265
+ * hook is only tidiness on the way out.
1266
+ *
1267
+ * `exit` rather than `beforeExit`: `beforeExit` fires when
1268
+ * the loop merely drains and the loop can subsequently be
1269
+ * resurrected, which would leave a still-in-use instance
1270
+ * marked Disposed. `exit` is terminal, synchronous-only and
1271
+ * cannot alter the exit code.
1272
+ */
1273
+ static #installExitCleanup() {
2542
1274
  if (_CertificateManager.#cleanupInstalled) return;
2543
1275
  _CertificateManager.#cleanupInstalled = true;
2544
1276
  const closeDanglingWatchers = () => {
@@ -2554,13 +1286,22 @@ var CertificateManager = class _CertificateManager extends EventEmitter {
2554
1286
  }
2555
1287
  _CertificateManager.#activeInstances.clear();
2556
1288
  };
2557
- process.on("beforeExit", closeDanglingWatchers);
2558
- for (const signal of ["SIGINT", "SIGTERM"]) {
2559
- process.once(signal, () => {
2560
- closeDanglingWatchers();
2561
- process.exit();
2562
- });
1289
+ _CertificateManager.#exitHandler = closeDanglingWatchers;
1290
+ process.on("exit", closeDanglingWatchers);
1291
+ }
1292
+ /**
1293
+ * Remove the `exit` hook once the last instance is disposed,
1294
+ * so a library that is initialized and disposed repeatedly
1295
+ * does not accumulate process listeners. A later
1296
+ * `initialize()` re-arms it.
1297
+ */
1298
+ static #uninstallExitCleanupIfIdle() {
1299
+ if (_CertificateManager.#activeInstances.size > 0) return;
1300
+ if (_CertificateManager.#exitHandler) {
1301
+ process.removeListener("exit", _CertificateManager.#exitHandler);
1302
+ _CertificateManager.#exitHandler = void 0;
2563
1303
  }
1304
+ _CertificateManager.#cleanupInstalled = false;
2564
1305
  }
2565
1306
  /**
2566
1307
  * Dispose **all** active CertificateManager instances,
@@ -2626,6 +1367,9 @@ var CertificateManager = class _CertificateManager extends EventEmitter {
2626
1367
  #disableFileWatchers;
2627
1368
  #privateKeyPassphrase;
2628
1369
  #privateKeyProvider;
1370
+ #keyOperations;
1371
+ /** The stable lazy wrap handed out by {@link getKeyOperations} for non-opaque configurations. */
1372
+ #lazyLocalKeyOperations;
2629
1373
  /**
2630
1374
  * The on-disk key, decrypted once and kept for the instance's lifetime,
2631
1375
  * so the passphrase (or its resolver function) is consulted at most
@@ -2669,14 +1413,25 @@ var CertificateManager = class _CertificateManager extends EventEmitter {
2669
1413
  this.#disableFileWatchers = options.disableFileWatchers ?? process.env.OPCUA_PKI_DISABLE_FILE_WATCHERS === "true";
2670
1414
  this.#privateKeyPassphrase = options.privateKeyPassphrase;
2671
1415
  this.#privateKeyProvider = options.privateKeyProvider;
1416
+ this.#keyOperations = options.keyOperations;
1417
+ if (this.#keyOperations && this.#privateKeyProvider) {
1418
+ throw new Error(
1419
+ "CertificateManager: 'keyOperations' and 'privateKeyProvider' are mutually exclusive: one hides the key, the other sources its material"
1420
+ );
1421
+ }
1422
+ if (this.#keyOperations && this.#privateKeyPassphrase) {
1423
+ throw new Error(
1424
+ "CertificateManager: 'privateKeyPassphrase' is meaningless with 'keyOperations': there is no key material for a passphrase to protect"
1425
+ );
1426
+ }
2672
1427
  mkdirRecursiveSync(options.location);
2673
- if (!fs10.existsSync(this.#location)) {
1428
+ if (!fs7.existsSync(this.#location)) {
2674
1429
  throw new Error(`CertificateManager cannot access location ${this.#location}`);
2675
1430
  }
2676
1431
  }
2677
1432
  /** Path to the OpenSSL configuration file. */
2678
1433
  get configFile() {
2679
- return path6.join(this.rootDir, "own/openssl.cnf");
1434
+ return path4.join(this.rootDir, "own/openssl.cnf");
2680
1435
  }
2681
1436
  /** Root directory of the PKI store. */
2682
1437
  get rootDir() {
@@ -2692,7 +1447,7 @@ var CertificateManager = class _CertificateManager extends EventEmitter {
2692
1447
  * be no meaningful file in that case).
2693
1448
  */
2694
1449
  get privateKey() {
2695
- return path6.join(this.rootDir, "own/private/private_key.pem");
1450
+ return path4.join(this.rootDir, "own/private/private_key.pem");
2696
1451
  }
2697
1452
  /**
2698
1453
  * Resolve the private key: from `privateKeyProvider` if configured,
@@ -2710,6 +1465,11 @@ var CertificateManager = class _CertificateManager extends EventEmitter {
2710
1465
  * authority on what the current key is.
2711
1466
  */
2712
1467
  async getPrivateKey() {
1468
+ if (this.#keyOperations) {
1469
+ throw new PrivateKeyUnavailableError(
1470
+ "CertificateManager.getPrivateKey is not available when keyOperations is configured: the private key is held by the key-operations provider (HSM/KMS) and cannot be read \u2014 use getKeyOperations() instead"
1471
+ );
1472
+ }
2713
1473
  if (this.#privateKeyProvider) {
2714
1474
  return await this.#privateKeyProvider.getPrivateKey();
2715
1475
  }
@@ -2719,7 +1479,7 @@ var CertificateManager = class _CertificateManager extends EventEmitter {
2719
1479
  if (!this.#privateKeyPromise) {
2720
1480
  this.#privateKeyPromise = (async () => {
2721
1481
  const passphrase = await resolvePrivateKeyPassphrase(this.#privateKeyPassphrase);
2722
- return readPrivateKey2(this.privateKey, passphrase);
1482
+ return readPrivateKey(this.privateKey, passphrase);
2723
1483
  })().then(
2724
1484
  (key) => {
2725
1485
  this.#cachedPrivateKey = key;
@@ -2735,6 +1495,75 @@ var CertificateManager = class _CertificateManager extends EventEmitter {
2735
1495
  }
2736
1496
  /** In-flight first read of the on-disk key, so concurrent callers share one passphrase resolution. */
2737
1497
  #privateKeyPromise;
1498
+ /**
1499
+ * True when this manager's key is opaque — configured through
1500
+ * `keyOperations`, held by an HSM/KMS, never obtainable as material.
1501
+ * When true, {@link getPrivateKey} throws `PrivateKeyUnavailableError`
1502
+ * and {@link getKeyOperations} is the only way to use the key.
1503
+ */
1504
+ isPrivateKeyOpaque() {
1505
+ return !!this.#keyOperations;
1506
+ }
1507
+ /**
1508
+ * The key as an opaque {@link IKeyOperations} — the recommended way to
1509
+ * *use* the private key regardless of where it lives.
1510
+ *
1511
+ * Returns the configured `keyOperations` object when the key is opaque.
1512
+ * Otherwise returns a stable lazy wrap over {@link getPrivateKey}: its
1513
+ * methods resolve the key on first use (disk read, passphrase,
1514
+ * `privateKeyProvider` — all async), so the wrap offers no synchronous
1515
+ * fast path; callers that need one resolve the key themselves and build
1516
+ * a `LocalKeyOperations` over it. The wrap follows key rotation: a
1517
+ * `privateKeyProvider` that starts returning a different key gets a
1518
+ * fresh underlying `LocalKeyOperations`.
1519
+ */
1520
+ getKeyOperations() {
1521
+ if (this.#keyOperations) {
1522
+ return this.#keyOperations;
1523
+ }
1524
+ if (!this.#lazyLocalKeyOperations) {
1525
+ let cached;
1526
+ const resolve = async () => {
1527
+ const key = await this.getPrivateKey();
1528
+ if (!cached || cached.key !== key) {
1529
+ cached = { key, ops: keyOperationsFromPrivateKey(key) };
1530
+ }
1531
+ return cached.ops;
1532
+ };
1533
+ this.#lazyLocalKeyOperations = {
1534
+ sign: async (data, params) => (await resolve()).sign(data, params),
1535
+ decryptBlock: async (block, params) => (await resolve()).decryptBlock(block, params),
1536
+ getKeyMetadata: async () => (await resolve()).getKeyMetadata(),
1537
+ getPublicKey: async () => (await resolve()).getPublicKey()
1538
+ };
1539
+ }
1540
+ return this.#lazyLocalKeyOperations;
1541
+ }
1542
+ /**
1543
+ * Fail closed at `initialize()` time, not on the first certificate
1544
+ * operation, if the key cannot actually be used: wrong/missing
1545
+ * passphrase on an encrypted key, a broken `privateKeyProvider`, or an
1546
+ * unreachable `keyOperations` provider (probed via `getKeyMetadata`).
1547
+ */
1548
+ async #probePrivateKey() {
1549
+ if (this.#keyOperations) {
1550
+ await this.#keyOperations.getKeyMetadata();
1551
+ return;
1552
+ }
1553
+ await this.getPrivateKey();
1554
+ }
1555
+ /**
1556
+ * The key to hand to a certificate-issuance primitive: the raw
1557
+ * {@link PrivateKey} for a local configuration, or a {@link CaSigner}
1558
+ * adapted from `keyOperations` when the key is opaque — which requires
1559
+ * the provider to implement `getPublicKey` (the adapter says so if not).
1560
+ */
1561
+ async #resolveSigningKey() {
1562
+ if (this.#keyOperations) {
1563
+ return caSignerFromKeyOperations(this.#keyOperations);
1564
+ }
1565
+ return await this.getPrivateKey();
1566
+ }
2738
1567
  /**
2739
1568
  * Enable, disable, or rotate the passphrase protecting the on-disk
2740
1569
  * private key: decrypt with `oldPassphrase` (omit if the key is
@@ -2756,13 +1585,18 @@ var CertificateManager = class _CertificateManager extends EventEmitter {
2756
1585
  * disk file for this method to rewrite).
2757
1586
  */
2758
1587
  async reencryptPrivateKey(oldPassphrase, newPassphrase) {
1588
+ if (this.#keyOperations) {
1589
+ throw new PrivateKeyUnavailableError(
1590
+ "reencryptPrivateKey: not supported when keyOperations is configured \u2014 there is no key material to rewrite"
1591
+ );
1592
+ }
2759
1593
  if (this.#privateKeyProvider) {
2760
1594
  throw new Error("reencryptPrivateKey: not supported when a privateKeyProvider is configured");
2761
1595
  }
2762
1596
  const oldPass = await resolvePrivateKeyPassphrase(oldPassphrase);
2763
1597
  const newPass = await resolvePrivateKeyPassphrase(newPassphrase);
2764
1598
  await this.withLock2(async () => {
2765
- const privateKey = readPrivateKey2(this.privateKey, oldPass);
1599
+ const privateKey = readPrivateKey(this.privateKey, oldPass);
2766
1600
  await this.#rewritePrivateKeyFile(privateKey, newPass);
2767
1601
  });
2768
1602
  this.#cachedPrivateKey = void 0;
@@ -2778,15 +1612,15 @@ var CertificateManager = class _CertificateManager extends EventEmitter {
2778
1612
  async #rewritePrivateKeyFile(privateKey, passphrase) {
2779
1613
  const tmpFilename = `${this.privateKey}.${process.pid}-${Date.now()}.tmp`;
2780
1614
  try {
2781
- await writePrivateKeyFile2(tmpFilename, privateKey, { passphrase });
2782
- await fs10.promises.rename(tmpFilename, this.privateKey);
1615
+ await writePrivateKeyFile(tmpFilename, privateKey, { passphrase });
1616
+ await fs7.promises.rename(tmpFilename, this.privateKey);
2783
1617
  } finally {
2784
- await fs10.promises.rm(tmpFilename, { force: true });
1618
+ await fs7.promises.rm(tmpFilename, { force: true });
2785
1619
  }
2786
1620
  }
2787
1621
  /** Path to the OpenSSL random seed file. */
2788
1622
  get randomFile() {
2789
- return path6.join(this.rootDir, "./random.rnd");
1623
+ return path4.join(this.rootDir, "./random.rnd");
2790
1624
  }
2791
1625
  /**
2792
1626
  * Move a certificate to the rejected store.
@@ -2823,30 +1657,30 @@ var CertificateManager = class _CertificateManager extends EventEmitter {
2823
1657
  }
2824
1658
  /** Path to the rejected certificates folder. */
2825
1659
  get rejectedFolder() {
2826
- return path6.join(this.rootDir, "rejected");
1660
+ return path4.join(this.rootDir, "rejected");
2827
1661
  }
2828
1662
  /** Path to the trusted certificates folder. */
2829
1663
  get trustedFolder() {
2830
- return path6.join(this.rootDir, "trusted/certs");
1664
+ return path4.join(this.rootDir, "trusted/certs");
2831
1665
  }
2832
1666
  /** Path to the trusted CRL folder. */
2833
1667
  get crlFolder() {
2834
- return path6.join(this.rootDir, "trusted/crl");
1668
+ return path4.join(this.rootDir, "trusted/crl");
2835
1669
  }
2836
1670
  /** Path to the issuer (CA) certificates folder. */
2837
1671
  get issuersCertFolder() {
2838
- return path6.join(this.rootDir, "issuers/certs");
1672
+ return path4.join(this.rootDir, "issuers/certs");
2839
1673
  }
2840
1674
  /** Path to the issuer CRL folder. */
2841
1675
  get issuersCrlFolder() {
2842
- return path6.join(this.rootDir, "issuers/crl");
1676
+ return path4.join(this.rootDir, "issuers/crl");
2843
1677
  }
2844
1678
  /** Path to the own certificate folder. */
2845
1679
  get ownCertFolder() {
2846
- return path6.join(this.rootDir, "own/certs");
1680
+ return path4.join(this.rootDir, "own/certs");
2847
1681
  }
2848
1682
  get ownPrivateFolder() {
2849
- return path6.join(this.rootDir, "own/private");
1683
+ return path4.join(this.rootDir, "own/private");
2850
1684
  }
2851
1685
  /**
2852
1686
  * Check if a certificate is in the trusted store.
@@ -2881,9 +1715,9 @@ var CertificateManager = class _CertificateManager extends EventEmitter {
2881
1715
  } catch (_err) {
2882
1716
  return "BadCertificateInvalid";
2883
1717
  }
2884
- const filename = path6.join(this.rejectedFolder, `${buildIdealCertificateName(leafCertificate)}.pem`);
1718
+ const filename = path4.join(this.rejectedFolder, `${buildIdealCertificateName(leafCertificate)}.pem`);
2885
1719
  debugLog("certificate has never been seen before and is now rejected (untrusted) ", filename);
2886
- await fsWriteFile(filename, toPem2(chain, "CERTIFICATE"));
1720
+ await fsWriteFile(filename, toPem(chain, "CERTIFICATE"));
2887
1721
  this.#thumbs.rejected.set(fingerprint, { certificate: leafCertificate, filename });
2888
1722
  }
2889
1723
  return "BadCertificateUntrusted";
@@ -2897,7 +1731,7 @@ var CertificateManager = class _CertificateManager extends EventEmitter {
2897
1731
  }
2898
1732
  const chain = coerceCertificateChain(certificateOrChain);
2899
1733
  debugLog("NB CERTIFICATE IN CHAIN = ", chain.length);
2900
- const info = exploreCertificate2(chain[0]);
1734
+ const info = exploreCertificate(chain[0]);
2901
1735
  let hasValidIssuer = false;
2902
1736
  let hasTrustedIssuer = false;
2903
1737
  const hasIssuerKey = info.tbsCertificate.extensions?.authorityKeyIdentifier?.keyIdentifier;
@@ -2985,23 +1819,19 @@ var CertificateManager = class _CertificateManager extends EventEmitter {
2985
1819
  return "BadCertificateUntrusted" /* BadCertificateUntrusted */;
2986
1820
  }
2987
1821
  }
2988
- const _c2 = chain[1] ? exploreCertificateInfo(chain[1]) : "non";
2989
- debugLog("chain[1] info=", _c2);
2990
- const certificateInfo = exploreCertificateInfo(chain[0]);
1822
+ const { validity } = exploreCertificate(chain[0]).tbsCertificate;
2991
1823
  const now = /* @__PURE__ */ new Date();
2992
1824
  let isTimeInvalid = false;
2993
- if (certificateInfo.notBefore.getTime() > now.getTime()) {
1825
+ if (validity.notBefore.getTime() > now.getTime()) {
2994
1826
  debugLog(
2995
- `${chalk6.red("certificate is invalid : certificate is not active yet !")} not before date =${certificateInfo.notBefore}`
1827
+ `${chalk3.red("certificate is invalid : certificate is not active yet !")} not before date =${validity.notBefore}`
2996
1828
  );
2997
1829
  if (!options.acceptPendingCertificate) {
2998
1830
  isTimeInvalid = true;
2999
1831
  }
3000
1832
  }
3001
- if (certificateInfo.notAfter.getTime() <= now.getTime()) {
3002
- debugLog(
3003
- `${chalk6.red("certificate is invalid : certificate has expired !")} not after date =${certificateInfo.notAfter}`
3004
- );
1833
+ if (validity.notAfter.getTime() <= now.getTime()) {
1834
+ debugLog(`${chalk3.red("certificate is invalid : certificate has expired !")} not after date =${validity.notAfter}`);
3005
1835
  if (!options.acceptOutdatedCertificate) {
3006
1836
  isTimeInvalid = true;
3007
1837
  }
@@ -3040,7 +1870,7 @@ var CertificateManager = class _CertificateManager extends EventEmitter {
3040
1870
  const chain = coerceCertificateChain(certificate);
3041
1871
  for (const element of chain) {
3042
1872
  try {
3043
- exploreCertificateInfo(element);
1873
+ exploreCertificate(element);
3044
1874
  } catch (_err) {
3045
1875
  return "BadCertificateInvalid" /* BadCertificateInvalid */;
3046
1876
  }
@@ -3093,55 +1923,55 @@ var CertificateManager = class _CertificateManager extends EventEmitter {
3093
1923
  this.#initializingPromise = void 0;
3094
1924
  this.state = 2 /* Initialized */;
3095
1925
  _CertificateManager.#activeInstances.add(this);
3096
- _CertificateManager.#installProcessCleanup();
1926
+ _CertificateManager.#installExitCleanup();
3097
1927
  }
3098
1928
  async #initialize() {
3099
1929
  const pkiDir = this.#location;
3100
1930
  mkdirRecursiveSync(pkiDir);
3101
- mkdirRecursiveSync(path6.join(pkiDir, "own"));
3102
- mkdirRecursiveSync(path6.join(pkiDir, "own/certs"));
3103
- ensurePrivateDirectory(path6.join(pkiDir, "own/private"));
3104
- mkdirRecursiveSync(path6.join(pkiDir, "rejected"));
3105
- mkdirRecursiveSync(path6.join(pkiDir, "trusted"));
3106
- mkdirRecursiveSync(path6.join(pkiDir, "trusted/certs"));
3107
- mkdirRecursiveSync(path6.join(pkiDir, "trusted/crl"));
3108
- mkdirRecursiveSync(path6.join(pkiDir, "issuers"));
3109
- mkdirRecursiveSync(path6.join(pkiDir, "issuers/certs"));
3110
- mkdirRecursiveSync(path6.join(pkiDir, "issuers/crl"));
3111
- const ownsDiskKey = !this.#privateKeyProvider;
3112
- const needsKeyGeneration = ownsDiskKey && !fs10.existsSync(this.privateKey);
1931
+ mkdirRecursiveSync(path4.join(pkiDir, "own"));
1932
+ mkdirRecursiveSync(path4.join(pkiDir, "own/certs"));
1933
+ ensurePrivateDirectory(path4.join(pkiDir, "own/private"));
1934
+ mkdirRecursiveSync(path4.join(pkiDir, "rejected"));
1935
+ mkdirRecursiveSync(path4.join(pkiDir, "trusted"));
1936
+ mkdirRecursiveSync(path4.join(pkiDir, "trusted/certs"));
1937
+ mkdirRecursiveSync(path4.join(pkiDir, "trusted/crl"));
1938
+ mkdirRecursiveSync(path4.join(pkiDir, "issuers"));
1939
+ mkdirRecursiveSync(path4.join(pkiDir, "issuers/certs"));
1940
+ mkdirRecursiveSync(path4.join(pkiDir, "issuers/crl"));
1941
+ const ownsDiskKey = !this.#privateKeyProvider && !this.#keyOperations;
1942
+ const needsKeyGeneration = ownsDiskKey && !fs7.existsSync(this.privateKey);
3113
1943
  const needsKeyEncryption = ownsDiskKey && !needsKeyGeneration && this.#privateKeyPassphrase !== void 0 && !isEncryptedPrivateKeyFile(this.privateKey);
3114
- if (!fs10.existsSync(this.configFile) || needsKeyGeneration || needsKeyEncryption) {
1944
+ if (!fs7.existsSync(this.configFile) || needsKeyGeneration || needsKeyEncryption) {
3115
1945
  return await this.withLock2(async () => {
3116
1946
  if (this.state === 3 /* Disposing */ || this.state === 4 /* Disposed */) {
3117
1947
  return;
3118
1948
  }
3119
- if (!fs10.existsSync(this.configFile)) {
3120
- fs10.writeFileSync(this.configFile, configurationFileSimpleTemplate2);
1949
+ if (!fs7.existsSync(this.configFile)) {
1950
+ fs7.writeFileSync(this.configFile, configurationFileSimpleTemplate);
3121
1951
  }
3122
- if (ownsDiskKey && !fs10.existsSync(this.privateKey)) {
1952
+ if (ownsDiskKey && !fs7.existsSync(this.privateKey)) {
3123
1953
  debugLog("generating private key ...");
3124
1954
  const passphrase = await resolvePrivateKeyPassphrase(this.#privateKeyPassphrase);
3125
- await generatePrivateKeyFile2(this.privateKey, this.keySize, { passphrase });
3126
- this.#cachedPrivateKey = readPrivateKey2(this.privateKey, passphrase);
1955
+ await generatePrivateKeyFile(this.privateKey, this.keySize, { passphrase });
1956
+ this.#cachedPrivateKey = readPrivateKey(this.privateKey, passphrase);
3127
1957
  } else if (ownsDiskKey && this.#privateKeyPassphrase !== void 0 && !isEncryptedPrivateKeyFile(this.privateKey)) {
3128
1958
  warningLog("initialize: private key is plaintext but a passphrase is configured; encrypting it in place");
3129
1959
  const passphrase = await resolvePrivateKeyPassphrase(this.#privateKeyPassphrase);
3130
- const plaintextKey = readPrivateKey2(this.privateKey);
1960
+ const plaintextKey = readPrivateKey(this.privateKey);
3131
1961
  await this.#rewritePrivateKeyFile(plaintextKey, passphrase);
3132
1962
  this.#cachedPrivateKey = plaintextKey;
3133
1963
  }
3134
1964
  if (ownsDiskKey) {
3135
1965
  restrictPrivateFilePermissions(this.privateKey, 384);
3136
1966
  }
3137
- await this.getPrivateKey();
1967
+ await this.#probePrivateKey();
3138
1968
  await this.#readCertificates();
3139
1969
  });
3140
1970
  } else {
3141
1971
  if (ownsDiskKey) {
3142
1972
  restrictPrivateFilePermissions(this.privateKey, 384);
3143
1973
  }
3144
- await this.getPrivateKey();
1974
+ await this.#probePrivateKey();
3145
1975
  await this.#readCertificates();
3146
1976
  }
3147
1977
  }
@@ -3180,6 +2010,7 @@ var CertificateManager = class _CertificateManager extends EventEmitter {
3180
2010
  this.#cachedPrivateKey = void 0;
3181
2011
  this.#privateKeyPromise = void 0;
3182
2012
  _CertificateManager.#activeInstances.delete(this);
2013
+ _CertificateManager.#uninstallExitCleanupIfIdle();
3183
2014
  }
3184
2015
  }
3185
2016
  /**
@@ -3207,7 +2038,7 @@ var CertificateManager = class _CertificateManager extends EventEmitter {
3207
2038
  await this.#readCertificates();
3208
2039
  }
3209
2040
  async withLock2(action) {
3210
- const lockFileName = path6.join(this.rootDir, "mutex");
2041
+ const lockFileName = path4.join(this.rootDir, "mutex");
3211
2042
  return withLock({ fileToLock: lockFileName }, async () => {
3212
2043
  return await action();
3213
2044
  });
@@ -3225,16 +2056,16 @@ var CertificateManager = class _CertificateManager extends EventEmitter {
3225
2056
  if (typeof params.applicationUri !== "string") {
3226
2057
  throw new Error("createSelfSignedCertificate: expecting applicationUri to be a string");
3227
2058
  }
3228
- if (!this.#privateKeyProvider && !fs10.existsSync(this.privateKey)) {
2059
+ if (!this.#privateKeyProvider && !this.#keyOperations && !fs7.existsSync(this.privateKey)) {
3229
2060
  throw new Error(`Cannot find private key ${this.privateKey}`);
3230
2061
  }
3231
- let certificateFilename = path6.join(this.rootDir, "own/certs/self_signed_certificate.pem");
2062
+ let certificateFilename = path4.join(this.rootDir, "own/certs/self_signed_certificate.pem");
3232
2063
  certificateFilename = params.outputFile || certificateFilename;
3233
2064
  const _params = {
3234
2065
  ...params,
3235
2066
  rootDir: this.rootDir,
3236
2067
  configFile: this.configFile,
3237
- privateKey: await this.getPrivateKey(),
2068
+ privateKey: await this.#resolveSigningKey(),
3238
2069
  subject: params.subject || "CN=FIXME"
3239
2070
  };
3240
2071
  await this.withLock2(async () => {
@@ -3260,14 +2091,14 @@ var CertificateManager = class _CertificateManager extends EventEmitter {
3260
2091
  }
3261
2092
  const _params = {
3262
2093
  ...params,
3263
- rootDir: path6.resolve(this.rootDir),
3264
- configFile: path6.resolve(this.configFile),
3265
- privateKey: await this.getPrivateKey()
2094
+ rootDir: path4.resolve(this.rootDir),
2095
+ configFile: path4.resolve(this.configFile),
2096
+ privateKey: await this.#resolveSigningKey()
3266
2097
  };
3267
2098
  return await this.withLock2(async () => {
3268
2099
  const now = /* @__PURE__ */ new Date();
3269
2100
  const today = `${now.toISOString().slice(0, 10)}_${now.getTime()}`;
3270
- const certificateSigningRequestFilename = path6.join(this.rootDir, "own/certs", `certificate_${today}.csr`);
2101
+ const certificateSigningRequestFilename = path4.join(this.rootDir, "own/certs", `certificate_${today}.csr`);
3271
2102
  await createCertificateSigningRequestAsync(certificateSigningRequestFilename, _params);
3272
2103
  return certificateSigningRequestFilename;
3273
2104
  });
@@ -3287,13 +2118,13 @@ var CertificateManager = class _CertificateManager extends EventEmitter {
3287
2118
  return status;
3288
2119
  }
3289
2120
  }
3290
- const pemCertificate = toPem2(certificate, "CERTIFICATE");
2121
+ const pemCertificate = toPem(certificate, "CERTIFICATE");
3291
2122
  const fingerprint = makeFingerprint(certificate);
3292
2123
  if (this.#thumbs.issuers.certs.has(fingerprint)) {
3293
2124
  return "Good" /* Good */;
3294
2125
  }
3295
- const filename = path6.join(this.issuersCertFolder, `issuer_${buildIdealCertificateName(certificate)}.pem`);
3296
- await fs10.promises.writeFile(filename, pemCertificate, "ascii");
2126
+ const filename = path4.join(this.issuersCertFolder, `issuer_${buildIdealCertificateName(certificate)}.pem`);
2127
+ await fs7.promises.writeFile(filename, pemCertificate, "ascii");
3297
2128
  this.#thumbs.issuers.certs.set(fingerprint, { certificate, filename });
3298
2129
  if (addInTrustList) {
3299
2130
  await this.trustCertificate(certificate);
@@ -3332,10 +2163,10 @@ var CertificateManager = class _CertificateManager extends EventEmitter {
3332
2163
  if (!index.has(key)) {
3333
2164
  index.set(key, { crls: [], serialNumbers: {} });
3334
2165
  }
3335
- const pemCertificate = toPem2(crl, "X509 CRL");
2166
+ const pemCertificate = toPem(crl, "X509 CRL");
3336
2167
  const sanitizedKey = key.replace(/:/g, "");
3337
- const filename = path6.join(folder, `crl_[${sanitizedKey}].pem`);
3338
- await fs10.promises.writeFile(filename, pemCertificate, "ascii");
2168
+ const filename = path4.join(folder, `crl_[${sanitizedKey}].pem`);
2169
+ await fs7.promises.writeFile(filename, pemCertificate, "ascii");
3339
2170
  await this.#onCrlFileAdded(index, filename);
3340
2171
  await this.#waitAndCheckCRLProcessingStatus();
3341
2172
  return "Good" /* Good */;
@@ -3354,11 +2185,11 @@ var CertificateManager = class _CertificateManager extends EventEmitter {
3354
2185
  async clearRevocationLists(target) {
3355
2186
  const clearFolder = async (folder, index) => {
3356
2187
  try {
3357
- const files = await fs10.promises.readdir(folder);
2188
+ const files = await fs7.promises.readdir(folder);
3358
2189
  for (const file of files) {
3359
- const ext = path6.extname(file).toLowerCase();
2190
+ const ext = path4.extname(file).toLowerCase();
3360
2191
  if (ext === ".crl" || ext === ".pem" || ext === ".der") {
3361
- await fs10.promises.unlink(path6.join(folder, file));
2192
+ await fs7.promises.unlink(path4.join(folder, file));
3362
2193
  }
3363
2194
  }
3364
2195
  } catch (err) {
@@ -3400,7 +2231,7 @@ var CertificateManager = class _CertificateManager extends EventEmitter {
3400
2231
  return null;
3401
2232
  }
3402
2233
  try {
3403
- await fs10.promises.unlink(entry.filename);
2234
+ await fs7.promises.unlink(entry.filename);
3404
2235
  } catch (err) {
3405
2236
  if (err.code !== "ENOENT") {
3406
2237
  throw err;
@@ -3424,7 +2255,7 @@ var CertificateManager = class _CertificateManager extends EventEmitter {
3424
2255
  return null;
3425
2256
  }
3426
2257
  try {
3427
- await fs10.promises.unlink(entry.filename);
2258
+ await fs7.promises.unlink(entry.filename);
3428
2259
  } catch (err) {
3429
2260
  if (err.code !== "ENOENT") {
3430
2261
  throw err;
@@ -3440,14 +2271,14 @@ var CertificateManager = class _CertificateManager extends EventEmitter {
3440
2271
  * @param target - "issuers", "trusted", or "all" (default "all")
3441
2272
  */
3442
2273
  async removeRevocationListsForIssuer(issuerCertificate, target = "all") {
3443
- const issuerInfo = exploreCertificate2(issuerCertificate);
2274
+ const issuerInfo = exploreCertificate(issuerCertificate);
3444
2275
  const issuerFingerprint = issuerInfo.tbsCertificate.subjectFingerPrint;
3445
2276
  const processIndex = async (index) => {
3446
2277
  const crlData = index.get(issuerFingerprint);
3447
2278
  if (!crlData) return;
3448
2279
  for (const crlEntry of crlData.crls) {
3449
2280
  try {
3450
- await fs10.promises.unlink(crlEntry.filename);
2281
+ await fs7.promises.unlink(crlEntry.filename);
3451
2282
  } catch (err) {
3452
2283
  if (err.code !== "ENOENT") {
3453
2284
  throw err;
@@ -3514,7 +2345,7 @@ var CertificateManager = class _CertificateManager extends EventEmitter {
3514
2345
  const opts = this.#addCertValidation;
3515
2346
  let leafInfo;
3516
2347
  try {
3517
- leafInfo = exploreCertificate2(leafCertificate);
2348
+ leafInfo = exploreCertificate(leafCertificate);
3518
2349
  } catch (_err) {
3519
2350
  return "BadCertificateInvalid" /* BadCertificateInvalid */;
3520
2351
  }
@@ -3528,17 +2359,12 @@ var CertificateManager = class _CertificateManager extends EventEmitter {
3528
2359
  return "BadSecurityChecksFailed" /* BadSecurityChecksFailed */;
3529
2360
  }
3530
2361
  if (!opts.acceptExpiredCertificate) {
3531
- let certDetails;
3532
- try {
3533
- certDetails = exploreCertificateInfo(currentCert);
3534
- } catch (_err) {
3535
- return "BadCertificateInvalid" /* BadCertificateInvalid */;
3536
- }
2362
+ const { validity } = currentInfo.tbsCertificate;
3537
2363
  const now = /* @__PURE__ */ new Date();
3538
- if (certDetails.notBefore.getTime() > now.getTime()) {
2364
+ if (validity.notBefore.getTime() > now.getTime()) {
3539
2365
  return "BadCertificateTimeInvalid" /* BadCertificateTimeInvalid */;
3540
2366
  }
3541
- if (certDetails.notAfter.getTime() <= now.getTime()) {
2367
+ if (validity.notAfter.getTime() <= now.getTime()) {
3542
2368
  return depth === 1 ? "BadCertificateTimeInvalid" /* BadCertificateTimeInvalid */ : "BadCertificateIssuerTimeInvalid" /* BadCertificateIssuerTimeInvalid */;
3543
2369
  }
3544
2370
  }
@@ -3582,7 +2408,7 @@ var CertificateManager = class _CertificateManager extends EventEmitter {
3582
2408
  }
3583
2409
  currentCert = issuerCert;
3584
2410
  try {
3585
- currentInfo = exploreCertificate2(currentCert);
2411
+ currentInfo = exploreCertificate(currentCert);
3586
2412
  } catch (_err) {
3587
2413
  return "BadCertificateInvalid" /* BadCertificateInvalid */;
3588
2414
  }
@@ -3631,7 +2457,7 @@ var CertificateManager = class _CertificateManager extends EventEmitter {
3631
2457
  */
3632
2458
  async findIssuerCertificate(certificate) {
3633
2459
  const firstCertificate = coerceCertificateChain(certificate)[0];
3634
- const certInfo = exploreCertificate2(firstCertificate);
2460
+ const certInfo = exploreCertificate(firstCertificate);
3635
2461
  if (isSelfSigned2(certInfo)) {
3636
2462
  return firstCertificate;
3637
2463
  }
@@ -3689,7 +2515,7 @@ var CertificateManager = class _CertificateManager extends EventEmitter {
3689
2515
  let depth = 0;
3690
2516
  while (depth < maxDepth) {
3691
2517
  const lastCert = result[result.length - 1];
3692
- const lastInfo = exploreCertificate2(lastCert);
2518
+ const lastInfo = exploreCertificate(lastCert);
3693
2519
  if (isSelfSigned2(lastInfo)) {
3694
2520
  const wasExtended = result.length > chain.length;
3695
2521
  return {
@@ -3716,7 +2542,7 @@ var CertificateManager = class _CertificateManager extends EventEmitter {
3716
2542
  return {
3717
2543
  chain: result,
3718
2544
  status: "AlreadyComplete" /* AlreadyComplete */,
3719
- message: `Chain ends at root "${exploreCertificate2(issuerCert).tbsCertificate.subject.commonName}" (already present in chain).`
2545
+ message: `Chain ends at root "${exploreCertificate(issuerCert).tbsCertificate.subject.commonName}" (already present in chain).`
3720
2546
  };
3721
2547
  }
3722
2548
  result.push(issuerCert);
@@ -3755,9 +2581,9 @@ var CertificateManager = class _CertificateManager extends EventEmitter {
3755
2581
  const fingerprint = makeFingerprint(certificate);
3756
2582
  let status = await this.#checkRejectedOrTrusted(certificate);
3757
2583
  if (status === "unknown") {
3758
- const pem = toPem2(chain, "CERTIFICATE");
3759
- const filename = path6.join(this.rejectedFolder, `${buildIdealCertificateName(certificate)}.pem`);
3760
- await fs10.promises.writeFile(filename, pem);
2584
+ const pem = toPem(chain, "CERTIFICATE");
2585
+ const filename = path4.join(this.rejectedFolder, `${buildIdealCertificateName(certificate)}.pem`);
2586
+ await fs7.promises.writeFile(filename, pem);
3761
2587
  this.#thumbs.rejected.set(fingerprint, { certificate, filename });
3762
2588
  status = "rejected";
3763
2589
  }
@@ -3773,10 +2599,10 @@ var CertificateManager = class _CertificateManager extends EventEmitter {
3773
2599
  throw new Error(`#moveCertificate: certificate ${fingerprint.substring(0, 10)} not found in ${status} index`);
3774
2600
  }
3775
2601
  const destFolder = newStatus === "trusted" ? this.trustedFolder : this.rejectedFolder;
3776
- const certificateDest = path6.join(destFolder, path6.basename(srcEntry.filename));
2602
+ const certificateDest = path4.join(destFolder, path4.basename(srcEntry.filename));
3777
2603
  debugLog("#moveCertificate", fingerprint.substring(0, 10), "old name", srcEntry.filename);
3778
2604
  debugLog("#moveCertificate", fingerprint.substring(0, 10), "new name", certificateDest);
3779
- await fs10.promises.rename(srcEntry.filename, certificateDest);
2605
+ await fs7.promises.rename(srcEntry.filename, certificateDest);
3780
2606
  indexSrc.delete(fingerprint);
3781
2607
  const indexDest = newStatus === "trusted" ? this.#thumbs.trusted : this.#thumbs.rejected;
3782
2608
  indexDest.set(fingerprint, { certificate, filename: certificateDest });
@@ -3784,7 +2610,7 @@ var CertificateManager = class _CertificateManager extends EventEmitter {
3784
2610
  });
3785
2611
  }
3786
2612
  #findAssociatedCRLs(issuerCertificate) {
3787
- const issuerCertificateInfo = exploreCertificate2(issuerCertificate);
2613
+ const issuerCertificateInfo = exploreCertificate(issuerCertificate);
3788
2614
  const key = issuerCertificateInfo.tbsCertificate.subjectFingerPrint;
3789
2615
  return this.#thumbs.issuersCrl.get(key) ?? this.#thumbs.crl.get(key) ?? null;
3790
2616
  }
@@ -3823,7 +2649,7 @@ var CertificateManager = class _CertificateManager extends EventEmitter {
3823
2649
  if (!crls) {
3824
2650
  return "BadCertificateRevocationUnknown" /* BadCertificateRevocationUnknown */;
3825
2651
  }
3826
- const certInfo = exploreCertificate2(firstCertificate);
2652
+ const certInfo = exploreCertificate(firstCertificate);
3827
2653
  const serialNumber = certInfo.tbsCertificate.serialNumber || certInfo.tbsCertificate.extensions?.authorityKeyIdentifier?.serial || "";
3828
2654
  const key = certInfo.tbsCertificate.extensions?.authorityKeyIdentifier?.authorityCertIssuerFingerPrint || "<unknown>";
3829
2655
  const crl2 = this.#thumbs.crl.get(key) ?? null;
@@ -3849,7 +2675,7 @@ var CertificateManager = class _CertificateManager extends EventEmitter {
3849
2675
  const { index, filename } = nextCRL;
3850
2676
  const crl = await readCertificateRevocationList(filename);
3851
2677
  const crlInfo = exploreCertificateRevocationList(crl);
3852
- debugLog(chalk6.cyan("add CRL in folder "), filename);
2678
+ debugLog(chalk3.cyan("add CRL in folder "), filename);
3853
2679
  const fingerprint = crlInfo.tbsCertList.issuerFingerprint;
3854
2680
  if (!index.has(fingerprint)) {
3855
2681
  index.set(fingerprint, { crls: [], serialNumbers: {} });
@@ -3862,7 +2688,7 @@ var CertificateManager = class _CertificateManager extends EventEmitter {
3862
2688
  data.serialNumbers[serialNumber] = revokedCertificate.revocationDate;
3863
2689
  }
3864
2690
  }
3865
- debugLog(chalk6.cyan("CRL"), fingerprint, "serial numbers = ", Object.keys(data.serialNumbers));
2691
+ debugLog(chalk3.cyan("CRL"), fingerprint, "serial numbers = ", Object.keys(data.serialNumbers));
3866
2692
  } catch (err) {
3867
2693
  debugLog("CRL filename error =");
3868
2694
  debugLog(err);
@@ -3888,14 +2714,15 @@ var CertificateManager = class _CertificateManager extends EventEmitter {
3888
2714
  const chokidarOptions = {
3889
2715
  usePolling,
3890
2716
  ...usePolling ? { interval: pollingInterval } : {},
2717
+ depth: 0,
3891
2718
  persistent: false
3892
2719
  };
3893
2720
  const allCapturedHandles = [];
3894
- const origWatch = fs10.watch;
2721
+ const origWatch = fs7.watch;
3895
2722
  let watcherReadyCount = 0;
3896
2723
  const totalWatchers = 5;
3897
- fs10.watch = ((...args) => {
3898
- const handle = origWatch.apply(fs10, args);
2724
+ fs7.watch = ((...args) => {
2725
+ const handle = origWatch.apply(fs7, args);
3899
2726
  handle.setMaxListeners(handle.getMaxListeners() + 1);
3900
2727
  handle.on("error", () => {
3901
2728
  });
@@ -3911,7 +2738,7 @@ var CertificateManager = class _CertificateManager extends EventEmitter {
3911
2738
  }
3912
2739
  watcherReadyCount++;
3913
2740
  if (watcherReadyCount >= totalWatchers) {
3914
- fs10.watch = origWatch;
2741
+ fs7.watch = origWatch;
3915
2742
  }
3916
2743
  };
3917
2744
  return { w, capturedHandles: allCapturedHandles.slice(startIdx), unreffAll };
@@ -3924,7 +2751,7 @@ var CertificateManager = class _CertificateManager extends EventEmitter {
3924
2751
  this.#scanCrlFolder(this.issuersCrlFolder, this.#thumbs.issuersCrl)
3925
2752
  ]);
3926
2753
  if (this.#disableFileWatchers) {
3927
- fs10.watch = origWatch;
2754
+ fs7.watch = origWatch;
3928
2755
  } else {
3929
2756
  this.#startWatcher(this.trustedFolder, this.#thumbs.trusted, createUnreffedWatcher, "trusted");
3930
2757
  this.#startWatcher(this.issuersCertFolder, this.#thumbs.issuers.certs, createUnreffedWatcher, "issuersCerts");
@@ -3939,19 +2766,19 @@ var CertificateManager = class _CertificateManager extends EventEmitter {
3939
2766
  * file reads, preventing main-loop stalls with large folders.
3940
2767
  */
3941
2768
  async #scanCertFolder(folder, index) {
3942
- if (!fs10.existsSync(folder)) return;
3943
- const files = await fs10.promises.readdir(folder);
2769
+ if (!fs7.existsSync(folder)) return;
2770
+ const files = await fs7.promises.readdir(folder);
3944
2771
  for (const file of files) {
3945
- const filename = path6.join(folder, file);
2772
+ const filename = path4.join(folder, file);
3946
2773
  try {
3947
- const stat = await fs10.promises.stat(filename);
2774
+ const stat = await fs7.promises.stat(filename);
3948
2775
  if (!stat.isFile()) continue;
3949
2776
  const certs = await readCertificateChainAsync(filename);
3950
2777
  if (certs.length === 0) continue;
3951
2778
  const certificate = certs[0];
3952
2779
  if (certs.length > 1) {
3953
2780
  try {
3954
- await fs10.promises.writeFile(filename, toPem2(certs, "CERTIFICATE"), "ascii");
2781
+ await fs7.promises.writeFile(filename, toPem(certs, "CERTIFICATE"), "ascii");
3955
2782
  } catch (writeErr) {
3956
2783
  debugLog(`scanCertFolder: could not rewrite legacy PEM ${filename} (read-only fs?)`, writeErr);
3957
2784
  }
@@ -3965,7 +2792,7 @@ var CertificateManager = class _CertificateManager extends EventEmitter {
3965
2792
  }
3966
2793
  }
3967
2794
  }
3968
- const info = exploreCertificate2(certificate);
2795
+ const info = exploreCertificate(certificate);
3969
2796
  const fingerprint = makeFingerprint(certificate);
3970
2797
  index.set(fingerprint, { certificate, filename, info });
3971
2798
  this.#filenameToHash.set(filename, fingerprint);
@@ -3978,12 +2805,12 @@ var CertificateManager = class _CertificateManager extends EventEmitter {
3978
2805
  * Scan a CRL folder and populate the in-memory CRL index.
3979
2806
  */
3980
2807
  async #scanCrlFolder(folder, index) {
3981
- if (!fs10.existsSync(folder)) return;
3982
- const files = await fs10.promises.readdir(folder);
2808
+ if (!fs7.existsSync(folder)) return;
2809
+ const files = await fs7.promises.readdir(folder);
3983
2810
  for (const file of files) {
3984
- const filename = path6.join(folder, file);
2811
+ const filename = path4.join(folder, file);
3985
2812
  try {
3986
- const stat = await fs10.promises.stat(filename);
2813
+ const stat = await fs7.promises.stat(filename);
3987
2814
  if (!stat.isFile()) continue;
3988
2815
  this.#onCrlFileAdded(index, filename);
3989
2816
  } catch (err) {
@@ -4041,7 +2868,7 @@ var CertificateManager = class _CertificateManager extends EventEmitter {
4041
2868
  });
4042
2869
  let ready = false;
4043
2870
  w.on("unlink", (filename) => {
4044
- debugLog(chalk6.cyan(`unlink in folder ${folder}`), filename);
2871
+ debugLog(chalk3.cyan(`unlink in folder ${folder}`), filename);
4045
2872
  const h = this.#filenameToHash.get(filename);
4046
2873
  if (h && index.has(h)) {
4047
2874
  index.delete(h);
@@ -4049,16 +2876,16 @@ var CertificateManager = class _CertificateManager extends EventEmitter {
4049
2876
  }
4050
2877
  });
4051
2878
  w.on("add", (filename) => {
4052
- debugLog(chalk6.cyan(`add in folder ${folder}`), filename);
2879
+ debugLog(chalk3.cyan(`add in folder ${folder}`), filename);
4053
2880
  try {
4054
2881
  const certificate = readCertificateChain(filename)[0];
4055
- const info = exploreCertificate2(certificate);
2882
+ const info = exploreCertificate(certificate);
4056
2883
  const fingerprint = makeFingerprint(certificate);
4057
2884
  const isNew = !index.has(fingerprint);
4058
2885
  index.set(fingerprint, { certificate, filename, info });
4059
2886
  this.#filenameToHash.set(filename, fingerprint);
4060
2887
  debugLog(
4061
- chalk6.magenta("CERT"),
2888
+ chalk3.magenta("CERT"),
4062
2889
  info.tbsCertificate.subjectFingerPrint,
4063
2890
  info.tbsCertificate.serialNumber,
4064
2891
  info.tbsCertificate.extensions?.authorityKeyIdentifier?.authorityCertIssuerFingerPrint
@@ -4072,7 +2899,7 @@ var CertificateManager = class _CertificateManager extends EventEmitter {
4072
2899
  }
4073
2900
  });
4074
2901
  w.on("change", (changedPath) => {
4075
- debugLog(chalk6.cyan(`change in folder ${folder}`), changedPath);
2902
+ debugLog(chalk3.cyan(`change in folder ${folder}`), changedPath);
4076
2903
  try {
4077
2904
  const certificate = readCertificateChain(changedPath)[0];
4078
2905
  const newFingerprint = makeFingerprint(certificate);
@@ -4080,7 +2907,7 @@ var CertificateManager = class _CertificateManager extends EventEmitter {
4080
2907
  if (oldHash && oldHash !== newFingerprint) {
4081
2908
  index.delete(oldHash);
4082
2909
  }
4083
- index.set(newFingerprint, { certificate, filename: changedPath, info: exploreCertificate2(certificate) });
2910
+ index.set(newFingerprint, { certificate, filename: changedPath, info: exploreCertificate(certificate) });
4084
2911
  this.#filenameToHash.set(changedPath, newFingerprint);
4085
2912
  this.emit("certificateChange", { store, certificate, fingerprint: newFingerprint, filename: changedPath });
4086
2913
  } catch (err) {
@@ -4108,12 +2935,152 @@ var CertificateManager = class _CertificateManager extends EventEmitter {
4108
2935
  });
4109
2936
  }
4110
2937
  };
2938
+
2939
+ // packages/node-opcua-pki/lib/pki/toolbox_pfx.ts
2940
+ import assert5 from "assert";
2941
+ import fs8 from "fs";
2942
+ import {
2943
+ certificateMatchesPrivateKey,
2944
+ certificatesToPem,
2945
+ coercePrivateKeyPem as coercePrivateKeyPem3,
2946
+ createPfx,
2947
+ makeSHA1Thumbprint as makeSHA1Thumbprint2,
2948
+ Pkcs12UnsupportedAlgorithmError,
2949
+ parsePfx,
2950
+ readCertificateChain as readCertificateChain2,
2951
+ readPrivateKey as readPrivateKey2,
2952
+ toPem as toPem2,
2953
+ x509
2954
+ } from "node-opcua-crypto";
2955
+ async function readPfxFile(options) {
2956
+ const { pfxFile, passphrase = "" } = options;
2957
+ assert5(fs8.existsSync(pfxFile), `PFX file does not exist: ${pfxFile}`);
2958
+ try {
2959
+ return await parsePfx(await fs8.promises.readFile(pfxFile), passphrase);
2960
+ } catch (err) {
2961
+ if (err instanceof Pkcs12UnsupportedAlgorithmError) {
2962
+ const explained = new Error(
2963
+ [
2964
+ `${pfxFile} uses an encryption algorithm that is not supported (${err.message}).`,
2965
+ "Bundles written by openssl 1.x default to 40-bit RC2, which openssl 3 itself",
2966
+ "rejects unless given -legacy. Convert the file once with:",
2967
+ " openssl pkcs12 -legacy -in old.pfx -nodes -out tmp.pem",
2968
+ " openssl pkcs12 -export -in tmp.pem -out new.pfx"
2969
+ ].join("\n")
2970
+ );
2971
+ explained.cause = err;
2972
+ throw explained;
2973
+ }
2974
+ throw err;
2975
+ }
2976
+ }
2977
+ async function createPFX(options) {
2978
+ const { certificateFile, privateKeyFile, privateKeyPassphrase, outputFile, passphrase = "", caCertificateFiles } = options;
2979
+ assert5(fs8.existsSync(certificateFile), `Certificate file does not exist: ${certificateFile}`);
2980
+ assert5(fs8.existsSync(privateKeyFile), `Private key file does not exist: ${privateKeyFile}`);
2981
+ const [certificate, ...bundledIssuers] = readCertificateChain2(certificateFile);
2982
+ if (!certificate) {
2983
+ throw new Error(`Certificate file holds no certificate: ${certificateFile}`);
2984
+ }
2985
+ const certificateChain = [...bundledIssuers];
2986
+ for (const caFile of caCertificateFiles ?? []) {
2987
+ assert5(fs8.existsSync(caFile), `CA certificate file does not exist: ${caFile}`);
2988
+ certificateChain.push(...readCertificateChain2(caFile));
2989
+ }
2990
+ const privateKey = readPrivateKey2(privateKeyFile, privateKeyPassphrase);
2991
+ if (!certificateMatchesPrivateKey(certificate, privateKey)) {
2992
+ throw new Error(`The private key ${privateKeyFile} does not match the certificate ${certificateFile}`);
2993
+ }
2994
+ const pfx = await createPfx({
2995
+ certificate,
2996
+ certificateChain,
2997
+ privateKey,
2998
+ password: passphrase,
2999
+ friendlyName: options.friendlyName
3000
+ });
3001
+ await fs8.promises.writeFile(outputFile, pfx);
3002
+ }
3003
+ async function extractCertificateFromPFX(options) {
3004
+ const { certificate } = await readPfxFile(options);
3005
+ return toPem2(certificate, "CERTIFICATE");
3006
+ }
3007
+ async function extractPrivateKeyFromPFX(options) {
3008
+ const { privateKey } = await readPfxFile(options);
3009
+ return coercePrivateKeyPem3(privateKey);
3010
+ }
3011
+ async function extractCACertificatesFromPFX(options) {
3012
+ const { certificateChain } = await readPfxFile(options);
3013
+ return certificateChain.length === 0 ? "" : certificatesToPem(certificateChain);
3014
+ }
3015
+ async function extractAllFromPFX(options) {
3016
+ const { certificate, certificateChain, privateKey, friendlyName } = await readPfxFile(options);
3017
+ return {
3018
+ certificate: toPem2(certificate, "CERTIFICATE"),
3019
+ privateKey: coercePrivateKeyPem3(privateKey),
3020
+ caCertificates: certificateChain.length === 0 ? "" : certificatesToPem(certificateChain),
3021
+ friendlyName
3022
+ };
3023
+ }
3024
+ async function convertPFXtoPEM(pfxFile, pemFile, passphrase = "") {
3025
+ const { certificate, certificateChain, privateKey } = await readPfxFile({ pfxFile, passphrase });
3026
+ const parts = [coercePrivateKeyPem3(privateKey), toPem2(certificate, "CERTIFICATE")];
3027
+ if (certificateChain.length > 0) {
3028
+ parts.push(certificatesToPem(certificateChain));
3029
+ }
3030
+ await fs8.promises.writeFile(pemFile, `${parts.map((part) => part.trimEnd()).join("\n")}
3031
+ `, "utf-8");
3032
+ }
3033
+ function formatThumbprint(certificate) {
3034
+ return (makeSHA1Thumbprint2(certificate).toString("hex").toUpperCase().match(/../g) ?? []).join(":");
3035
+ }
3036
+ function describePublicKey(certificate) {
3037
+ const algorithm = certificate.publicKey.algorithm;
3038
+ if (algorithm.modulusLength) {
3039
+ return `${algorithm.name} ${algorithm.modulusLength} bits`;
3040
+ }
3041
+ if (algorithm.namedCurve) {
3042
+ return `${algorithm.name} ${algorithm.namedCurve}`;
3043
+ }
3044
+ return algorithm.name;
3045
+ }
3046
+ function describeCertificate(der, indent) {
3047
+ const certificate = new x509.X509Certificate(der);
3048
+ return [
3049
+ `${indent}subject : ${certificate.subject}`,
3050
+ `${indent}issuer : ${certificate.issuer}`,
3051
+ `${indent}serial number : ${certificate.serialNumber}`,
3052
+ `${indent}not before : ${certificate.notBefore.toISOString()}`,
3053
+ `${indent}not after : ${certificate.notAfter.toISOString()}`,
3054
+ `${indent}public key : ${describePublicKey(certificate)}`,
3055
+ `${indent}signature : ${certificate.signatureAlgorithm.name}`,
3056
+ `${indent}SHA-1 thumbprint : ${formatThumbprint(der)}`
3057
+ ];
3058
+ }
3059
+ async function dumpPFX(pfxFile, passphrase = "") {
3060
+ const { certificate, certificateChain, privateKey, friendlyName } = await readPfxFile({ pfxFile, passphrase });
3061
+ const lines = [`PFX bundle : ${pfxFile}`];
3062
+ if (friendlyName) {
3063
+ lines.push(`friendly name : ${friendlyName}`);
3064
+ }
3065
+ const keyVerdict = certificateMatchesPrivateKey(certificate, privateKey) ? "matches this certificate" : "DOES NOT match this certificate";
3066
+ lines.push("", "certificate", ...describeCertificate(certificate, " "), ` private key : ${keyVerdict}`);
3067
+ lines.push("", `issuer chain : ${certificateChain.length} certificate(s)`);
3068
+ for (const [index, issuer] of certificateChain.entries()) {
3069
+ lines.push(` [${index}]`, ...describeCertificate(issuer, " "));
3070
+ }
3071
+ return `${lines.join("\n")}
3072
+ `;
3073
+ }
4111
3074
  export {
4112
3075
  CertificateAuthority,
3076
+ CertificateAuthorityCore,
4113
3077
  CertificateManager,
4114
3078
  CertificateManagerState,
4115
3079
  ChainCompletionStatus,
3080
+ NativeCaBackend,
3081
+ OpenSslCaBackend,
4116
3082
  PrivateKeyPassphraseRequiredError,
3083
+ PrivateKeyUnavailableError2 as PrivateKeyUnavailableError,
4117
3084
  Subject,
4118
3085
  VerificationStatus,
4119
3086
  adjustApplicationUri,
@@ -4130,6 +3097,7 @@ export {
4130
3097
  install_prerequisite,
4131
3098
  isIntermediateIssuer,
4132
3099
  isIssuer,
3100
+ isOpaqueSigner,
4133
3101
  isRootIssuer,
4134
3102
  makeFingerprint,
4135
3103
  quote,