node-opcua-pki 6.10.2 → 6.11.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,20 +1,27 @@
1
1
  // packages/node-opcua-pki/lib/ca/certificate_authority.ts
2
- import assert6 from "assert";
3
- import fs6 from "fs";
2
+ import assert7 from "assert";
3
+ import fs7 from "fs";
4
4
  import os3 from "os";
5
5
  import path5 from "path";
6
6
  import chalk5 from "chalk";
7
7
  import {
8
+ CertificatePurpose,
9
+ certificateMatchesPrivateKey,
8
10
  convertPEMtoDER,
9
11
  exploreCertificate,
10
12
  exploreCertificateSigningRequest,
11
13
  generatePrivateKeyFile,
12
14
  readCertificatePEM,
13
15
  readCertificateSigningRequest,
16
+ readPrivateKey,
14
17
  Subject as Subject2,
15
18
  toPem
16
19
  } from "node-opcua-crypto";
17
20
 
21
+ // packages/node-opcua-pki/lib/pki/toolbox_pfx.ts
22
+ import assert4 from "assert";
23
+ import fs4 from "fs";
24
+
18
25
  // packages/node-opcua-pki/lib/toolbox/common.ts
19
26
  import assert from "assert";
20
27
  function quote(str) {
@@ -91,30 +98,13 @@ function makePath(folderName, filename) {
91
98
  return s;
92
99
  }
93
100
 
94
- // packages/node-opcua-pki/lib/toolbox/display.ts
95
- import chalk2 from "chalk";
96
- function displayTitle(str) {
97
- if (!g_config.silent) {
98
- warningLog("");
99
- warningLog(chalk2.yellowBright(str));
100
- warningLog(chalk2.yellow(new Array(str.length + 1).join("=")), "\n");
101
- }
102
- }
103
- function displaySubtitle(str) {
104
- if (!g_config.silent) {
105
- warningLog("");
106
- warningLog(` ${chalk2.yellowBright(str)}`);
107
- warningLog(` ${chalk2.white(new Array(str.length + 1).join("-"))}`, "\n");
108
- }
109
- }
110
- function display(str) {
111
- if (!g_config.silent) {
112
- warningLog(` ${str}`);
113
- }
114
- }
115
-
116
- // packages/node-opcua-pki/lib/toolbox/with_openssl/index.ts
117
- import exp from "constants";
101
+ // packages/node-opcua-pki/lib/toolbox/with_openssl/execute_openssl.ts
102
+ import assert3 from "assert";
103
+ import child_process2 from "child_process";
104
+ import fs3 from "fs";
105
+ import os2 from "os";
106
+ import byline2 from "byline";
107
+ import chalk3 from "chalk";
118
108
 
119
109
  // packages/node-opcua-pki/lib/toolbox/with_openssl/_env.ts
120
110
  var exportedEnvVars = {};
@@ -155,22 +145,6 @@ function processAltNames(params) {
155
145
  setEnv("ALTNAME", subjectAltNameString);
156
146
  }
157
147
 
158
- // packages/node-opcua-pki/lib/toolbox/with_openssl/create_certificate_signing_request.ts
159
- import assert5 from "assert";
160
- import fs5 from "fs";
161
- import path4 from "path";
162
-
163
- // packages/node-opcua-pki/lib/misc/subject.ts
164
- import { Subject } from "node-opcua-crypto";
165
-
166
- // packages/node-opcua-pki/lib/toolbox/with_openssl/execute_openssl.ts
167
- import assert3 from "assert";
168
- import child_process2 from "child_process";
169
- import fs3 from "fs";
170
- import os2 from "os";
171
- import byline2 from "byline";
172
- import chalk4 from "chalk";
173
-
174
148
  // packages/node-opcua-pki/lib/toolbox/with_openssl/install_prerequisite.ts
175
149
  import child_process from "child_process";
176
150
  import fs2 from "fs";
@@ -178,7 +152,7 @@ import os from "os";
178
152
  import path2 from "path";
179
153
  import url from "url";
180
154
  import byline from "byline";
181
- import chalk3 from "chalk";
155
+ import chalk2 from "chalk";
182
156
  import ProgressBar from "progress";
183
157
  import wget from "wget-improved-2";
184
158
  import yauzl from "yauzl";
@@ -196,7 +170,7 @@ function makeOptions() {
196
170
  proxyAuth: auth
197
171
  }
198
172
  };
199
- warningLog(chalk3.green("- using proxy "), proxy);
173
+ warningLog(chalk2.green("- using proxy "), proxy);
200
174
  warningLog(options);
201
175
  return options;
202
176
  }
@@ -225,7 +199,7 @@ async function execute(cmd, cwd) {
225
199
  output += `${line}
226
200
  `;
227
201
  if (doDebug2) {
228
- process.stdout.write(` stdout ${chalk3.yellow(line)}
202
+ process.stdout.write(` stdout ${chalk2.yellow(line)}
229
203
  `);
230
204
  }
231
205
  });
@@ -248,8 +222,8 @@ async function getopensslExecPath() {
248
222
  const exitCode = result1?.exitCode;
249
223
  const output = result1?.output;
250
224
  if (exitCode !== 0) {
251
- warningLog(chalk3.yellow(" it seems that ") + chalk3.cyan("openssl") + chalk3.yellow(" is not installed on your computer "));
252
- warningLog(chalk3.yellow("Please install it before running this programs"));
225
+ warningLog(chalk2.yellow(" it seems that ") + chalk2.cyan("openssl") + chalk2.yellow(" is not installed on your computer "));
226
+ warningLog(chalk2.yellow("Please install it before running this programs"));
253
227
  throw new Error("Cannot find openssl");
254
228
  }
255
229
  const opensslExecPath = output.replace(/\n\r/g, "").trim();
@@ -259,7 +233,7 @@ async function check_system_openssl_version() {
259
233
  const opensslExecPath = await getopensslExecPath();
260
234
  const q_opensslExecPath = quote2(opensslExecPath);
261
235
  if (doDebug2) {
262
- warningLog(` OpenSSL found in : ${chalk3.yellow(opensslExecPath)}`);
236
+ warningLog(` OpenSSL found in : ${chalk2.yellow(opensslExecPath)}`);
263
237
  }
264
238
  const result = await execute(`${q_opensslExecPath} version`);
265
239
  const exitCode = result?.exitCode;
@@ -267,9 +241,9 @@ async function check_system_openssl_version() {
267
241
  const version = output.trim();
268
242
  const versionOK = exitCode === 0 && is_expected_openssl_version(version);
269
243
  if (!versionOK) {
270
- let message = chalk3.whiteBright("Warning !!!!!!!!!!!! ") + "\nyour version of openssl is " + version + ". It doesn't match the expected version";
244
+ let message = chalk2.whiteBright("Warning !!!!!!!!!!!! ") + "\nyour version of openssl is " + version + ". It doesn't match the expected version";
271
245
  if (process.platform === "darwin") {
272
- message += chalk3.cyan("\nplease refer to :") + chalk3.yellow(" https://github.com/node-opcua/node-opcua/wiki/installing-node-opcua-or-node-red-on-MacOS");
246
+ message += chalk2.cyan("\nplease refer to :") + chalk2.yellow(" https://github.com/node-opcua/node-opcua/wiki/installing-node-opcua-or-node-red-on-MacOS");
273
247
  }
274
248
  console.log(message);
275
249
  }
@@ -295,7 +269,7 @@ async function install_and_check_win32_openssl_version() {
295
269
  const exists = fs2.existsSync(opensslExecPath2);
296
270
  if (!exists) {
297
271
  warningLog("checking presence of ", opensslExecPath2);
298
- warningLog(chalk3.red(" cannot find file ") + opensslExecPath2);
272
+ warningLog(chalk2.red(" cannot find file ") + opensslExecPath2);
299
273
  return {
300
274
  opensslOk: false,
301
275
  version: `cannot find file ${opensslExecPath2}`
@@ -329,12 +303,12 @@ async function install_and_check_win32_openssl_version() {
329
303
  async function download_openssl() {
330
304
  const url2 = 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";
331
305
  const outputFilename = path2.join(downloadFolder, path2.basename(url2));
332
- warningLog(`downloading ${chalk3.yellow(url2)} to ${outputFilename}`);
306
+ warningLog(`downloading ${chalk2.yellow(url2)} to ${outputFilename}`);
333
307
  if (fs2.existsSync(outputFilename)) {
334
308
  return { downloadedFile: outputFilename };
335
309
  }
336
310
  const options = makeOptions();
337
- const bar = new ProgressBar(chalk3.cyan("[:bar]") + chalk3.cyan(" :percent ") + chalk3.white(":etas"), {
311
+ const bar = new ProgressBar(chalk2.cyan("[:bar]") + chalk2.cyan(" :percent ") + chalk2.white(":etas"), {
338
312
  complete: "=",
339
313
  incomplete: " ",
340
314
  total: 100,
@@ -416,21 +390,21 @@ async function install_and_check_win32_openssl_version() {
416
390
  }
417
391
  const { opensslOk, version: _version } = await check_openssl_win32();
418
392
  if (!opensslOk) {
419
- warningLog(chalk3.yellow("openssl seems to be missing and need to be installed"));
393
+ warningLog(chalk2.yellow("openssl seems to be missing and need to be installed"));
420
394
  const { downloadedFile } = await download_openssl();
421
395
  if (doDebug2) {
422
- warningLog("deflating ", chalk3.yellow(downloadedFile));
396
+ warningLog("deflating ", chalk2.yellow(downloadedFile));
423
397
  }
424
398
  await unzip_openssl(downloadedFile);
425
399
  const opensslExists = !!fs2.existsSync(opensslExecPath);
426
400
  if (doDebug2) {
427
- warningLog("verifying ", opensslExists, opensslExists ? chalk3.green("OK ") : chalk3.red(" Error"), opensslExecPath);
401
+ warningLog("verifying ", opensslExists, opensslExists ? chalk2.green("OK ") : chalk2.red(" Error"), opensslExecPath);
428
402
  }
429
403
  const _opensslExecPath2 = await check_openssl_win32();
430
404
  return opensslExecPath;
431
405
  } else {
432
406
  if (doDebug2) {
433
- warningLog(chalk3.green("openssl is already installed and have the expected version."));
407
+ warningLog(chalk2.green("openssl is already installed and have the expected version."));
434
408
  }
435
409
  return opensslExecPath;
436
410
  }
@@ -461,7 +435,7 @@ async function execute2(cmd, options) {
461
435
  const from = new Error();
462
436
  options.cwd = options.cwd || process.cwd();
463
437
  if (!g_config.silent) {
464
- warningLog(chalk4.cyan(" CWD "), options.cwd);
438
+ warningLog(chalk3.cyan(" CWD "), options.cwd);
465
439
  }
466
440
  const outputs = [];
467
441
  return await new Promise((resolve, reject) => {
@@ -475,10 +449,10 @@ async function execute2(cmd, options) {
475
449
  if (err) {
476
450
  if (!options.hideErrorMessage) {
477
451
  const fence = "###########################################";
478
- console.error(chalk4.bgWhiteBright.redBright(`${fence} OPENSSL ERROR ${fence}`));
479
- console.error(chalk4.bgWhiteBright.redBright(`CWD = ${options.cwd}`));
480
- console.error(chalk4.bgWhiteBright.redBright(err.message));
481
- console.error(chalk4.bgWhiteBright.redBright(`${fence} OPENSSL ERROR ${fence}`));
452
+ console.error(chalk3.bgWhiteBright.redBright(`${fence} OPENSSL ERROR ${fence}`));
453
+ console.error(chalk3.bgWhiteBright.redBright(`CWD = ${options.cwd}`));
454
+ console.error(chalk3.bgWhiteBright.redBright(err.message));
455
+ console.error(chalk3.bgWhiteBright.redBright(`${fence} OPENSSL ERROR ${fence}`));
482
456
  console.error(from.stack);
483
457
  }
484
458
  reject(new Error(err.message));
@@ -497,7 +471,7 @@ async function execute2(cmd, options) {
497
471
  stream2.on("data", (line) => {
498
472
  line = line.toString();
499
473
  if (doDebug) {
500
- process.stdout.write(`${chalk4.white(" stdout ") + chalk4.whiteBright(line)}
474
+ process.stdout.write(`${chalk3.white(" stdout ") + chalk3.whiteBright(line)}
501
475
  `);
502
476
  }
503
477
  });
@@ -509,7 +483,7 @@ async function execute2(cmd, options) {
509
483
  stream1.on("data", (line) => {
510
484
  line = line.toString();
511
485
  if (displayError) {
512
- process.stdout.write(`${chalk4.white(" stderr ") + chalk4.red(line)}
486
+ process.stdout.write(`${chalk3.white(" stderr ") + chalk3.red(line)}
513
487
  `);
514
488
  }
515
489
  });
@@ -553,17 +527,107 @@ async function execute_openssl(cmd, options) {
553
527
  assert3(options.openssl_conf);
554
528
  setEnv("OPENSSL_CONF", options.openssl_conf);
555
529
  if (!g_config.silent) {
556
- warningLog(chalk4.cyan(" OPENSSL_CONF"), process.env.OPENSSL_CONF);
557
- warningLog(chalk4.cyan(" RANDFILE "), process.env.RANDFILE);
558
- warningLog(chalk4.cyan(" CMD openssl "), chalk4.cyanBright(cmd));
530
+ warningLog(chalk3.cyan(" OPENSSL_CONF"), process.env.OPENSSL_CONF);
531
+ warningLog(chalk3.cyan(" RANDFILE "), process.env.RANDFILE);
532
+ warningLog(chalk3.cyan(" CMD openssl "), chalk3.cyanBright(cmd));
559
533
  }
560
534
  await ensure_openssl_installed();
561
535
  return await execute2(`${quote(opensslPath)} ${cmd}`, options);
562
536
  }
563
537
 
538
+ // packages/node-opcua-pki/lib/pki/toolbox_pfx.ts
539
+ var q = quote;
540
+ var n2 = makePath;
541
+ async function createPFX(options) {
542
+ const { certificateFile, privateKeyFile, outputFile, passphrase = "", caCertificateFiles } = options;
543
+ assert4(fs4.existsSync(certificateFile), `Certificate file does not exist: ${certificateFile}`);
544
+ assert4(fs4.existsSync(privateKeyFile), `Private key file does not exist: ${privateKeyFile}`);
545
+ let cmd = `pkcs12 -export`;
546
+ cmd += ` -in ${q(n2(certificateFile))}`;
547
+ cmd += ` -inkey ${q(n2(privateKeyFile))}`;
548
+ if (caCertificateFiles) {
549
+ for (const caFile of caCertificateFiles) {
550
+ assert4(fs4.existsSync(caFile), `CA certificate file does not exist: ${caFile}`);
551
+ cmd += ` -certfile ${q(n2(caFile))}`;
552
+ }
553
+ }
554
+ cmd += ` -out ${q(n2(outputFile))}`;
555
+ cmd += ` -passout pass:${passphrase}`;
556
+ await execute_openssl(cmd, {});
557
+ }
558
+ async function extractCertificateFromPFX(options) {
559
+ const { pfxFile, passphrase = "" } = options;
560
+ assert4(fs4.existsSync(pfxFile), `PFX file does not exist: ${pfxFile}`);
561
+ const cmd = `pkcs12 -in ${q(n2(pfxFile))} -clcerts -nokeys -nodes -passin pass:${passphrase}`;
562
+ return await execute_openssl(cmd, {});
563
+ }
564
+ async function extractPrivateKeyFromPFX(options) {
565
+ const { pfxFile, passphrase = "" } = options;
566
+ assert4(fs4.existsSync(pfxFile), `PFX file does not exist: ${pfxFile}`);
567
+ const cmd = `pkcs12 -in ${q(n2(pfxFile))} -nocerts -nodes -passin pass:${passphrase}`;
568
+ return await execute_openssl(cmd, {});
569
+ }
570
+ async function extractCACertificatesFromPFX(options) {
571
+ const { pfxFile, passphrase = "" } = options;
572
+ assert4(fs4.existsSync(pfxFile), `PFX file does not exist: ${pfxFile}`);
573
+ const cmd = `pkcs12 -in ${q(n2(pfxFile))} -cacerts -nokeys -nodes -passin pass:${passphrase}`;
574
+ return await execute_openssl(cmd, {});
575
+ }
576
+ async function extractAllFromPFX(options) {
577
+ const [certificate, privateKey, caCertificates] = await Promise.all([
578
+ extractCertificateFromPFX(options),
579
+ extractPrivateKeyFromPFX(options),
580
+ extractCACertificatesFromPFX(options)
581
+ ]);
582
+ return { certificate, privateKey, caCertificates };
583
+ }
584
+ async function convertPFXtoPEM(pfxFile, pemFile, passphrase = "") {
585
+ assert4(fs4.existsSync(pfxFile), `PFX file does not exist: ${pfxFile}`);
586
+ const cmd = `pkcs12 -in ${q(n2(pfxFile))} -out ${q(n2(pemFile))} -nodes -passin pass:${passphrase}`;
587
+ await execute_openssl(cmd, {});
588
+ }
589
+ async function dumpPFX(pfxFile, passphrase = "") {
590
+ assert4(fs4.existsSync(pfxFile), `PFX file does not exist: ${pfxFile}`);
591
+ const cmd = `pkcs12 -in ${q(n2(pfxFile))} -info -nodes -passin pass:${passphrase}`;
592
+ return await execute_openssl(cmd, {});
593
+ }
594
+
595
+ // packages/node-opcua-pki/lib/toolbox/display.ts
596
+ import chalk4 from "chalk";
597
+ function displayTitle(str) {
598
+ if (!g_config.silent) {
599
+ warningLog("");
600
+ warningLog(chalk4.yellowBright(str));
601
+ warningLog(chalk4.yellow(new Array(str.length + 1).join("=")), "\n");
602
+ }
603
+ }
604
+ function displaySubtitle(str) {
605
+ if (!g_config.silent) {
606
+ warningLog("");
607
+ warningLog(` ${chalk4.yellowBright(str)}`);
608
+ warningLog(` ${chalk4.white(new Array(str.length + 1).join("-"))}`, "\n");
609
+ }
610
+ }
611
+ function display(str) {
612
+ if (!g_config.silent) {
613
+ warningLog(` ${str}`);
614
+ }
615
+ }
616
+
617
+ // packages/node-opcua-pki/lib/toolbox/with_openssl/index.ts
618
+ import exp from "constants";
619
+
620
+ // packages/node-opcua-pki/lib/toolbox/with_openssl/create_certificate_signing_request.ts
621
+ import assert6 from "assert";
622
+ import fs6 from "fs";
623
+ import path4 from "path";
624
+
625
+ // packages/node-opcua-pki/lib/misc/subject.ts
626
+ import { Subject } from "node-opcua-crypto";
627
+
564
628
  // packages/node-opcua-pki/lib/toolbox/with_openssl/toolbox.ts
565
- import assert4 from "assert";
566
- import fs4 from "fs";
629
+ import assert5 from "assert";
630
+ import fs5 from "fs";
567
631
  import path3 from "path";
568
632
  function openssl_require2DigitYearInDate() {
569
633
  if (!g_config.opensslVersion) {
@@ -578,13 +642,13 @@ var _counter = 0;
578
642
  function generateStaticConfig(configPath, options) {
579
643
  const prePath = options?.cwd || "";
580
644
  const originalFilename = !path3.isAbsolute(configPath) ? path3.join(prePath, configPath) : configPath;
581
- let staticConfig = fs4.readFileSync(originalFilename, { encoding: "utf8" });
645
+ let staticConfig = fs5.readFileSync(originalFilename, { encoding: "utf8" });
582
646
  for (const envVar of getEnvironmentVarNames()) {
583
647
  staticConfig = staticConfig.replace(new RegExp(envVar.pattern, "gi"), getEnv(envVar.key));
584
648
  }
585
649
  const staticConfigPath = `${configPath}.${process.pid}-${_counter++}.tmp`;
586
650
  const temporaryConfigPath = !path3.isAbsolute(configPath) ? path3.join(prePath, staticConfigPath) : staticConfigPath;
587
- fs4.writeFileSync(temporaryConfigPath, staticConfig);
651
+ fs5.writeFileSync(temporaryConfigPath, staticConfig);
588
652
  if (options?.cwd) {
589
653
  return path3.relative(options.cwd, temporaryConfigPath);
590
654
  } else {
@@ -609,8 +673,38 @@ function x509Date(date) {
609
673
  }
610
674
  }
611
675
 
676
+ // packages/node-opcua-pki/lib/toolbox/with_openssl/create_certificate_signing_request.ts
677
+ var q2 = quote;
678
+ var n3 = makePath;
679
+ async function createCertificateSigningRequestWithOpenSSL(certificateSigningRequestFilename, params) {
680
+ assert6(params);
681
+ assert6(params.rootDir);
682
+ assert6(params.configFile);
683
+ assert6(params.privateKey);
684
+ assert6(typeof params.privateKey === "string");
685
+ assert6(fs6.existsSync(params.configFile), `config file must exist ${params.configFile}`);
686
+ assert6(fs6.existsSync(params.privateKey), `Private key must exist${params.privateKey}`);
687
+ assert6(fs6.existsSync(params.rootDir), "RootDir key must exist");
688
+ assert6(typeof certificateSigningRequestFilename === "string");
689
+ processAltNames(params);
690
+ const configFile = generateStaticConfig(params.configFile, { cwd: params.rootDir });
691
+ const options = { cwd: params.rootDir, openssl_conf: path4.relative(params.rootDir, configFile) };
692
+ const configOption = ` -config ${q2(n3(configFile))}`;
693
+ const subject = params.subject ? new Subject(params.subject).toString() : void 0;
694
+ const subjectOptions = subject ? ` -subj "${subject}"` : "";
695
+ displaySubtitle("- Creating a Certificate Signing Request with openssl");
696
+ await execute_openssl(
697
+ "req -new -sha256 -batch -text " + configOption + " -key " + q2(n3(params.privateKey)) + subjectOptions + " -out " + q2(n3(certificateSigningRequestFilename)),
698
+ options
699
+ );
700
+ }
701
+
702
+ // packages/node-opcua-pki/lib/pki/templates/simple_config_template.cnf.ts
703
+ 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, cRLSign, keyCertSign\nsubjectAltName = $ENV::ALTNAME\n\n[ v3_ca_signed]\nsubjectKeyIdentifier = hash\nauthorityKeyIdentifier = keyid,issuer\nbasicConstraints = critical, CA:FALSE\nkeyUsage = nonRepudiation, digitalSignature, keyEncipherment, dataEncipherment, keyCertSign\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, keyCertSign\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';
704
+ var simple_config_template_cnf_default = config;
705
+
612
706
  // packages/node-opcua-pki/lib/ca/templates/ca_config_template.cnf.ts
613
- var config = `#.........DO NOT MODIFY BY HAND .........................
707
+ var config2 = `#.........DO NOT MODIFY BY HAND .........................
614
708
  [ ca ]
615
709
  default_ca = CA_default
616
710
  [ CA_default ]
@@ -739,22 +833,23 @@ subjectAltName = $ENV::ALTNAME
739
833
  #issuerAltName = issuer:copy
740
834
  authorityKeyIdentifier = keyid:always,issuer:always
741
835
  #authorityInfoAccess = @issuer_info`;
742
- var ca_config_template_cnf_default = config;
836
+ var ca_config_template_cnf_default = config2;
743
837
 
744
838
  // packages/node-opcua-pki/lib/ca/certificate_authority.ts
745
839
  var defaultSubject = "/C=FR/ST=IDF/L=Paris/O=Local NODE-OPCUA Certificate Authority/CN=NodeOPCUA-CA";
746
840
  var configurationFileTemplate = ca_config_template_cnf_default;
747
- var config2 = {
841
+ var configurationFileSimpleTemplate = simple_config_template_cnf_default;
842
+ var config3 = {
748
843
  certificateDir: "INVALID",
749
844
  forceCA: false,
750
845
  pkiDir: "INVALID"
751
846
  };
752
- var n2 = makePath;
753
- var q = quote;
847
+ var n4 = makePath;
848
+ var q3 = quote;
754
849
  function octetStringToIpAddress(a) {
755
850
  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();
756
851
  }
757
- assert6(octetStringToIpAddress("c07b9179") === "192.123.145.121");
852
+ assert7(octetStringToIpAddress("c07b9179") === "192.123.145.121");
758
853
  async function construct_CertificateAuthority(certificateAuthority) {
759
854
  const subject = certificateAuthority.subject;
760
855
  const caRootDir = path5.resolve(certificateAuthority.rootDir);
@@ -769,49 +864,49 @@ async function construct_CertificateAuthority(certificateAuthority) {
769
864
  await make_folders();
770
865
  async function construct_default_files() {
771
866
  const serial = path5.join(caRootDir, "serial");
772
- if (!fs6.existsSync(serial)) {
773
- await fs6.promises.writeFile(serial, "1000");
867
+ if (!fs7.existsSync(serial)) {
868
+ await fs7.promises.writeFile(serial, "1000");
774
869
  }
775
870
  const crlNumber = path5.join(caRootDir, "crlnumber");
776
- if (!fs6.existsSync(crlNumber)) {
777
- await fs6.promises.writeFile(crlNumber, "1000");
871
+ if (!fs7.existsSync(crlNumber)) {
872
+ await fs7.promises.writeFile(crlNumber, "1000");
778
873
  }
779
874
  const indexFile = path5.join(caRootDir, "index.txt");
780
- if (!fs6.existsSync(indexFile)) {
781
- await fs6.promises.writeFile(indexFile, "");
875
+ if (!fs7.existsSync(indexFile)) {
876
+ await fs7.promises.writeFile(indexFile, "");
782
877
  }
783
878
  }
784
879
  await construct_default_files();
785
- const caKeyExists = fs6.existsSync(path5.join(caRootDir, "private/cakey.pem"));
786
- const caCertExists = fs6.existsSync(path5.join(caRootDir, "public/cacert.pem"));
787
- if (caKeyExists && caCertExists && !config2.forceCA) {
880
+ const caKeyExists = fs7.existsSync(path5.join(caRootDir, "private/cakey.pem"));
881
+ const caCertExists = fs7.existsSync(path5.join(caRootDir, "public/cacert.pem"));
882
+ if (caKeyExists && caCertExists && !config3.forceCA) {
788
883
  debugLog("CA private key and certificate already exist ... skipping");
789
884
  return;
790
885
  }
791
886
  if (caKeyExists && !caCertExists) {
792
887
  debugLog("CA private key exists but cacert.pem is missing \u2014 rebuilding CA");
793
- fs6.unlinkSync(path5.join(caRootDir, "private/cakey.pem"));
888
+ fs7.unlinkSync(path5.join(caRootDir, "private/cakey.pem"));
794
889
  const staleCsr = path5.join(caRootDir, "private/cakey.csr");
795
- if (fs6.existsSync(staleCsr)) {
796
- fs6.unlinkSync(staleCsr);
890
+ if (fs7.existsSync(staleCsr)) {
891
+ fs7.unlinkSync(staleCsr);
797
892
  }
798
893
  }
799
894
  displayTitle("Create Certificate Authority (CA)");
800
895
  const indexFileAttr = path5.join(caRootDir, "index.txt.attr");
801
- if (!fs6.existsSync(indexFileAttr)) {
802
- await fs6.promises.writeFile(indexFileAttr, "unique_subject = no");
896
+ if (!fs7.existsSync(indexFileAttr)) {
897
+ await fs7.promises.writeFile(indexFileAttr, "unique_subject = no");
803
898
  }
804
899
  const caConfigFile = certificateAuthority.configFile;
805
900
  if (1) {
806
901
  let data = configurationFileTemplate;
807
902
  data = makePath(data.replace(/%%ROOT_FOLDER%%/, caRootDir));
808
- await fs6.promises.writeFile(caConfigFile, data);
903
+ await fs7.promises.writeFile(caConfigFile, data);
809
904
  }
810
905
  const subjectOpt = ` -subj "${subject.toString()}" `;
811
906
  processAltNames({});
812
907
  const options = { cwd: caRootDir };
813
908
  const configFile = generateStaticConfig("conf/caconfig.cnf", options);
814
- const configOption = ` -config ${q(n2(configFile))}`;
909
+ const configOption = ` -config ${q3(n4(configFile))}`;
815
910
  const keySize = certificateAuthority.keySize;
816
911
  const privateKeyFilename = path5.join(caRootDir, "private/cakey.pem");
817
912
  const csrFilename = path5.join(caRootDir, "private/cakey.csr");
@@ -819,14 +914,26 @@ async function construct_CertificateAuthority(certificateAuthority) {
819
914
  await generatePrivateKeyFile(privateKeyFilename, keySize);
820
915
  displayTitle("Generate a certificate request for the CA key");
821
916
  await execute_openssl(
822
- "req -new -sha256 -text -extensions v3_ca_req" + configOption + " -key " + q(n2(privateKeyFilename)) + " -out " + q(n2(csrFilename)) + " " + subjectOpt,
823
- options
824
- );
825
- displayTitle("Generate CA Certificate (self-signed)");
826
- await execute_openssl(
827
- " x509 -sha256 -req -days 3650 -text -extensions v3_ca -extfile " + q(n2(configFile)) + " -in private/cakey.csr -signkey " + q(n2(privateKeyFilename)) + " -out public/cacert.pem",
917
+ "req -new -sha256 -text -extensions v3_ca_req" + configOption + " -key " + q3(n4(privateKeyFilename)) + " -out " + q3(n4(csrFilename)) + " " + subjectOpt,
828
918
  options
829
919
  );
920
+ const issuerCA = certificateAuthority._issuerCA;
921
+ if (issuerCA) {
922
+ displayTitle("Generate CA Certificate (signed by issuer CA)");
923
+ const issuerCert = path5.resolve(issuerCA.caCertificate);
924
+ const issuerKey = path5.resolve(issuerCA.rootDir, "private/cakey.pem");
925
+ const issuerSerial = path5.resolve(issuerCA.rootDir, "serial");
926
+ await execute_openssl(
927
+ " x509 -sha256 -req -days 3650 -text -extensions v3_ca -extfile " + q3(n4(configFile)) + " -in private/cakey.csr -CA " + q3(n4(issuerCert)) + " -CAkey " + q3(n4(issuerKey)) + " -CAserial " + q3(n4(issuerSerial)) + " -out public/cacert.pem",
928
+ options
929
+ );
930
+ } else {
931
+ displayTitle("Generate CA Certificate (self-signed)");
932
+ await execute_openssl(
933
+ " x509 -sha256 -req -days 3650 -text -extensions v3_ca -extfile " + q3(n4(configFile)) + " -in private/cakey.csr -signkey " + q3(n4(privateKeyFilename)) + " -out public/cacert.pem",
934
+ options
935
+ );
936
+ }
830
937
  displaySubtitle("generate initial CRL (Certificate Revocation List)");
831
938
  await regenerateCrl(certificateAuthority.revocationList, configOption, options);
832
939
  displayTitle("Create Certificate Authority (CA) ---> DONE");
@@ -836,7 +943,7 @@ async function regenerateCrl(revocationList, configOption, options) {
836
943
  await execute_openssl(`ca -gencrl ${configOption} -out crl/revocation_list.crl`, options);
837
944
  await execute_openssl("crl -in crl/revocation_list.crl -out crl/revocation_list.der -outform der", options);
838
945
  displaySubtitle("Display (Certificate Revocation List)");
839
- await execute_openssl(`crl -in ${q(n2(revocationList))} -text -noout`, options);
946
+ await execute_openssl(`crl -in ${q3(n4(revocationList))} -text -noout`, options);
840
947
  }
841
948
  function parseOpenSSLDate(dateStr) {
842
949
  const raw = dateStr?.split(",")[0] ?? "";
@@ -857,12 +964,15 @@ var CertificateAuthority = class {
857
964
  location;
858
965
  /** X.500 subject of the CA certificate. */
859
966
  subject;
967
+ /** @internal Parent CA (undefined for root CAs). */
968
+ _issuerCA;
860
969
  constructor(options) {
861
- assert6(Object.prototype.hasOwnProperty.call(options, "location"));
862
- assert6(Object.prototype.hasOwnProperty.call(options, "keySize"));
970
+ assert7(Object.prototype.hasOwnProperty.call(options, "location"));
971
+ assert7(Object.prototype.hasOwnProperty.call(options, "keySize"));
863
972
  this.location = options.location;
864
973
  this.keySize = options.keySize || 2048;
865
974
  this.subject = new Subject2(options.subject || defaultSubject);
975
+ this._issuerCA = options.issuerCA;
866
976
  }
867
977
  /** Absolute path to the CA root directory (alias for {@link location}). */
868
978
  get rootDir() {
@@ -876,6 +986,18 @@ var CertificateAuthority = class {
876
986
  get caCertificate() {
877
987
  return makePath(this.rootDir, "./public/cacert.pem");
878
988
  }
989
+ /**
990
+ * Path to the issuer certificate chain (`public/issuer_chain.pem`).
991
+ *
992
+ * This file is created by {@link installCACertificate} when the
993
+ * provided cert file contains additional issuer certificates
994
+ * (e.g. intermediate + root). It is appended to signed certs
995
+ * by {@link constructCertificateChain} to produce a full chain
996
+ * per OPC UA Part 6 §6.2.6.
997
+ */
998
+ get issuerCertificateChain() {
999
+ return makePath(this.rootDir, "./public/issuer_chain.pem");
1000
+ }
879
1001
  /**
880
1002
  * Path to the current Certificate Revocation List in DER format.
881
1003
  * (`crl/revocation_list.der`)
@@ -933,10 +1055,10 @@ var CertificateAuthority = class {
933
1055
  */
934
1056
  getCRLDER() {
935
1057
  const crlPath = this.revocationListDER;
936
- if (!fs6.existsSync(crlPath)) {
1058
+ if (!fs7.existsSync(crlPath)) {
937
1059
  return Buffer.alloc(0);
938
1060
  }
939
- return fs6.readFileSync(crlPath);
1061
+ return fs7.readFileSync(crlPath);
940
1062
  }
941
1063
  /**
942
1064
  * Return the current Certificate Revocation List as a
@@ -946,10 +1068,10 @@ var CertificateAuthority = class {
946
1068
  */
947
1069
  getCRLPEM() {
948
1070
  const crlPath = this.revocationList;
949
- if (!fs6.existsSync(crlPath)) {
1071
+ if (!fs7.existsSync(crlPath)) {
950
1072
  return "";
951
1073
  }
952
- const raw = fs6.readFileSync(crlPath, "utf-8");
1074
+ const raw = fs7.readFileSync(crlPath, "utf-8");
953
1075
  const beginMarker = "-----BEGIN X509 CRL-----";
954
1076
  const idx = raw.indexOf(beginMarker);
955
1077
  if (idx > 0) {
@@ -1002,7 +1124,7 @@ var CertificateAuthority = class {
1002
1124
  getCertificateBySerial(serial) {
1003
1125
  const upper = serial.toUpperCase();
1004
1126
  const certFile = path5.join(this.rootDir, "certs", `${upper}.pem`);
1005
- if (!fs6.existsSync(certFile)) {
1127
+ if (!fs7.existsSync(certFile)) {
1006
1128
  return void 0;
1007
1129
  }
1008
1130
  const pem = readCertificatePEM(certFile);
@@ -1031,10 +1153,10 @@ var CertificateAuthority = class {
1031
1153
  */
1032
1154
  _parseIndexTxt() {
1033
1155
  const indexPath = this.indexFile;
1034
- if (!fs6.existsSync(indexPath)) {
1156
+ if (!fs7.existsSync(indexPath)) {
1035
1157
  return [];
1036
1158
  }
1037
- const content = fs6.readFileSync(indexPath, "utf-8");
1159
+ const content = fs7.readFileSync(indexPath, "utf-8");
1038
1160
  const lines = content.split("\n").filter((l) => l.trim().length > 0);
1039
1161
  const records = [];
1040
1162
  for (const line of lines) {
@@ -1088,25 +1210,145 @@ var CertificateAuthority = class {
1088
1210
  * internally so that callers can work with in-memory
1089
1211
  * buffers only.
1090
1212
  *
1213
+ * The CA can override fields from the CSR by passing
1214
+ * `options.dns`, `options.ip`, `options.applicationUri`,
1215
+ * `options.startDate`, or `options.subject`.
1216
+ *
1091
1217
  * @param csrDer - the CSR as a DER-encoded buffer
1092
- * @param options - signing options
1093
- * @param options.validity - certificate validity in days
1094
- * (default: 365)
1218
+ * @param options - signing options and CA overrides
1095
1219
  * @returns the signed certificate as a DER-encoded buffer
1096
1220
  */
1097
1221
  async signCertificateRequestFromDER(csrDer, options) {
1098
1222
  const validity = options?.validity ?? 365;
1099
- const tmpDir = await fs6.promises.mkdtemp(path5.join(os3.tmpdir(), "pki-sign-"));
1223
+ const tmpDir = await fs7.promises.mkdtemp(path5.join(os3.tmpdir(), "pki-sign-"));
1100
1224
  try {
1101
1225
  const csrFile = path5.join(tmpDir, "request.csr");
1102
1226
  const certFile = path5.join(tmpDir, "certificate.pem");
1103
1227
  const csrPem = toPem(csrDer, "CERTIFICATE REQUEST");
1104
- await fs6.promises.writeFile(csrFile, csrPem, "utf-8");
1105
- await this.signCertificateRequest(certFile, csrFile, { validity });
1228
+ await fs7.promises.writeFile(csrFile, csrPem, "utf-8");
1229
+ const signingParams = { validity };
1230
+ if (options?.startDate) signingParams.startDate = options.startDate;
1231
+ if (options?.dns) signingParams.dns = options.dns;
1232
+ if (options?.ip) signingParams.ip = options.ip;
1233
+ if (options?.applicationUri) signingParams.applicationUri = options.applicationUri;
1234
+ if (options?.subject) signingParams.subject = options.subject;
1235
+ await this.signCertificateRequest(certFile, csrFile, signingParams);
1106
1236
  const certPem = readCertificatePEM(certFile);
1107
1237
  return convertPEMtoDER(certPem);
1108
1238
  } finally {
1109
- await fs6.promises.rm(tmpDir, {
1239
+ await fs7.promises.rm(tmpDir, {
1240
+ recursive: true,
1241
+ force: true
1242
+ });
1243
+ }
1244
+ }
1245
+ /**
1246
+ * Generate a new RSA key pair, create an internal CSR, sign it
1247
+ * with this CA, and return both the certificate and private key
1248
+ * as DER-encoded buffers.
1249
+ *
1250
+ * The private key is **never stored** by the CA — it exists only
1251
+ * in a temporary directory that is cleaned up after the operation.
1252
+ *
1253
+ * This is used by `StartNewKeyPairRequest` (OPC UA Part 12) for
1254
+ * constrained devices that cannot generate their own keys.
1255
+ *
1256
+ * @param options - key generation and certificate parameters
1257
+ * @returns `{ certificateDer, privateKey }` — certificate as DER,
1258
+ * private key as a branded `PrivateKey` buffer
1259
+ */
1260
+ async generateKeyPairAndSignDER(options) {
1261
+ const keySize = options.keySize ?? 2048;
1262
+ const validity = options.validity ?? 365;
1263
+ const startDate = options.startDate ?? /* @__PURE__ */ new Date();
1264
+ const tmpDir = await fs7.promises.mkdtemp(path5.join(os3.tmpdir(), "pki-keygen-"));
1265
+ try {
1266
+ const privateKeyFile = path5.join(tmpDir, "private_key.pem");
1267
+ await generatePrivateKeyFile(privateKeyFile, keySize);
1268
+ const configFile = path5.join(tmpDir, "openssl.cnf");
1269
+ await fs7.promises.writeFile(configFile, configurationFileSimpleTemplate, "utf-8");
1270
+ const csrFile = path5.join(tmpDir, "request.csr");
1271
+ await createCertificateSigningRequestWithOpenSSL(csrFile, {
1272
+ rootDir: tmpDir,
1273
+ configFile,
1274
+ privateKey: privateKeyFile,
1275
+ applicationUri: options.applicationUri,
1276
+ subject: options.subject,
1277
+ dns: options.dns ?? [],
1278
+ ip: options.ip ?? [],
1279
+ purpose: CertificatePurpose.ForApplication
1280
+ });
1281
+ const certFile = path5.join(tmpDir, "certificate.pem");
1282
+ await this.signCertificateRequest(certFile, csrFile, {
1283
+ applicationUri: options.applicationUri,
1284
+ dns: options.dns,
1285
+ ip: options.ip,
1286
+ startDate,
1287
+ validity
1288
+ });
1289
+ const certPem = readCertificatePEM(certFile);
1290
+ const certificateDer = convertPEMtoDER(certPem);
1291
+ const privateKey = readPrivateKey(privateKeyFile);
1292
+ return { certificateDer, privateKey };
1293
+ } finally {
1294
+ await fs7.promises.rm(tmpDir, {
1295
+ recursive: true,
1296
+ force: true
1297
+ });
1298
+ }
1299
+ }
1300
+ /**
1301
+ * Generate a new RSA key pair, create an internal CSR, sign it
1302
+ * with this CA, and return the result as a PKCS#12 (PFX)
1303
+ * buffer bundling the certificate, private key, and CA chain.
1304
+ *
1305
+ * The private key is **never stored** by the CA — it exists only
1306
+ * in a temporary directory that is cleaned up after the operation.
1307
+ *
1308
+ * @param options - key generation, certificate, and PFX options
1309
+ * @returns the PFX as a `Buffer`
1310
+ */
1311
+ async generateKeyPairAndSignPFX(options) {
1312
+ const keySize = options.keySize ?? 2048;
1313
+ const validity = options.validity ?? 365;
1314
+ const startDate = options.startDate ?? /* @__PURE__ */ new Date();
1315
+ const passphrase = options.passphrase ?? "";
1316
+ const tmpDir = await fs7.promises.mkdtemp(path5.join(os3.tmpdir(), "pki-keygen-pfx-"));
1317
+ try {
1318
+ const privateKeyFile = path5.join(tmpDir, "private_key.pem");
1319
+ await generatePrivateKeyFile(privateKeyFile, keySize);
1320
+ const configFile = path5.join(tmpDir, "openssl.cnf");
1321
+ await fs7.promises.writeFile(configFile, configurationFileSimpleTemplate, "utf-8");
1322
+ const csrFile = path5.join(tmpDir, "request.csr");
1323
+ await createCertificateSigningRequestWithOpenSSL(csrFile, {
1324
+ rootDir: tmpDir,
1325
+ configFile,
1326
+ privateKey: privateKeyFile,
1327
+ applicationUri: options.applicationUri,
1328
+ subject: options.subject,
1329
+ dns: options.dns ?? [],
1330
+ ip: options.ip ?? [],
1331
+ purpose: CertificatePurpose.ForApplication
1332
+ });
1333
+ const certFile = path5.join(tmpDir, "certificate.pem");
1334
+ await this.signCertificateRequest(certFile, csrFile, {
1335
+ applicationUri: options.applicationUri,
1336
+ dns: options.dns,
1337
+ ip: options.ip,
1338
+ startDate,
1339
+ validity
1340
+ });
1341
+ const pfxFile = path5.join(tmpDir, "bundle.pfx");
1342
+ await createPFX({
1343
+ certificateFile: certFile,
1344
+ privateKeyFile,
1345
+ outputFile: pfxFile,
1346
+ passphrase,
1347
+ caCertificateFiles: [this.caCertificate]
1348
+ });
1349
+ return await fs7.promises.readFile(pfxFile);
1350
+ } finally {
1351
+ await fs7.promises.rm(tmpDir, {
1110
1352
  recursive: true,
1111
1353
  force: true
1112
1354
  });
@@ -1128,7 +1370,7 @@ var CertificateAuthority = class {
1128
1370
  const info = exploreCertificate(certDer);
1129
1371
  const serial = info.tbsCertificate.serialNumber.replace(/:/g, "").toUpperCase();
1130
1372
  const storedCertFile = path5.join(this.rootDir, "certs", `${serial}.pem`);
1131
- if (!fs6.existsSync(storedCertFile)) {
1373
+ if (!fs7.existsSync(storedCertFile)) {
1132
1374
  throw new Error(`Cannot revoke: no stored certificate found for serial ${serial} at ${storedCertFile}`);
1133
1375
  }
1134
1376
  await this.revokeCertificate(storedCertFile, {
@@ -1143,6 +1385,204 @@ var CertificateAuthority = class {
1143
1385
  async initialize() {
1144
1386
  await construct_CertificateAuthority(this);
1145
1387
  }
1388
+ /**
1389
+ * Initialize the CA directory structure and generate the
1390
+ * private key + CSR **without signing**.
1391
+ *
1392
+ * Use this when the CA certificate will be signed by an
1393
+ * external (third-party) root CA. After receiving the signed
1394
+ * certificate, call {@link installCACertificate} to complete
1395
+ * the setup.
1396
+ *
1397
+ * **Idempotent / restart-safe:**
1398
+ * - If the CA certificate exists and is valid → `{ status: "ready" }`
1399
+ * - If the CA certificate has expired → `{ status: "expired", csrPath, expiryDate }`
1400
+ * (a new CSR is generated, preserving the existing private key)
1401
+ * - If key + CSR exist but no cert (restart before install) →
1402
+ * `{ status: "pending", csrPath }` without regenerating
1403
+ * - Otherwise → generates key + CSR → `{ status: "created", csrPath }`
1404
+ *
1405
+ * @returns an {@link InitializeCSRResult} describing the CA state
1406
+ */
1407
+ async initializeCSR() {
1408
+ const caRootDir = path5.resolve(this.rootDir);
1409
+ mkdirRecursiveSync(caRootDir);
1410
+ for (const dir of ["private", "public", "certs", "crl", "conf"]) {
1411
+ mkdirRecursiveSync(path5.join(caRootDir, dir));
1412
+ }
1413
+ const caCertFile = this.caCertificate;
1414
+ const privateKeyFile = path5.join(caRootDir, "private/cakey.pem");
1415
+ const csrFile = path5.join(caRootDir, "private/cakey.csr");
1416
+ if (fs7.existsSync(caCertFile)) {
1417
+ const certDer = convertPEMtoDER(readCertificatePEM(caCertFile));
1418
+ const certInfo = exploreCertificate(certDer);
1419
+ const notAfter = certInfo.tbsCertificate.validity.notAfter;
1420
+ if (notAfter.getTime() < Date.now()) {
1421
+ debugLog("CA certificate has expired \u2014 generating renewal CSR");
1422
+ await this._generateCSR(caRootDir, privateKeyFile, csrFile);
1423
+ return { status: "expired", csrPath: csrFile, expiryDate: notAfter };
1424
+ }
1425
+ debugLog("CA certificate already exists and is valid \u2014 ready");
1426
+ return { status: "ready" };
1427
+ }
1428
+ if (fs7.existsSync(privateKeyFile) && fs7.existsSync(csrFile)) {
1429
+ debugLog("CA key + CSR already exist \u2014 pending external signing");
1430
+ return { status: "pending", csrPath: csrFile };
1431
+ }
1432
+ const serial = path5.join(caRootDir, "serial");
1433
+ if (!fs7.existsSync(serial)) {
1434
+ await fs7.promises.writeFile(serial, "1000");
1435
+ }
1436
+ const crlNumber = path5.join(caRootDir, "crlnumber");
1437
+ if (!fs7.existsSync(crlNumber)) {
1438
+ await fs7.promises.writeFile(crlNumber, "1000");
1439
+ }
1440
+ const indexFile = path5.join(caRootDir, "index.txt");
1441
+ if (!fs7.existsSync(indexFile)) {
1442
+ await fs7.promises.writeFile(indexFile, "");
1443
+ }
1444
+ const indexFileAttr = path5.join(caRootDir, "index.txt.attr");
1445
+ if (!fs7.existsSync(indexFileAttr)) {
1446
+ await fs7.promises.writeFile(indexFileAttr, "unique_subject = no");
1447
+ }
1448
+ const caConfigFile = this.configFile;
1449
+ let data = configurationFileTemplate;
1450
+ data = makePath(data.replace(/%%ROOT_FOLDER%%/, caRootDir));
1451
+ await fs7.promises.writeFile(caConfigFile, data);
1452
+ if (!fs7.existsSync(privateKeyFile)) {
1453
+ await generatePrivateKeyFile(privateKeyFile, this.keySize);
1454
+ }
1455
+ await this._generateCSR(caRootDir, privateKeyFile, csrFile);
1456
+ return { status: "created", csrPath: csrFile };
1457
+ }
1458
+ /**
1459
+ * Check whether the CA certificate needs renewal and, if so,
1460
+ * generate a new CSR for re-signing by the external root CA.
1461
+ *
1462
+ * Use this while the CA is running to detect upcoming expiry
1463
+ * **before** it actually expires. The existing private key is
1464
+ * preserved so previously issued certs remain valid.
1465
+ *
1466
+ * @param thresholdDays - number of days before expiry at which
1467
+ * to trigger renewal (default: 30)
1468
+ * @returns an {@link InitializeCSRResult} — `"expired"` if
1469
+ * renewal is needed, `"ready"` if the cert is still valid
1470
+ */
1471
+ async renewCSR(thresholdDays = 30) {
1472
+ const caRootDir = path5.resolve(this.rootDir);
1473
+ const caCertFile = this.caCertificate;
1474
+ const privateKeyFile = path5.join(caRootDir, "private/cakey.pem");
1475
+ const csrFile = path5.join(caRootDir, "private/cakey.csr");
1476
+ if (!fs7.existsSync(caCertFile)) {
1477
+ return this.initializeCSR();
1478
+ }
1479
+ const certDer = convertPEMtoDER(readCertificatePEM(caCertFile));
1480
+ const certInfo = exploreCertificate(certDer);
1481
+ const notAfter = certInfo.tbsCertificate.validity.notAfter;
1482
+ const thresholdMs = thresholdDays * 24 * 60 * 60 * 1e3;
1483
+ if (notAfter.getTime() - Date.now() < thresholdMs) {
1484
+ debugLog(`CA certificate expires within ${thresholdDays} days \u2014 generating renewal CSR`);
1485
+ await this._generateCSR(caRootDir, privateKeyFile, csrFile);
1486
+ return { status: "expired", csrPath: csrFile, expiryDate: notAfter };
1487
+ }
1488
+ return { status: "ready" };
1489
+ }
1490
+ /**
1491
+ * Generate a CSR using the existing private key.
1492
+ * @internal
1493
+ */
1494
+ async _generateCSR(caRootDir, privateKeyFile, csrFile) {
1495
+ const subjectOpt = ` -subj "${this.subject.toString()}" `;
1496
+ processAltNames({});
1497
+ const options = { cwd: caRootDir };
1498
+ const configFile = generateStaticConfig("conf/caconfig.cnf", options);
1499
+ const configOption = ` -config ${q3(n4(configFile))}`;
1500
+ await execute_openssl(
1501
+ "req -new -sha256 -text -extensions v3_ca_req" + configOption + " -key " + q3(n4(privateKeyFile)) + " -out " + q3(n4(csrFile)) + " " + subjectOpt,
1502
+ options
1503
+ );
1504
+ }
1505
+ /**
1506
+ * Install an externally-signed CA certificate and generate
1507
+ * the initial CRL.
1508
+ *
1509
+ * Call this after {@link initializeCSR} once the external
1510
+ * root CA has signed the CSR.
1511
+ *
1512
+ * **Safety checks:**
1513
+ * - Verifies that the certificate's public key matches the
1514
+ * CA private key before installing.
1515
+ *
1516
+ * @param signedCertFile - path to the PEM-encoded signed
1517
+ * CA certificate (issued by the external root CA)
1518
+ * @returns an {@link InstallCACertificateResult} with
1519
+ * `status: "success"` or `status: "error"` and a `reason`
1520
+ */
1521
+ async installCACertificate(signedCertFile) {
1522
+ const caRootDir = path5.resolve(this.rootDir);
1523
+ const caCertFile = this.caCertificate;
1524
+ const privateKeyFile = path5.join(caRootDir, "private/cakey.pem");
1525
+ const fullPem = await fs7.promises.readFile(signedCertFile, "utf8");
1526
+ const pemBlocks = fullPem.match(/-----BEGIN CERTIFICATE-----[\s\S]*?-----END CERTIFICATE-----/g);
1527
+ if (!pemBlocks || pemBlocks.length === 0) {
1528
+ return {
1529
+ status: "error",
1530
+ reason: "no_certificate_found",
1531
+ message: "The provided file does not contain any PEM-encoded certificate."
1532
+ };
1533
+ }
1534
+ const certDer = convertPEMtoDER(pemBlocks[0]);
1535
+ const privateKey = readPrivateKey(privateKeyFile);
1536
+ if (!certificateMatchesPrivateKey(certDer, privateKey)) {
1537
+ return {
1538
+ status: "error",
1539
+ reason: "certificate_key_mismatch",
1540
+ message: "The provided certificate does not match the CA private key. Ensure the certificate was signed from the CSR generated by initializeCSR()."
1541
+ };
1542
+ }
1543
+ await fs7.promises.writeFile(caCertFile, `${pemBlocks[0]}
1544
+ `);
1545
+ const issuerChainFile = this.issuerCertificateChain;
1546
+ if (pemBlocks.length > 1) {
1547
+ const issuerPem = `${pemBlocks.slice(1).join("\n")}
1548
+ `;
1549
+ await fs7.promises.writeFile(issuerChainFile, issuerPem);
1550
+ debugLog(`Stored ${pemBlocks.length - 1} issuer certificate(s) in issuer_chain.pem`);
1551
+ } else {
1552
+ if (fs7.existsSync(issuerChainFile)) {
1553
+ await fs7.promises.unlink(issuerChainFile);
1554
+ }
1555
+ }
1556
+ const options = { cwd: caRootDir };
1557
+ const configFile = generateStaticConfig("conf/caconfig.cnf", options);
1558
+ const configOption = ` -config ${q3(n4(configFile))}`;
1559
+ await regenerateCrl(this.revocationList, configOption, options);
1560
+ return { status: "success" };
1561
+ }
1562
+ /**
1563
+ * Sign a CSR with CA extensions (`v3_ca`), producing a
1564
+ * subordinate CA certificate.
1565
+ *
1566
+ * Unlike {@link signCertificateRequest} which signs with
1567
+ * end-entity extensions (SANs, etc.), this method signs
1568
+ * with `basicConstraints = CA:TRUE` and `keyUsage =
1569
+ * keyCertSign, cRLSign`.
1570
+ *
1571
+ * @param certFile - output path for the signed CA cert (PEM)
1572
+ * @param csrFile - path to the subordinate CA's CSR
1573
+ * @param params - signing parameters
1574
+ */
1575
+ async signCACertificateRequest(certFile, csrFile, params) {
1576
+ const caRootDir = path5.resolve(this.rootDir);
1577
+ const options = { cwd: caRootDir };
1578
+ const configFile = generateStaticConfig("conf/caconfig.cnf", options);
1579
+ const validity = params.validity ?? 3650;
1580
+ await execute_openssl(
1581
+ ` x509 -sha256 -req -days ${validity} -text -extensions v3_ca -extfile ` + q3(n4(configFile)) + " -in " + q3(n4(csrFile)) + " -CA " + q3(n4(this.caCertificate)) + " -CAkey " + q3(n4(path5.join(caRootDir, "private/cakey.pem"))) + " -CAserial " + q3(n4(path5.join(caRootDir, "serial"))) + " -out " + q3(n4(certFile)),
1582
+ options
1583
+ );
1584
+ await this.constructCertificateChain(certFile);
1585
+ }
1146
1586
  /**
1147
1587
  * Rebuild the combined CA certificate + CRL file.
1148
1588
  *
@@ -1152,13 +1592,13 @@ var CertificateAuthority = class {
1152
1592
  */
1153
1593
  async constructCACertificateWithCRL() {
1154
1594
  const cacertWithCRL = this.caCertificateWithCrl;
1155
- if (fs6.existsSync(this.revocationList)) {
1156
- await fs6.promises.writeFile(
1595
+ if (fs7.existsSync(this.revocationList)) {
1596
+ await fs7.promises.writeFile(
1157
1597
  cacertWithCRL,
1158
- fs6.readFileSync(this.caCertificate, "utf8") + fs6.readFileSync(this.revocationList, "utf8")
1598
+ fs7.readFileSync(this.caCertificate, "utf8") + fs7.readFileSync(this.revocationList, "utf8")
1159
1599
  );
1160
1600
  } else {
1161
- await fs6.promises.writeFile(cacertWithCRL, fs6.readFileSync(this.caCertificate));
1601
+ await fs7.promises.writeFile(cacertWithCRL, fs7.readFileSync(this.caCertificate));
1162
1602
  }
1163
1603
  }
1164
1604
  /**
@@ -1168,14 +1608,15 @@ var CertificateAuthority = class {
1168
1608
  * @param certificate - path to the certificate file to extend
1169
1609
  */
1170
1610
  async constructCertificateChain(certificate) {
1171
- assert6(fs6.existsSync(certificate));
1172
- assert6(fs6.existsSync(this.caCertificate));
1611
+ assert7(fs7.existsSync(certificate));
1612
+ assert7(fs7.existsSync(this.caCertificate));
1173
1613
  debugLog(chalk5.yellow(" certificate file :"), chalk5.cyan(certificate));
1174
- await fs6.promises.writeFile(
1175
- certificate,
1176
- await fs6.promises.readFile(certificate, "utf8") + await fs6.promises.readFile(this.caCertificate, "utf8")
1177
- // + fs.readFileSync(this.revocationList)
1178
- );
1614
+ let chain = await fs7.promises.readFile(certificate, "utf8");
1615
+ chain += await fs7.promises.readFile(this.caCertificate, "utf8");
1616
+ if (fs7.existsSync(this.issuerCertificateChain)) {
1617
+ chain += await fs7.promises.readFile(this.issuerCertificateChain, "utf8");
1618
+ }
1619
+ await fs7.promises.writeFile(certificate, chain);
1179
1620
  }
1180
1621
  /**
1181
1622
  * Create a self-signed certificate using OpenSSL.
@@ -1185,8 +1626,8 @@ var CertificateAuthority = class {
1185
1626
  * @param params - certificate parameters (subject, validity, SANs)
1186
1627
  */
1187
1628
  async createSelfSignedCertificate(certificateFile, privateKey, params) {
1188
- assert6(typeof privateKey === "string");
1189
- assert6(fs6.existsSync(privateKey));
1629
+ assert7(typeof privateKey === "string");
1630
+ assert7(fs7.existsSync(privateKey));
1190
1631
  if (!certificateFileExist(certificateFile)) {
1191
1632
  return;
1192
1633
  }
@@ -1194,7 +1635,7 @@ var CertificateAuthority = class {
1194
1635
  adjustApplicationUri(params);
1195
1636
  processAltNames(params);
1196
1637
  const csrFile = `${certificateFile}_csr`;
1197
- assert6(csrFile);
1638
+ assert7(csrFile);
1198
1639
  const configFile = generateStaticConfig(this.configFile, { cwd: this.rootDir });
1199
1640
  const options = {
1200
1641
  cwd: this.rootDir,
@@ -1205,19 +1646,19 @@ var CertificateAuthority = class {
1205
1646
  const subjectOptions = subject && subject.length > 1 ? ` -subj ${subject} ` : "";
1206
1647
  displaySubtitle("- the certificate signing request");
1207
1648
  await execute_openssl(
1208
- "req -new -sha256 -text " + configOption + subjectOptions + " -batch -key " + q(n2(privateKey)) + " -out " + q(n2(csrFile)),
1649
+ "req -new -sha256 -text " + configOption + subjectOptions + " -batch -key " + q3(n4(privateKey)) + " -out " + q3(n4(csrFile)),
1209
1650
  options
1210
1651
  );
1211
1652
  displaySubtitle("- creating the self-signed certificate");
1212
1653
  await execute_openssl(
1213
- "ca -selfsign -keyfile " + q(n2(privateKey)) + " -startdate " + x509Date(params.startDate) + " -enddate " + x509Date(params.endDate) + " -batch -out " + q(n2(certificateFile)) + " -in " + q(n2(csrFile)),
1654
+ "ca -selfsign -keyfile " + q3(n4(privateKey)) + " -startdate " + x509Date(params.startDate) + " -enddate " + x509Date(params.endDate) + " -batch -out " + q3(n4(certificateFile)) + " -in " + q3(n4(csrFile)),
1214
1655
  options
1215
1656
  );
1216
1657
  displaySubtitle("- dump the certificate for a check");
1217
- await execute_openssl(`x509 -in ${q(n2(certificateFile))} -dates -fingerprint -purpose -noout`, {});
1658
+ await execute_openssl(`x509 -in ${q3(n4(certificateFile))} -dates -fingerprint -purpose -noout`, {});
1218
1659
  displaySubtitle("- verify self-signed certificate");
1219
- await execute_openssl_no_failure(`verify -verbose -CAfile ${q(n2(certificateFile))} ${q(n2(certificateFile))}`, options);
1220
- await fs6.promises.unlink(csrFile);
1660
+ await execute_openssl_no_failure(`verify -verbose -CAfile ${q3(n4(certificateFile))} ${q3(n4(certificateFile))}`, options);
1661
+ await fs7.promises.unlink(csrFile);
1221
1662
  }
1222
1663
  /**
1223
1664
  * Revoke a certificate and regenerate the CRL.
@@ -1246,22 +1687,22 @@ var CertificateAuthority = class {
1246
1687
  setEnv("ALTNAME", "");
1247
1688
  const randomFile = path5.join(this.rootDir, "random.rnd");
1248
1689
  setEnv("RANDFILE", randomFile);
1249
- const configOption = ` -config ${q(n2(configFile))}`;
1690
+ const configOption = ` -config ${q3(n4(configFile))}`;
1250
1691
  const reason = params.reason || "keyCompromise";
1251
- assert6(crlReasons.indexOf(reason) >= 0);
1692
+ assert7(crlReasons.indexOf(reason) >= 0);
1252
1693
  displayTitle(`Revoking certificate ${certificate}`);
1253
1694
  displaySubtitle("Revoke certificate");
1254
- await execute_openssl_no_failure(`ca -verbose ${configOption} -revoke ${q(certificate)} -crl_reason ${reason}`, options);
1695
+ await execute_openssl_no_failure(`ca -verbose ${configOption} -revoke ${q3(certificate)} -crl_reason ${reason}`, options);
1255
1696
  await regenerateCrl(this.revocationList, configOption, options);
1256
1697
  displaySubtitle("Verify that certificate is revoked");
1257
1698
  await execute_openssl_no_failure(
1258
- "verify -verbose -CRLfile " + q(n2(this.revocationList)) + " -CAfile " + q(n2(this.caCertificate)) + " -crl_check " + q(n2(certificate)),
1699
+ "verify -verbose -CRLfile " + q3(n4(this.revocationList)) + " -CAfile " + q3(n4(this.caCertificate)) + " -crl_check " + q3(n4(certificate)),
1259
1700
  options
1260
1701
  );
1261
1702
  displaySubtitle("Produce CRL in DER form ");
1262
- await execute_openssl(`crl -in ${q(n2(this.revocationList))} -out crl/revocation_list.der -outform der`, options);
1703
+ await execute_openssl(`crl -in ${q3(n4(this.revocationList))} -out crl/revocation_list.der -outform der`, options);
1263
1704
  displaySubtitle("Produce CRL in PEM form ");
1264
- await execute_openssl(`crl -in ${q(n2(this.revocationList))} -out crl/revocation_list.pem -outform pem -text `, options);
1705
+ await execute_openssl(`crl -in ${q3(n4(this.revocationList))} -out crl/revocation_list.pem -outform pem -text `, options);
1265
1706
  }
1266
1707
  /**
1267
1708
  * Sign a Certificate Signing Request (CSR) with this CA.
@@ -1277,7 +1718,7 @@ var CertificateAuthority = class {
1277
1718
  */
1278
1719
  async signCertificateRequest(certificate, certificateSigningRequestFilename, params1) {
1279
1720
  await ensure_openssl_installed();
1280
- assert6(fs6.existsSync(certificateSigningRequestFilename));
1721
+ assert7(fs7.existsSync(certificateSigningRequestFilename));
1281
1722
  if (!certificateFileExist(certificate)) {
1282
1723
  return "";
1283
1724
  }
@@ -1304,11 +1745,11 @@ var CertificateAuthority = class {
1304
1745
  displaySubtitle("- then we ask the authority to sign the certificate signing request");
1305
1746
  const configOption = ` -config ${configFile}`;
1306
1747
  await execute_openssl(
1307
- "ca " + configOption + " -startdate " + x509Date(params1.startDate) + " -enddate " + x509Date(params1.endDate) + " -batch -out " + q(n2(certificate)) + " -in " + q(n2(certificateSigningRequestFilename)),
1748
+ "ca " + configOption + " -startdate " + x509Date(params1.startDate) + " -enddate " + x509Date(params1.endDate) + " -batch -out " + q3(n4(certificate)) + " -in " + q3(n4(certificateSigningRequestFilename)),
1308
1749
  options
1309
1750
  );
1310
1751
  displaySubtitle("- dump the certificate for a check");
1311
- await execute_openssl(`x509 -in ${q(n2(certificate))} -dates -fingerprint -purpose -noout`, options);
1752
+ await execute_openssl(`x509 -in ${q3(n4(certificate))} -dates -fingerprint -purpose -noout`, options);
1312
1753
  displaySubtitle("- construct CA certificate with CRL");
1313
1754
  await this.constructCACertificateWithCRL();
1314
1755
  displaySubtitle("- construct certificate chain");
@@ -1331,7 +1772,7 @@ var CertificateAuthority = class {
1331
1772
  const _configOption = ` -config ${configFile}`;
1332
1773
  _configOption;
1333
1774
  await execute_openssl_no_failure(
1334
- `verify -verbose -CAfile ${q(n2(this.caCertificateWithCrl))} ${q(n2(certificate))}`,
1775
+ `verify -verbose -CAfile ${q3(n4(this.caCertificateWithCrl))} ${q3(n4(certificate))}`,
1335
1776
  options
1336
1777
  );
1337
1778
  }
@@ -1340,7 +1781,7 @@ var CertificateAuthority = class {
1340
1781
 
1341
1782
  // packages/node-opcua-pki/lib/pki/certificate_manager.ts
1342
1783
  import { EventEmitter } from "events";
1343
- import fs9 from "fs";
1784
+ import fs10 from "fs";
1344
1785
  import path6 from "path";
1345
1786
  import { drainPendingLocks, withLock } from "@ster5/global-mutex";
1346
1787
  import chalk6 from "chalk";
@@ -1361,21 +1802,21 @@ import {
1361
1802
  } from "node-opcua-crypto";
1362
1803
 
1363
1804
  // packages/node-opcua-pki/lib/toolbox/without_openssl/create_certificate_signing_request.ts
1364
- import assert7 from "assert";
1365
- import fs7 from "fs";
1805
+ import assert8 from "assert";
1806
+ import fs8 from "fs";
1366
1807
  import { createCertificateSigningRequest, pemToPrivateKey, Subject as Subject3 } from "node-opcua-crypto";
1367
1808
  async function createCertificateSigningRequestAsync(certificateSigningRequestFilename, params) {
1368
- assert7(params);
1369
- assert7(params.rootDir);
1370
- assert7(params.configFile);
1371
- assert7(params.privateKey);
1372
- assert7(typeof params.privateKey === "string");
1373
- assert7(fs7.existsSync(params.privateKey), `Private key must exist${params.privateKey}`);
1374
- assert7(fs7.existsSync(params.rootDir), "RootDir key must exist");
1375
- assert7(typeof certificateSigningRequestFilename === "string");
1809
+ assert8(params);
1810
+ assert8(params.rootDir);
1811
+ assert8(params.configFile);
1812
+ assert8(params.privateKey);
1813
+ assert8(typeof params.privateKey === "string");
1814
+ assert8(fs8.existsSync(params.privateKey), `Private key must exist${params.privateKey}`);
1815
+ assert8(fs8.existsSync(params.rootDir), "RootDir key must exist");
1816
+ assert8(typeof certificateSigningRequestFilename === "string");
1376
1817
  const subject = params.subject ? new Subject3(params.subject).toString() : void 0;
1377
1818
  displaySubtitle("- Creating a Certificate Signing Request with subtile");
1378
- const privateKeyPem = await fs7.promises.readFile(params.privateKey, "utf-8");
1819
+ const privateKeyPem = await fs8.promises.readFile(params.privateKey, "utf-8");
1379
1820
  const privateKey = await pemToPrivateKey(privateKeyPem);
1380
1821
  const { csr } = await createCertificateSigningRequest({
1381
1822
  privateKey,
@@ -1385,38 +1826,38 @@ async function createCertificateSigningRequestAsync(certificateSigningRequestFil
1385
1826
  applicationUri: params.applicationUri,
1386
1827
  purpose: params.purpose
1387
1828
  });
1388
- await fs7.promises.writeFile(certificateSigningRequestFilename, csr, "utf-8");
1829
+ await fs8.promises.writeFile(certificateSigningRequestFilename, csr, "utf-8");
1389
1830
  display(`- privateKey ${params.privateKey}`);
1390
1831
  display(`- certificateSigningRequestFilename ${certificateSigningRequestFilename}`);
1391
1832
  }
1392
1833
 
1393
1834
  // packages/node-opcua-pki/lib/toolbox/without_openssl/create_self_signed_certificate.ts
1394
- import assert8 from "assert";
1395
- import fs8 from "fs";
1835
+ import assert9 from "assert";
1836
+ import fs9 from "fs";
1396
1837
  import {
1397
- CertificatePurpose,
1838
+ CertificatePurpose as CertificatePurpose2,
1398
1839
  createSelfSignedCertificate as createSelfSignedCertificate1,
1399
1840
  pemToPrivateKey as pemToPrivateKey2,
1400
1841
  Subject as Subject4
1401
1842
  } from "node-opcua-crypto";
1402
1843
  async function createSelfSignedCertificateAsync(certificate, params) {
1403
- params.purpose = params.purpose || CertificatePurpose.ForApplication;
1404
- assert8(params.purpose, "Please provide a Certificate Purpose");
1405
- assert8(fs8.existsSync(params.configFile));
1406
- assert8(fs8.existsSync(params.rootDir));
1407
- assert8(fs8.existsSync(params.privateKey));
1844
+ params.purpose = params.purpose || CertificatePurpose2.ForApplication;
1845
+ assert9(params.purpose, "Please provide a Certificate Purpose");
1846
+ assert9(fs9.existsSync(params.configFile));
1847
+ assert9(fs9.existsSync(params.rootDir));
1848
+ assert9(fs9.existsSync(params.privateKey));
1408
1849
  if (!params.subject) {
1409
1850
  throw Error("Missing subject");
1410
1851
  }
1411
- assert8(typeof params.applicationUri === "string");
1412
- assert8(Array.isArray(params.dns));
1852
+ assert9(typeof params.applicationUri === "string");
1853
+ assert9(Array.isArray(params.dns));
1413
1854
  adjustDate(params);
1414
- assert8(Object.prototype.hasOwnProperty.call(params, "validity"));
1855
+ assert9(Object.prototype.hasOwnProperty.call(params, "validity"));
1415
1856
  let subject = new Subject4(params.subject);
1416
1857
  subject = subject.toString();
1417
1858
  const purpose = params.purpose;
1418
1859
  displayTitle("Generate a certificate request");
1419
- const privateKeyPem = await fs8.promises.readFile(params.privateKey, "utf-8");
1860
+ const privateKeyPem = await fs9.promises.readFile(params.privateKey, "utf-8");
1420
1861
  const privateKey = await pemToPrivateKey2(privateKeyPem);
1421
1862
  const { cert } = await createSelfSignedCertificate1({
1422
1863
  privateKey,
@@ -1429,19 +1870,15 @@ async function createSelfSignedCertificateAsync(certificate, params) {
1429
1870
  applicationUri: params.applicationUri,
1430
1871
  purpose
1431
1872
  });
1432
- await fs8.promises.writeFile(certificate, cert, "utf-8");
1873
+ await fs9.promises.writeFile(certificate, cert, "utf-8");
1433
1874
  }
1434
1875
  async function createSelfSignedCertificate(certificate, params) {
1435
1876
  await createSelfSignedCertificateAsync(certificate, params);
1436
1877
  }
1437
1878
 
1438
- // packages/node-opcua-pki/lib/pki/templates/simple_config_template.cnf.ts
1439
- var config3 = '##################################################################################################\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, cRLSign, keyCertSign\nsubjectAltName = $ENV::ALTNAME\n\n[ v3_ca_signed]\nsubjectKeyIdentifier = hash\nauthorityKeyIdentifier = keyid,issuer\nbasicConstraints = critical, CA:FALSE\nkeyUsage = nonRepudiation, digitalSignature, keyEncipherment, dataEncipherment, keyCertSign\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, keyCertSign\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';
1440
- var simple_config_template_cnf_default = config3;
1441
-
1442
1879
  // packages/node-opcua-pki/lib/pki/certificate_manager.ts
1443
- var configurationFileSimpleTemplate = simple_config_template_cnf_default;
1444
- var fsWriteFile = fs9.promises.writeFile;
1880
+ var configurationFileSimpleTemplate2 = simple_config_template_cnf_default;
1881
+ var fsWriteFile = fs10.promises.writeFile;
1445
1882
  function getOrComputeInfo(entry) {
1446
1883
  if (!entry.info) {
1447
1884
  entry.info = exploreCertificateCached(entry.certificate);
@@ -1668,7 +2105,7 @@ var CertificateManager = class _CertificateManager extends EventEmitter {
1668
2105
  this.#location = makePath(options.location, "");
1669
2106
  this.keySize = options.keySize;
1670
2107
  mkdirRecursiveSync(options.location);
1671
- if (!fs9.existsSync(this.#location)) {
2108
+ if (!fs10.existsSync(this.#location)) {
1672
2109
  throw new Error(`CertificateManager cannot access location ${this.#location}`);
1673
2110
  }
1674
2111
  }
@@ -1981,15 +2418,15 @@ var CertificateManager = class _CertificateManager extends EventEmitter {
1981
2418
  mkdirRecursiveSync(path6.join(pkiDir, "issuers"));
1982
2419
  mkdirRecursiveSync(path6.join(pkiDir, "issuers/certs"));
1983
2420
  mkdirRecursiveSync(path6.join(pkiDir, "issuers/crl"));
1984
- if (!fs9.existsSync(this.configFile) || !fs9.existsSync(this.privateKey)) {
2421
+ if (!fs10.existsSync(this.configFile) || !fs10.existsSync(this.privateKey)) {
1985
2422
  return await this.withLock2(async () => {
1986
2423
  if (this.state === 3 /* Disposing */ || this.state === 4 /* Disposed */) {
1987
2424
  return;
1988
2425
  }
1989
- if (!fs9.existsSync(this.configFile)) {
1990
- fs9.writeFileSync(this.configFile, configurationFileSimpleTemplate);
2426
+ if (!fs10.existsSync(this.configFile)) {
2427
+ fs10.writeFileSync(this.configFile, configurationFileSimpleTemplate2);
1991
2428
  }
1992
- if (!fs9.existsSync(this.privateKey)) {
2429
+ if (!fs10.existsSync(this.privateKey)) {
1993
2430
  debugLog("generating private key ...");
1994
2431
  await generatePrivateKeyFile2(this.privateKey, this.keySize);
1995
2432
  await this.#readCertificates();
@@ -2079,7 +2516,7 @@ var CertificateManager = class _CertificateManager extends EventEmitter {
2079
2516
  if (typeof params.applicationUri !== "string") {
2080
2517
  throw new Error("createSelfSignedCertificate: expecting applicationUri to be a string");
2081
2518
  }
2082
- if (!fs9.existsSync(this.privateKey)) {
2519
+ if (!fs10.existsSync(this.privateKey)) {
2083
2520
  throw new Error(`Cannot find private key ${this.privateKey}`);
2084
2521
  }
2085
2522
  let certificateFilename = path6.join(this.rootDir, "own/certs/self_signed_certificate.pem");
@@ -2143,7 +2580,7 @@ var CertificateManager = class _CertificateManager extends EventEmitter {
2143
2580
  return "Good" /* Good */;
2144
2581
  }
2145
2582
  const filename = path6.join(this.issuersCertFolder, `issuer_${buildIdealCertificateName(certificate)}.pem`);
2146
- await fs9.promises.writeFile(filename, pemCertificate, "ascii");
2583
+ await fs10.promises.writeFile(filename, pemCertificate, "ascii");
2147
2584
  this.#thumbs.issuers.certs.set(fingerprint, { certificate, filename });
2148
2585
  if (addInTrustList) {
2149
2586
  await this.trustCertificate(certificate);
@@ -2166,8 +2603,9 @@ var CertificateManager = class _CertificateManager extends EventEmitter {
2166
2603
  index.set(key, { crls: [], serialNumbers: {} });
2167
2604
  }
2168
2605
  const pemCertificate = toPem2(crl, "X509 CRL");
2169
- const filename = path6.join(folder, `crl_${buildIdealCertificateName(crl)}.pem`);
2170
- await fs9.promises.writeFile(filename, pemCertificate, "ascii");
2606
+ const sanitizedKey = key.replace(/:/g, "");
2607
+ const filename = path6.join(folder, `crl_[${sanitizedKey}].pem`);
2608
+ await fs10.promises.writeFile(filename, pemCertificate, "ascii");
2171
2609
  await this.#onCrlFileAdded(index, filename);
2172
2610
  await this.#waitAndCheckCRLProcessingStatus();
2173
2611
  return "Good" /* Good */;
@@ -2186,11 +2624,11 @@ var CertificateManager = class _CertificateManager extends EventEmitter {
2186
2624
  async clearRevocationLists(target) {
2187
2625
  const clearFolder = async (folder, index) => {
2188
2626
  try {
2189
- const files = await fs9.promises.readdir(folder);
2627
+ const files = await fs10.promises.readdir(folder);
2190
2628
  for (const file of files) {
2191
2629
  const ext = path6.extname(file).toLowerCase();
2192
2630
  if (ext === ".crl" || ext === ".pem" || ext === ".der") {
2193
- await fs9.promises.unlink(path6.join(folder, file));
2631
+ await fs10.promises.unlink(path6.join(folder, file));
2194
2632
  }
2195
2633
  }
2196
2634
  } catch (err) {
@@ -2232,7 +2670,7 @@ var CertificateManager = class _CertificateManager extends EventEmitter {
2232
2670
  return null;
2233
2671
  }
2234
2672
  try {
2235
- await fs9.promises.unlink(entry.filename);
2673
+ await fs10.promises.unlink(entry.filename);
2236
2674
  } catch (err) {
2237
2675
  if (err.code !== "ENOENT") {
2238
2676
  throw err;
@@ -2256,7 +2694,7 @@ var CertificateManager = class _CertificateManager extends EventEmitter {
2256
2694
  return null;
2257
2695
  }
2258
2696
  try {
2259
- await fs9.promises.unlink(entry.filename);
2697
+ await fs10.promises.unlink(entry.filename);
2260
2698
  } catch (err) {
2261
2699
  if (err.code !== "ENOENT") {
2262
2700
  throw err;
@@ -2279,7 +2717,7 @@ var CertificateManager = class _CertificateManager extends EventEmitter {
2279
2717
  if (!crlData) return;
2280
2718
  for (const crlEntry of crlData.crls) {
2281
2719
  try {
2282
- await fs9.promises.unlink(crlEntry.filename);
2720
+ await fs10.promises.unlink(crlEntry.filename);
2283
2721
  } catch (err) {
2284
2722
  if (err.code !== "ENOENT") {
2285
2723
  throw err;
@@ -2432,7 +2870,7 @@ var CertificateManager = class _CertificateManager extends EventEmitter {
2432
2870
  if (status === "unknown") {
2433
2871
  const pem = toPem2(certificate, "CERTIFICATE");
2434
2872
  const filename = path6.join(this.rejectedFolder, `${buildIdealCertificateName(certificate)}.pem`);
2435
- await fs9.promises.writeFile(filename, pem);
2873
+ await fs10.promises.writeFile(filename, pem);
2436
2874
  this.#thumbs.rejected.set(fingerprint, { certificate, filename });
2437
2875
  status = "rejected";
2438
2876
  }
@@ -2451,7 +2889,7 @@ var CertificateManager = class _CertificateManager extends EventEmitter {
2451
2889
  const certificateDest = path6.join(destFolder, path6.basename(srcEntry.filename));
2452
2890
  debugLog("#moveCertificate", fingerprint.substring(0, 10), "old name", srcEntry.filename);
2453
2891
  debugLog("#moveCertificate", fingerprint.substring(0, 10), "new name", certificateDest);
2454
- await fs9.promises.rename(srcEntry.filename, certificateDest);
2892
+ await fs10.promises.rename(srcEntry.filename, certificateDest);
2455
2893
  indexSrc.delete(fingerprint);
2456
2894
  const indexDest = newStatus === "trusted" ? this.#thumbs.trusted : this.#thumbs.rejected;
2457
2895
  indexDest.set(fingerprint, { certificate, filename: certificateDest });
@@ -2561,11 +2999,11 @@ var CertificateManager = class _CertificateManager extends EventEmitter {
2561
2999
  persistent: false
2562
3000
  };
2563
3001
  const allCapturedHandles = [];
2564
- const origWatch = fs9.watch;
3002
+ const origWatch = fs10.watch;
2565
3003
  let watcherReadyCount = 0;
2566
3004
  const totalWatchers = 5;
2567
- fs9.watch = ((...args) => {
2568
- const handle = origWatch.apply(fs9, args);
3005
+ fs10.watch = ((...args) => {
3006
+ const handle = origWatch.apply(fs10, args);
2569
3007
  handle.setMaxListeners(handle.getMaxListeners() + 1);
2570
3008
  handle.on("error", () => {
2571
3009
  });
@@ -2581,7 +3019,7 @@ var CertificateManager = class _CertificateManager extends EventEmitter {
2581
3019
  }
2582
3020
  watcherReadyCount++;
2583
3021
  if (watcherReadyCount >= totalWatchers) {
2584
- fs9.watch = origWatch;
3022
+ fs10.watch = origWatch;
2585
3023
  }
2586
3024
  };
2587
3025
  return { w, capturedHandles: allCapturedHandles.slice(startIdx), unreffAll };
@@ -2605,12 +3043,12 @@ var CertificateManager = class _CertificateManager extends EventEmitter {
2605
3043
  * file reads, preventing main-loop stalls with large folders.
2606
3044
  */
2607
3045
  async #scanCertFolder(folder, index) {
2608
- if (!fs9.existsSync(folder)) return;
2609
- const files = await fs9.promises.readdir(folder);
3046
+ if (!fs10.existsSync(folder)) return;
3047
+ const files = await fs10.promises.readdir(folder);
2610
3048
  for (const file of files) {
2611
3049
  const filename = path6.join(folder, file);
2612
3050
  try {
2613
- const stat = await fs9.promises.stat(filename);
3051
+ const stat = await fs10.promises.stat(filename);
2614
3052
  if (!stat.isFile()) continue;
2615
3053
  const certificate = await readCertificateAsync(filename);
2616
3054
  const info = exploreCertificateCached(certificate);
@@ -2626,12 +3064,12 @@ var CertificateManager = class _CertificateManager extends EventEmitter {
2626
3064
  * Scan a CRL folder and populate the in-memory CRL index.
2627
3065
  */
2628
3066
  async #scanCrlFolder(folder, index) {
2629
- if (!fs9.existsSync(folder)) return;
2630
- const files = await fs9.promises.readdir(folder);
3067
+ if (!fs10.existsSync(folder)) return;
3068
+ const files = await fs10.promises.readdir(folder);
2631
3069
  for (const file of files) {
2632
3070
  const filename = path6.join(folder, file);
2633
3071
  try {
2634
- const stat = await fs9.promises.stat(filename);
3072
+ const stat = await fs10.promises.stat(filename);
2635
3073
  if (!stat.isFile()) continue;
2636
3074
  this.#onCrlFileAdded(index, filename);
2637
3075
  } catch (err) {
@@ -2756,65 +3194,6 @@ var CertificateManager = class _CertificateManager extends EventEmitter {
2756
3194
  });
2757
3195
  }
2758
3196
  };
2759
-
2760
- // packages/node-opcua-pki/lib/pki/toolbox_pfx.ts
2761
- import assert9 from "assert";
2762
- import fs10 from "fs";
2763
- var q2 = quote;
2764
- var n3 = makePath;
2765
- async function createPFX(options) {
2766
- const { certificateFile, privateKeyFile, outputFile, passphrase = "", caCertificateFiles } = options;
2767
- assert9(fs10.existsSync(certificateFile), `Certificate file does not exist: ${certificateFile}`);
2768
- assert9(fs10.existsSync(privateKeyFile), `Private key file does not exist: ${privateKeyFile}`);
2769
- let cmd = `pkcs12 -export`;
2770
- cmd += ` -in ${q2(n3(certificateFile))}`;
2771
- cmd += ` -inkey ${q2(n3(privateKeyFile))}`;
2772
- if (caCertificateFiles) {
2773
- for (const caFile of caCertificateFiles) {
2774
- assert9(fs10.existsSync(caFile), `CA certificate file does not exist: ${caFile}`);
2775
- cmd += ` -certfile ${q2(n3(caFile))}`;
2776
- }
2777
- }
2778
- cmd += ` -out ${q2(n3(outputFile))}`;
2779
- cmd += ` -passout pass:${passphrase}`;
2780
- await execute_openssl(cmd, {});
2781
- }
2782
- async function extractCertificateFromPFX(options) {
2783
- const { pfxFile, passphrase = "" } = options;
2784
- assert9(fs10.existsSync(pfxFile), `PFX file does not exist: ${pfxFile}`);
2785
- const cmd = `pkcs12 -in ${q2(n3(pfxFile))} -clcerts -nokeys -nodes -passin pass:${passphrase}`;
2786
- return await execute_openssl(cmd, {});
2787
- }
2788
- async function extractPrivateKeyFromPFX(options) {
2789
- const { pfxFile, passphrase = "" } = options;
2790
- assert9(fs10.existsSync(pfxFile), `PFX file does not exist: ${pfxFile}`);
2791
- const cmd = `pkcs12 -in ${q2(n3(pfxFile))} -nocerts -nodes -passin pass:${passphrase}`;
2792
- return await execute_openssl(cmd, {});
2793
- }
2794
- async function extractCACertificatesFromPFX(options) {
2795
- const { pfxFile, passphrase = "" } = options;
2796
- assert9(fs10.existsSync(pfxFile), `PFX file does not exist: ${pfxFile}`);
2797
- const cmd = `pkcs12 -in ${q2(n3(pfxFile))} -cacerts -nokeys -nodes -passin pass:${passphrase}`;
2798
- return await execute_openssl(cmd, {});
2799
- }
2800
- async function extractAllFromPFX(options) {
2801
- const [certificate, privateKey, caCertificates] = await Promise.all([
2802
- extractCertificateFromPFX(options),
2803
- extractPrivateKeyFromPFX(options),
2804
- extractCACertificatesFromPFX(options)
2805
- ]);
2806
- return { certificate, privateKey, caCertificates };
2807
- }
2808
- async function convertPFXtoPEM(pfxFile, pemFile, passphrase = "") {
2809
- assert9(fs10.existsSync(pfxFile), `PFX file does not exist: ${pfxFile}`);
2810
- const cmd = `pkcs12 -in ${q2(n3(pfxFile))} -out ${q2(n3(pemFile))} -nodes -passin pass:${passphrase}`;
2811
- await execute_openssl(cmd, {});
2812
- }
2813
- async function dumpPFX(pfxFile, passphrase = "") {
2814
- assert9(fs10.existsSync(pfxFile), `PFX file does not exist: ${pfxFile}`);
2815
- const cmd = `pkcs12 -in ${q2(n3(pfxFile))} -info -nodes -passin pass:${passphrase}`;
2816
- return await execute_openssl(cmd, {});
2817
- }
2818
3197
  export {
2819
3198
  CertificateAuthority,
2820
3199
  CertificateManager,