node-opcua-pki 6.3.0 → 6.5.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,15 +1,7 @@
1
- // node_modules/tsup/assets/esm_shims.js
2
- import path from "path";
3
- import { fileURLToPath } from "url";
4
- var getFilename = () => fileURLToPath(import.meta.url);
5
- var getDirname = () => path.dirname(getFilename());
6
- var __dirname = /* @__PURE__ */ getDirname();
7
- var __filename = /* @__PURE__ */ getFilename();
8
-
9
1
  // packages/node-opcua-pki/lib/ca/certificate_authority.ts
10
2
  import assert6 from "assert";
11
3
  import fs6 from "fs";
12
- import path6 from "path";
4
+ import path5 from "path";
13
5
  import chalk5 from "chalk";
14
6
  import {
15
7
  exploreCertificateSigningRequest,
@@ -43,7 +35,7 @@ function adjustApplicationUri(params) {
43
35
  // packages/node-opcua-pki/lib/toolbox/common2.ts
44
36
  import assert2 from "assert";
45
37
  import fs from "fs";
46
- import path2 from "path";
38
+ import path from "path";
47
39
  import chalk from "chalk";
48
40
 
49
41
  // packages/node-opcua-pki/lib/toolbox/config.ts
@@ -85,7 +77,7 @@ function mkdirRecursiveSync(folder) {
85
77
  function makePath(folderName, filename) {
86
78
  let s;
87
79
  if (filename) {
88
- s = path2.join(path2.normalize(folderName), filename);
80
+ s = path.join(path.normalize(folderName), filename);
89
81
  } else {
90
82
  assert2(folderName);
91
83
  s = folderName;
@@ -96,13 +88,6 @@ function makePath(folderName, filename) {
96
88
 
97
89
  // packages/node-opcua-pki/lib/toolbox/display.ts
98
90
  import chalk2 from "chalk";
99
- function displayChapter(str) {
100
- const l = " ";
101
- warningLog(`${chalk2.bgWhite(l)} `);
102
- str = ` ${str}${l}`.substring(0, l.length);
103
- warningLog(chalk2.bgWhite.cyan(str));
104
- warningLog(`${chalk2.bgWhite(l)} `);
105
- }
106
91
  function displayTitle(str) {
107
92
  if (!g_config.silent) {
108
93
  warningLog("");
@@ -168,7 +153,7 @@ function processAltNames(params) {
168
153
  // packages/node-opcua-pki/lib/toolbox/with_openssl/create_certificate_signing_request.ts
169
154
  import assert5 from "assert";
170
155
  import fs5 from "fs";
171
- import path5 from "path";
156
+ import path4 from "path";
172
157
 
173
158
  // packages/node-opcua-pki/lib/misc/subject.ts
174
159
  import { Subject } from "node-opcua-crypto";
@@ -185,7 +170,7 @@ import chalk4 from "chalk";
185
170
  import child_process from "child_process";
186
171
  import fs2 from "fs";
187
172
  import os from "os";
188
- import path3 from "path";
173
+ import path2 from "path";
189
174
  import url from "url";
190
175
  import byline from "byline";
191
176
  import chalk3 from "chalk";
@@ -286,19 +271,19 @@ async function check_system_openssl_version() {
286
271
  return output;
287
272
  }
288
273
  async function install_and_check_win32_openssl_version() {
289
- const downloadFolder = path3.join(os.tmpdir(), ".");
274
+ const downloadFolder = path2.join(os.tmpdir(), ".");
290
275
  function get_openssl_folder_win32() {
291
276
  if (process.env.LOCALAPPDATA) {
292
- const userProgramFolder = path3.join(process.env.LOCALAPPDATA, "Programs");
277
+ const userProgramFolder = path2.join(process.env.LOCALAPPDATA, "Programs");
293
278
  if (fs2.existsSync(userProgramFolder)) {
294
- return path3.join(userProgramFolder, "openssl");
279
+ return path2.join(userProgramFolder, "openssl");
295
280
  }
296
281
  }
297
- return path3.join(process.cwd(), "openssl");
282
+ return path2.join(process.cwd(), "openssl");
298
283
  }
299
284
  function get_openssl_exec_path_win32() {
300
285
  const opensslFolder2 = get_openssl_folder_win32();
301
- return path3.join(opensslFolder2, "openssl.exe");
286
+ return path2.join(opensslFolder2, "openssl.exe");
302
287
  }
303
288
  async function check_openssl_win32() {
304
289
  const opensslExecPath2 = get_openssl_exec_path_win32();
@@ -338,7 +323,7 @@ async function install_and_check_win32_openssl_version() {
338
323
  }
339
324
  async function download_openssl() {
340
325
  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";
341
- const outputFilename = path3.join(downloadFolder, path3.basename(url2));
326
+ const outputFilename = path2.join(downloadFolder, path2.basename(url2));
342
327
  warningLog(`downloading ${chalk3.yellow(url2)} to ${outputFilename}`);
343
328
  if (fs2.existsSync(outputFilename)) {
344
329
  return { downloadedFile: outputFilename };
@@ -403,7 +388,7 @@ async function install_and_check_win32_openssl_version() {
403
388
  if (err) {
404
389
  return reject(err);
405
390
  }
406
- const file = path3.join(opensslFolder2, entry.fileName);
391
+ const file = path2.join(opensslFolder2, entry.fileName);
407
392
  if (doDebug2) {
408
393
  warningLog(" unzipping :", file);
409
394
  }
@@ -574,7 +559,7 @@ async function execute_openssl(cmd, options) {
574
559
  // packages/node-opcua-pki/lib/toolbox/with_openssl/toolbox.ts
575
560
  import assert4 from "assert";
576
561
  import fs4 from "fs";
577
- import path4 from "path";
562
+ import path3 from "path";
578
563
  function openssl_require2DigitYearInDate() {
579
564
  if (!g_config.opensslVersion) {
580
565
  throw new Error(
@@ -587,26 +572,20 @@ g_config.opensslVersion = "";
587
572
  var _counter = 0;
588
573
  function generateStaticConfig(configPath, options) {
589
574
  const prePath = options?.cwd || "";
590
- const originalFilename = !path4.isAbsolute(configPath) ? path4.join(prePath, configPath) : configPath;
575
+ const originalFilename = !path3.isAbsolute(configPath) ? path3.join(prePath, configPath) : configPath;
591
576
  let staticConfig = fs4.readFileSync(originalFilename, { encoding: "utf8" });
592
577
  for (const envVar of getEnvironmentVarNames()) {
593
578
  staticConfig = staticConfig.replace(new RegExp(envVar.pattern, "gi"), getEnv(envVar.key));
594
579
  }
595
580
  const staticConfigPath = `${configPath}.${process.pid}-${_counter++}.tmp`;
596
- const temporaryConfigPath = !path4.isAbsolute(configPath) ? path4.join(prePath, staticConfigPath) : staticConfigPath;
581
+ const temporaryConfigPath = !path3.isAbsolute(configPath) ? path3.join(prePath, staticConfigPath) : staticConfigPath;
597
582
  fs4.writeFileSync(temporaryConfigPath, staticConfig);
598
583
  if (options?.cwd) {
599
- return path4.relative(options.cwd, temporaryConfigPath);
584
+ return path3.relative(options.cwd, temporaryConfigPath);
600
585
  } else {
601
586
  return temporaryConfigPath;
602
587
  }
603
588
  }
604
- var q = quote;
605
- var n2 = makePath;
606
- async function getPublicKeyFromPrivateKey(privateKeyFilename, publicKeyFilename) {
607
- assert4(fs4.existsSync(privateKeyFilename));
608
- await execute_openssl(`rsa -pubout -in ${q(n2(privateKeyFilename))} -out ${q(n2(publicKeyFilename))}`, {});
609
- }
610
589
  function x509Date(date) {
611
590
  date = date || /* @__PURE__ */ new Date();
612
591
  const Y = date.getUTCFullYear();
@@ -624,45 +603,6 @@ function x509Date(date) {
624
603
  return `${w(Y, 4) + w(M, 2) + w(D, 2) + w(h, 2) + w(m, 2) + w(s, 2)}Z`;
625
604
  }
626
605
  }
627
- async function dumpCertificate(certificate) {
628
- assert4(fs4.existsSync(certificate));
629
- return await execute_openssl(`x509 -in ${q(n2(certificate))} -text -noout`, {});
630
- }
631
- async function toDer(certificatePem) {
632
- assert4(fs4.existsSync(certificatePem));
633
- const certificateDer = certificatePem.replace(".pem", ".der");
634
- return await execute_openssl(`x509 -outform der -in ${certificatePem} -out ${certificateDer}`, {});
635
- }
636
- async function fingerprint(certificatePem) {
637
- assert4(fs4.existsSync(certificatePem));
638
- return await execute_openssl(`x509 -fingerprint -noout -in ${certificatePem}`, {});
639
- }
640
-
641
- // packages/node-opcua-pki/lib/toolbox/with_openssl/create_certificate_signing_request.ts
642
- var q2 = quote;
643
- var n3 = makePath;
644
- async function createCertificateSigningRequestWithOpenSSL(certificateSigningRequestFilename, params) {
645
- assert5(params);
646
- assert5(params.rootDir);
647
- assert5(params.configFile);
648
- assert5(params.privateKey);
649
- assert5(typeof params.privateKey === "string");
650
- assert5(fs5.existsSync(params.configFile), `config file must exist ${params.configFile}`);
651
- assert5(fs5.existsSync(params.privateKey), `Private key must exist${params.privateKey}`);
652
- assert5(fs5.existsSync(params.rootDir), "RootDir key must exist");
653
- assert5(typeof certificateSigningRequestFilename === "string");
654
- processAltNames(params);
655
- const configFile = generateStaticConfig(params.configFile, { cwd: params.rootDir });
656
- const options = { cwd: params.rootDir, openssl_conf: path5.relative(params.rootDir, configFile) };
657
- const configOption = ` -config ${q2(n3(configFile))}`;
658
- const subject = params.subject ? new Subject(params.subject).toString() : void 0;
659
- const subjectOptions = subject ? ` -subj "${subject}"` : "";
660
- displaySubtitle("- Creating a Certificate Signing Request with openssl");
661
- await execute_openssl(
662
- "req -new -sha256 -batch -text " + configOption + " -key " + q2(n3(params.privateKey)) + subjectOptions + " -out " + q2(n3(certificateSigningRequestFilename)),
663
- options
664
- );
665
- }
666
606
 
667
607
  // packages/node-opcua-pki/lib/ca/templates/ca_config_template.cnf.ts
668
608
  var config = `#.........DO NOT MODIFY BY HAND .........................
@@ -800,45 +740,45 @@ var config2 = {
800
740
  forceCA: false,
801
741
  pkiDir: "INVALID"
802
742
  };
803
- var n4 = makePath;
804
- var q3 = quote;
743
+ var n2 = makePath;
744
+ var q = quote;
805
745
  function octetStringToIpAddress(a) {
806
746
  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();
807
747
  }
808
748
  assert6(octetStringToIpAddress("c07b9179") === "192.123.145.121");
809
749
  async function construct_CertificateAuthority(certificateAuthority) {
810
750
  const subject = certificateAuthority.subject;
811
- const caRootDir = path6.resolve(certificateAuthority.rootDir);
751
+ const caRootDir = path5.resolve(certificateAuthority.rootDir);
812
752
  async function make_folders() {
813
753
  mkdirRecursiveSync(caRootDir);
814
- mkdirRecursiveSync(path6.join(caRootDir, "private"));
815
- mkdirRecursiveSync(path6.join(caRootDir, "public"));
816
- mkdirRecursiveSync(path6.join(caRootDir, "certs"));
817
- mkdirRecursiveSync(path6.join(caRootDir, "crl"));
818
- mkdirRecursiveSync(path6.join(caRootDir, "conf"));
754
+ mkdirRecursiveSync(path5.join(caRootDir, "private"));
755
+ mkdirRecursiveSync(path5.join(caRootDir, "public"));
756
+ mkdirRecursiveSync(path5.join(caRootDir, "certs"));
757
+ mkdirRecursiveSync(path5.join(caRootDir, "crl"));
758
+ mkdirRecursiveSync(path5.join(caRootDir, "conf"));
819
759
  }
820
760
  await make_folders();
821
761
  async function construct_default_files() {
822
- const serial = path6.join(caRootDir, "serial");
762
+ const serial = path5.join(caRootDir, "serial");
823
763
  if (!fs6.existsSync(serial)) {
824
764
  await fs6.promises.writeFile(serial, "1000");
825
765
  }
826
- const crlNumber = path6.join(caRootDir, "crlnumber");
766
+ const crlNumber = path5.join(caRootDir, "crlnumber");
827
767
  if (!fs6.existsSync(crlNumber)) {
828
768
  await fs6.promises.writeFile(crlNumber, "1000");
829
769
  }
830
- const indexFile = path6.join(caRootDir, "index.txt");
770
+ const indexFile = path5.join(caRootDir, "index.txt");
831
771
  if (!fs6.existsSync(indexFile)) {
832
772
  await fs6.promises.writeFile(indexFile, "");
833
773
  }
834
774
  }
835
775
  await construct_default_files();
836
- if (fs6.existsSync(path6.join(caRootDir, "private/cakey.pem")) && !config2.forceCA) {
776
+ if (fs6.existsSync(path5.join(caRootDir, "private/cakey.pem")) && !config2.forceCA) {
837
777
  debugLog("CA private key already exists ... skipping");
838
778
  return;
839
779
  }
840
780
  displayTitle("Create Certificate Authority (CA)");
841
- const indexFileAttr = path6.join(caRootDir, "index.txt.attr");
781
+ const indexFileAttr = path5.join(caRootDir, "index.txt.attr");
842
782
  if (!fs6.existsSync(indexFileAttr)) {
843
783
  await fs6.promises.writeFile(indexFileAttr, "unique_subject = no");
844
784
  }
@@ -852,20 +792,20 @@ async function construct_CertificateAuthority(certificateAuthority) {
852
792
  processAltNames({});
853
793
  const options = { cwd: caRootDir };
854
794
  const configFile = generateStaticConfig("conf/caconfig.cnf", options);
855
- const configOption = ` -config ${q3(n4(configFile))}`;
795
+ const configOption = ` -config ${q(n2(configFile))}`;
856
796
  const keySize = certificateAuthority.keySize;
857
- const privateKeyFilename = path6.join(caRootDir, "private/cakey.pem");
858
- const csrFilename = path6.join(caRootDir, "private/cakey.csr");
797
+ const privateKeyFilename = path5.join(caRootDir, "private/cakey.pem");
798
+ const csrFilename = path5.join(caRootDir, "private/cakey.csr");
859
799
  displayTitle(`Generate the CA private Key - ${keySize}`);
860
800
  await generatePrivateKeyFile(privateKeyFilename, keySize);
861
801
  displayTitle("Generate a certificate request for the CA key");
862
802
  await execute_openssl(
863
- "req -new -sha256 -text -extensions v3_ca" + configOption + " -key " + q3(n4(privateKeyFilename)) + " -out " + q3(n4(csrFilename)) + " " + subjectOpt,
803
+ "req -new -sha256 -text -extensions v3_ca" + configOption + " -key " + q(n2(privateKeyFilename)) + " -out " + q(n2(csrFilename)) + " " + subjectOpt,
864
804
  options
865
805
  );
866
806
  displayTitle("Generate CA Certificate (self-signed)");
867
807
  await execute_openssl(
868
- " 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",
808
+ " 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",
869
809
  options
870
810
  );
871
811
  displaySubtitle("generate initial CRL (Certificate Revocation List)");
@@ -877,11 +817,14 @@ async function regenerateCrl(revocationList, configOption, options) {
877
817
  await execute_openssl(`ca -gencrl ${configOption} -out crl/revocation_list.crl`, options);
878
818
  await execute_openssl("crl -in crl/revocation_list.crl -out crl/revocation_list.der -outform der", options);
879
819
  displaySubtitle("Display (Certificate Revocation List)");
880
- await execute_openssl(`crl -in ${q3(n4(revocationList))} -text -noout`, options);
820
+ await execute_openssl(`crl -in ${q(n2(revocationList))} -text -noout`, options);
881
821
  }
882
822
  var CertificateAuthority = class {
823
+ /** RSA key size used when generating the CA private key. */
883
824
  keySize;
825
+ /** Root filesystem path of the CA directory structure. */
884
826
  location;
827
+ /** X.500 subject of the CA certificate. */
885
828
  subject;
886
829
  constructor(options) {
887
830
  assert6(Object.prototype.hasOwnProperty.call(options, "location"));
@@ -890,33 +833,54 @@ var CertificateAuthority = class {
890
833
  this.keySize = options.keySize || 2048;
891
834
  this.subject = new Subject2(options.subject || defaultSubject);
892
835
  }
836
+ /** Absolute path to the CA root directory (alias for {@link location}). */
893
837
  get rootDir() {
894
838
  return this.location;
895
839
  }
840
+ /** Path to the OpenSSL configuration file (`conf/caconfig.cnf`). */
896
841
  get configFile() {
897
- return path6.normalize(path6.join(this.rootDir, "./conf/caconfig.cnf"));
842
+ return path5.normalize(path5.join(this.rootDir, "./conf/caconfig.cnf"));
898
843
  }
844
+ /** Path to the CA certificate in PEM format (`public/cacert.pem`). */
899
845
  get caCertificate() {
900
846
  return makePath(this.rootDir, "./public/cacert.pem");
901
847
  }
902
848
  /**
903
- * the file name where the current Certificate Revocation List is stored (in DER format)
849
+ * Path to the current Certificate Revocation List in DER format.
850
+ * (`crl/revocation_list.der`)
904
851
  */
905
852
  get revocationListDER() {
906
853
  return makePath(this.rootDir, "./crl/revocation_list.der");
907
854
  }
908
855
  /**
909
- * the file name where the current Certificate Revocation List is stored (in PEM format)
856
+ * Path to the current Certificate Revocation List in PEM format.
857
+ * (`crl/revocation_list.crl`)
910
858
  */
911
859
  get revocationList() {
912
860
  return makePath(this.rootDir, "./crl/revocation_list.crl");
913
861
  }
862
+ /**
863
+ * Path to the concatenated CA certificate + CRL file.
864
+ * Used by OpenSSL for CRL-based verification.
865
+ */
914
866
  get caCertificateWithCrl() {
915
867
  return makePath(this.rootDir, "./public/cacertificate_with_crl.pem");
916
868
  }
869
+ /**
870
+ * Initialize the CA directory structure, generate the CA
871
+ * private key and self-signed certificate if they do not
872
+ * already exist.
873
+ */
917
874
  async initialize() {
918
875
  await construct_CertificateAuthority(this);
919
876
  }
877
+ /**
878
+ * Rebuild the combined CA certificate + CRL file.
879
+ *
880
+ * This concatenates the CA certificate with the current
881
+ * revocation list so that OpenSSL can verify certificates
882
+ * with CRL checking enabled.
883
+ */
920
884
  async constructCACertificateWithCRL() {
921
885
  const cacertWithCRL = this.caCertificateWithCrl;
922
886
  if (fs6.existsSync(this.revocationList)) {
@@ -928,6 +892,12 @@ var CertificateAuthority = class {
928
892
  await fs6.promises.writeFile(cacertWithCRL, fs6.readFileSync(this.caCertificate));
929
893
  }
930
894
  }
895
+ /**
896
+ * Append the CA certificate to a signed certificate file,
897
+ * creating a PEM certificate chain.
898
+ *
899
+ * @param certificate - path to the certificate file to extend
900
+ */
931
901
  async constructCertificateChain(certificate) {
932
902
  assert6(fs6.existsSync(certificate));
933
903
  assert6(fs6.existsSync(this.caCertificate));
@@ -938,6 +908,13 @@ var CertificateAuthority = class {
938
908
  // + fs.readFileSync(this.revocationList)
939
909
  );
940
910
  }
911
+ /**
912
+ * Create a self-signed certificate using OpenSSL.
913
+ *
914
+ * @param certificateFile - output path for the signed certificate
915
+ * @param privateKey - path to the private key file
916
+ * @param params - certificate parameters (subject, validity, SANs)
917
+ */
941
918
  async createSelfSignedCertificate(certificateFile, privateKey, params) {
942
919
  assert6(typeof privateKey === "string");
943
920
  assert6(fs6.existsSync(privateKey));
@@ -959,28 +936,27 @@ var CertificateAuthority = class {
959
936
  const subjectOptions = subject && subject.length > 1 ? ` -subj ${subject} ` : "";
960
937
  displaySubtitle("- the certificate signing request");
961
938
  await execute_openssl(
962
- "req -new -sha256 -text " + configOption + subjectOptions + " -batch -key " + q3(n4(privateKey)) + " -out " + q3(n4(csrFile)),
939
+ "req -new -sha256 -text " + configOption + subjectOptions + " -batch -key " + q(n2(privateKey)) + " -out " + q(n2(csrFile)),
963
940
  options
964
941
  );
965
942
  displaySubtitle("- creating the self-signed certificate");
966
943
  await execute_openssl(
967
- "ca -selfsign -keyfile " + q3(n4(privateKey)) + " -startdate " + x509Date(params.startDate) + " -enddate " + x509Date(params.endDate) + " -batch -out " + q3(n4(certificateFile)) + " -in " + q3(n4(csrFile)),
944
+ "ca -selfsign -keyfile " + q(n2(privateKey)) + " -startdate " + x509Date(params.startDate) + " -enddate " + x509Date(params.endDate) + " -batch -out " + q(n2(certificateFile)) + " -in " + q(n2(csrFile)),
968
945
  options
969
946
  );
970
947
  displaySubtitle("- dump the certificate for a check");
971
- await execute_openssl(`x509 -in ${q3(n4(certificateFile))} -dates -fingerprint -purpose -noout`, {});
948
+ await execute_openssl(`x509 -in ${q(n2(certificateFile))} -dates -fingerprint -purpose -noout`, {});
972
949
  displaySubtitle("- verify self-signed certificate");
973
- await execute_openssl_no_failure(`verify -verbose -CAfile ${q3(n4(certificateFile))} ${q3(n4(certificateFile))}`, options);
950
+ await execute_openssl_no_failure(`verify -verbose -CAfile ${q(n2(certificateFile))} ${q(n2(certificateFile))}`, options);
974
951
  await fs6.promises.unlink(csrFile);
975
952
  }
976
953
  /**
977
- * revoke a certificate and update the CRL
954
+ * Revoke a certificate and regenerate the CRL.
978
955
  *
979
- * @method revokeCertificate
980
- * @param certificate - the certificate to revoke
981
- * @param params
982
- * @param [params.reason = "keyCompromise" {String}]
983
- * @async
956
+ * @param certificate - path to the certificate file to revoke
957
+ * @param params - revocation parameters
958
+ * @param params.reason - CRL reason code
959
+ * (default `"keyCompromise"`)
984
960
  */
985
961
  async revokeCertificate(certificate, params) {
986
962
  const crlReasons = [
@@ -999,33 +975,36 @@ var CertificateAuthority = class {
999
975
  openssl_conf: makePath(configFile)
1000
976
  };
1001
977
  setEnv("ALTNAME", "");
1002
- const randomFile = path6.join(this.rootDir, "random.rnd");
978
+ const randomFile = path5.join(this.rootDir, "random.rnd");
1003
979
  setEnv("RANDFILE", randomFile);
1004
- const configOption = ` -config ${q3(n4(configFile))}`;
980
+ const configOption = ` -config ${q(n2(configFile))}`;
1005
981
  const reason = params.reason || "keyCompromise";
1006
982
  assert6(crlReasons.indexOf(reason) >= 0);
1007
983
  displayTitle(`Revoking certificate ${certificate}`);
1008
984
  displaySubtitle("Revoke certificate");
1009
- await execute_openssl_no_failure(`ca -verbose ${configOption} -revoke ${q3(certificate)} -crl_reason ${reason}`, options);
985
+ await execute_openssl_no_failure(`ca -verbose ${configOption} -revoke ${q(certificate)} -crl_reason ${reason}`, options);
1010
986
  await regenerateCrl(this.revocationList, configOption, options);
1011
987
  displaySubtitle("Verify that certificate is revoked");
1012
988
  await execute_openssl_no_failure(
1013
- "verify -verbose -CRLfile " + q3(n4(this.revocationList)) + " -CAfile " + q3(n4(this.caCertificate)) + " -crl_check " + q3(n4(certificate)),
989
+ "verify -verbose -CRLfile " + q(n2(this.revocationList)) + " -CAfile " + q(n2(this.caCertificate)) + " -crl_check " + q(n2(certificate)),
1014
990
  options
1015
991
  );
1016
992
  displaySubtitle("Produce CRL in DER form ");
1017
- await execute_openssl(`crl -in ${q3(n4(this.revocationList))} -out crl/revocation_list.der -outform der`, options);
993
+ await execute_openssl(`crl -in ${q(n2(this.revocationList))} -out crl/revocation_list.der -outform der`, options);
1018
994
  displaySubtitle("Produce CRL in PEM form ");
1019
- await execute_openssl(`crl -in ${q3(n4(this.revocationList))} -out crl/revocation_list.pem -outform pem -text `, options);
995
+ await execute_openssl(`crl -in ${q(n2(this.revocationList))} -out crl/revocation_list.pem -outform pem -text `, options);
1020
996
  }
1021
997
  /**
998
+ * Sign a Certificate Signing Request (CSR) with this CA.
999
+ *
1000
+ * The signed certificate is written to `certificate`, and the
1001
+ * CA certificate chain plus CRL are appended to form a
1002
+ * complete certificate chain.
1022
1003
  *
1023
- * @param certificate - the certificate filename to generate
1024
- * @param certificateSigningRequestFilename - the certificate signing request
1025
- * @param params - parameters
1026
- * @param params.applicationUri - the applicationUri
1027
- * @param params.startDate - startDate of the certificate
1028
- * @param params.validity - number of day of validity of the certificate
1004
+ * @param certificate - output path for the signed certificate
1005
+ * @param certificateSigningRequestFilename - path to the CSR
1006
+ * @param params1 - signing parameters (validity, dates, SANs)
1007
+ * @returns the path to the signed certificate
1029
1008
  */
1030
1009
  async signCertificateRequest(certificate, certificateSigningRequestFilename, params1) {
1031
1010
  await ensure_openssl_installed();
@@ -1043,12 +1022,12 @@ var CertificateAuthority = class {
1043
1022
  if (typeof applicationUri !== "string") {
1044
1023
  throw new Error("Cannot find applicationUri in CSR");
1045
1024
  }
1046
- const dns2 = csrInfo.extensionRequest.subjectAltName.dNSName || [];
1025
+ const dns = csrInfo.extensionRequest.subjectAltName.dNSName || [];
1047
1026
  let ip = csrInfo.extensionRequest.subjectAltName.iPAddress || [];
1048
1027
  ip = ip.map(octetStringToIpAddress);
1049
1028
  const params = {
1050
1029
  applicationUri,
1051
- dns: dns2,
1030
+ dns,
1052
1031
  ip
1053
1032
  };
1054
1033
  processAltNames(params);
@@ -1056,11 +1035,11 @@ var CertificateAuthority = class {
1056
1035
  displaySubtitle("- then we ask the authority to sign the certificate signing request");
1057
1036
  const configOption = ` -config ${configFile}`;
1058
1037
  await execute_openssl(
1059
- "ca " + configOption + " -startdate " + x509Date(params1.startDate) + " -enddate " + x509Date(params1.endDate) + " -batch -out " + q3(n4(certificate)) + " -in " + q3(n4(certificateSigningRequestFilename)),
1038
+ "ca " + configOption + " -startdate " + x509Date(params1.startDate) + " -enddate " + x509Date(params1.endDate) + " -batch -out " + q(n2(certificate)) + " -in " + q(n2(certificateSigningRequestFilename)),
1060
1039
  options
1061
1040
  );
1062
1041
  displaySubtitle("- dump the certificate for a check");
1063
- await execute_openssl(`x509 -in ${q3(n4(certificate))} -dates -fingerprint -purpose -noout`, options);
1042
+ await execute_openssl(`x509 -in ${q(n2(certificate))} -dates -fingerprint -purpose -noout`, options);
1064
1043
  displaySubtitle("- construct CA certificate with CRL");
1065
1044
  await this.constructCACertificateWithCRL();
1066
1045
  displaySubtitle("- construct certificate chain");
@@ -1069,6 +1048,11 @@ var CertificateAuthority = class {
1069
1048
  await this.verifyCertificate(certificate);
1070
1049
  return certificate;
1071
1050
  }
1051
+ /**
1052
+ * Verify a certificate against this CA.
1053
+ *
1054
+ * @param certificate - path to the certificate file to verify
1055
+ */
1072
1056
  async verifyCertificate(certificate) {
1073
1057
  const isImplemented = false;
1074
1058
  if (isImplemented) {
@@ -1078,98 +1062,16 @@ var CertificateAuthority = class {
1078
1062
  const _configOption = ` -config ${configFile}`;
1079
1063
  _configOption;
1080
1064
  await execute_openssl_no_failure(
1081
- `verify -verbose -CAfile ${q3(n4(this.caCertificateWithCrl))} ${q3(n4(certificate))}`,
1065
+ `verify -verbose -CAfile ${q(n2(this.caCertificateWithCrl))} ${q(n2(certificate))}`,
1082
1066
  options
1083
1067
  );
1084
1068
  }
1085
1069
  }
1086
1070
  };
1087
1071
 
1088
- // packages/node-opcua-pki/lib/ca/crypto_create_CA.ts
1089
- import assert11 from "assert";
1090
- import fs10 from "fs";
1091
- import { createRequire } from "module";
1092
- import os4 from "os";
1093
- import path8 from "path";
1094
- import chalk7 from "chalk";
1095
- import { CertificatePurpose as CertificatePurpose2, generatePrivateKeyFile as generatePrivateKeyFile3, Subject as Subject5 } from "node-opcua-crypto";
1096
-
1097
- // packages/node-opcua-pki/lib/misc/applicationurn.ts
1098
- import assert7 from "assert";
1099
- import { createHash } from "crypto";
1100
- function makeApplicationUrn(hostname, suffix) {
1101
- let hostnameHash = hostname;
1102
- if (hostnameHash.length + 7 + suffix.length >= 64) {
1103
- hostnameHash = createHash("md5").update(hostname).digest("hex").substring(0, 16);
1104
- }
1105
- const applicationUrn = `urn:${hostnameHash}:${suffix}`;
1106
- assert7(applicationUrn.length <= 64);
1107
- return applicationUrn;
1108
- }
1109
-
1110
- // packages/node-opcua-pki/lib/misc/hostname.ts
1111
- import dns from "dns";
1112
- import os3 from "os";
1113
- import { promisify } from "util";
1114
- function trim(str, length) {
1115
- if (!length) {
1116
- return str;
1117
- }
1118
- return str.substring(0, Math.min(str.length, length));
1119
- }
1120
- function fqdn(callback) {
1121
- const uqdn = os3.hostname();
1122
- dns.lookup(uqdn, { hints: dns.ADDRCONFIG }, (err1, ip) => {
1123
- if (err1) {
1124
- return callback(err1);
1125
- }
1126
- dns.lookupService(ip, 0, (err2, _fqdn) => {
1127
- if (err2) {
1128
- return callback(err2);
1129
- }
1130
- _fqdn = _fqdn.replace(".localdomain", "");
1131
- callback(null, _fqdn);
1132
- });
1133
- });
1134
- }
1135
- var _fullyQualifiedDomainNameInCache;
1136
- async function extractFullyQualifiedDomainName() {
1137
- if (_fullyQualifiedDomainNameInCache) {
1138
- return _fullyQualifiedDomainNameInCache;
1139
- }
1140
- if (process.platform === "win32") {
1141
- const env = process.env;
1142
- _fullyQualifiedDomainNameInCache = env.COMPUTERNAME + (env.USERDNSDOMAIN && env.USERDNSDOMAIN?.length > 0 ? `.${env.USERDNSDOMAIN}` : "");
1143
- } else {
1144
- try {
1145
- _fullyQualifiedDomainNameInCache = await promisify(fqdn)();
1146
- if (_fullyQualifiedDomainNameInCache === "localhost") {
1147
- throw new Error("localhost not expected");
1148
- }
1149
- if (/sethostname/.test(_fullyQualifiedDomainNameInCache)) {
1150
- throw new Error("Detecting fqdn on windows !!!");
1151
- }
1152
- } catch (_err) {
1153
- _fullyQualifiedDomainNameInCache = os3.hostname();
1154
- }
1155
- }
1156
- return _fullyQualifiedDomainNameInCache;
1157
- }
1158
- async function prepareFQDN() {
1159
- _fullyQualifiedDomainNameInCache = await extractFullyQualifiedDomainName();
1160
- }
1161
- function getFullyQualifiedDomainName(optional_max_length) {
1162
- if (!_fullyQualifiedDomainNameInCache) {
1163
- throw new Error("FullyQualifiedDomainName computation is not completed yet");
1164
- }
1165
- return _fullyQualifiedDomainNameInCache ? trim(_fullyQualifiedDomainNameInCache, optional_max_length) : "%FQDN%";
1166
- }
1167
- prepareFQDN();
1168
-
1169
1072
  // packages/node-opcua-pki/lib/pki/certificate_manager.ts
1170
- import assert10 from "assert";
1171
1073
  import fs9 from "fs";
1172
- import path7 from "path";
1074
+ import path6 from "path";
1173
1075
  import { withLock } from "@ster5/global-mutex";
1174
1076
  import chalk6 from "chalk";
1175
1077
  import chokidar from "chokidar";
@@ -1188,18 +1090,18 @@ import {
1188
1090
  } from "node-opcua-crypto";
1189
1091
 
1190
1092
  // packages/node-opcua-pki/lib/toolbox/without_openssl/create_certificate_signing_request.ts
1191
- import assert8 from "assert";
1093
+ import assert7 from "assert";
1192
1094
  import fs7 from "fs";
1193
1095
  import { createCertificateSigningRequest, pemToPrivateKey, Subject as Subject3 } from "node-opcua-crypto";
1194
1096
  async function createCertificateSigningRequestAsync(certificateSigningRequestFilename, params) {
1195
- assert8(params);
1196
- assert8(params.rootDir);
1197
- assert8(params.configFile);
1198
- assert8(params.privateKey);
1199
- assert8(typeof params.privateKey === "string");
1200
- assert8(fs7.existsSync(params.privateKey), `Private key must exist${params.privateKey}`);
1201
- assert8(fs7.existsSync(params.rootDir), "RootDir key must exist");
1202
- assert8(typeof certificateSigningRequestFilename === "string");
1097
+ assert7(params);
1098
+ assert7(params.rootDir);
1099
+ assert7(params.configFile);
1100
+ assert7(params.privateKey);
1101
+ assert7(typeof params.privateKey === "string");
1102
+ assert7(fs7.existsSync(params.privateKey), `Private key must exist${params.privateKey}`);
1103
+ assert7(fs7.existsSync(params.rootDir), "RootDir key must exist");
1104
+ assert7(typeof certificateSigningRequestFilename === "string");
1203
1105
  const subject = params.subject ? new Subject3(params.subject).toString() : void 0;
1204
1106
  displaySubtitle("- Creating a Certificate Signing Request with subtile");
1205
1107
  const privateKeyPem = await fs7.promises.readFile(params.privateKey, "utf-8");
@@ -1218,7 +1120,7 @@ async function createCertificateSigningRequestAsync(certificateSigningRequestFil
1218
1120
  }
1219
1121
 
1220
1122
  // packages/node-opcua-pki/lib/toolbox/without_openssl/create_self_signed_certificate.ts
1221
- import assert9 from "assert";
1123
+ import assert8 from "assert";
1222
1124
  import fs8 from "fs";
1223
1125
  import {
1224
1126
  CertificatePurpose,
@@ -1228,17 +1130,17 @@ import {
1228
1130
  } from "node-opcua-crypto";
1229
1131
  async function createSelfSignedCertificateAsync(certificate, params) {
1230
1132
  params.purpose = params.purpose || CertificatePurpose.ForApplication;
1231
- assert9(params.purpose, "Please provide a Certificate Purpose");
1232
- assert9(fs8.existsSync(params.configFile));
1233
- assert9(fs8.existsSync(params.rootDir));
1234
- assert9(fs8.existsSync(params.privateKey));
1133
+ assert8(params.purpose, "Please provide a Certificate Purpose");
1134
+ assert8(fs8.existsSync(params.configFile));
1135
+ assert8(fs8.existsSync(params.rootDir));
1136
+ assert8(fs8.existsSync(params.privateKey));
1235
1137
  if (!params.subject) {
1236
1138
  throw Error("Missing subject");
1237
1139
  }
1238
- assert9(typeof params.applicationUri === "string");
1239
- assert9(Array.isArray(params.dns));
1140
+ assert8(typeof params.applicationUri === "string");
1141
+ assert8(Array.isArray(params.dns));
1240
1142
  adjustDate(params);
1241
- assert9(Object.prototype.hasOwnProperty.call(params, "validity"));
1143
+ assert8(Object.prototype.hasOwnProperty.call(params, "validity"));
1242
1144
  let subject = new Subject4(params.subject);
1243
1145
  subject = subject.toString();
1244
1146
  const purpose = params.purpose;
@@ -1269,6 +1171,12 @@ var simple_config_template_cnf_default = config3;
1269
1171
  // packages/node-opcua-pki/lib/pki/certificate_manager.ts
1270
1172
  var configurationFileSimpleTemplate = simple_config_template_cnf_default;
1271
1173
  var fsWriteFile = fs9.promises.writeFile;
1174
+ function getOrComputeInfo(entry) {
1175
+ if (!entry.info) {
1176
+ entry.info = exploreCertificate(entry.certificate);
1177
+ }
1178
+ return entry.info;
1179
+ }
1272
1180
  var VerificationStatus = /* @__PURE__ */ ((VerificationStatus2) => {
1273
1181
  VerificationStatus2["BadCertificateInvalid"] = "BadCertificateInvalid";
1274
1182
  VerificationStatus2["BadSecurityChecksFailed"] = "BadSecurityChecksFailed";
@@ -1289,28 +1197,28 @@ var VerificationStatus = /* @__PURE__ */ ((VerificationStatus2) => {
1289
1197
  return VerificationStatus2;
1290
1198
  })(VerificationStatus || {});
1291
1199
  function makeFingerprint(certificate) {
1292
- return makeSHA1Thumbprint(certificate).toString("hex");
1200
+ const chain = split_der(certificate);
1201
+ return makeSHA1Thumbprint(chain[0]).toString("hex");
1293
1202
  }
1294
1203
  function short(stringToShorten) {
1295
1204
  return stringToShorten.substring(0, 10);
1296
1205
  }
1297
1206
  var forbiddenChars = /[\x00-\x1F<>:"/\\|?*]/g;
1298
1207
  function buildIdealCertificateName(certificate) {
1299
- const fingerprint2 = makeFingerprint(certificate);
1208
+ const fingerprint = makeFingerprint(certificate);
1300
1209
  try {
1301
1210
  const commonName = exploreCertificate(certificate).tbsCertificate.subject.commonName || "";
1302
1211
  const sanitizedCommonName = commonName.replace(forbiddenChars, "_");
1303
- return `${sanitizedCommonName}[${fingerprint2}]`;
1212
+ return `${sanitizedCommonName}[${fingerprint}]`;
1304
1213
  } catch (_err) {
1305
- return `invalid_certificate_[${fingerprint2}]`;
1214
+ return `invalid_certificate_[${fingerprint}]`;
1306
1215
  }
1307
1216
  }
1308
1217
  function findMatchingIssuerKey(entries, wantedIssuerKey) {
1309
- const selected = entries.filter(({ certificate }) => {
1310
- const info = exploreCertificate(certificate);
1218
+ return entries.filter((entry) => {
1219
+ const info = getOrComputeInfo(entry);
1311
1220
  return info.tbsCertificate.extensions && info.tbsCertificate.extensions.subjectKeyIdentifier === wantedIssuerKey;
1312
1221
  });
1313
- return selected;
1314
1222
  }
1315
1223
  function isSelfSigned2(info) {
1316
1224
  return info.tbsCertificate.extensions?.subjectKeyIdentifier === info.tbsCertificate.extensions?.authorityKeyIdentifier?.keyIdentifier;
@@ -1353,104 +1261,184 @@ var CertificateManagerState = /* @__PURE__ */ ((CertificateManagerState2) => {
1353
1261
  CertificateManagerState2[CertificateManagerState2["Disposed"] = 4] = "Disposed";
1354
1262
  return CertificateManagerState2;
1355
1263
  })(CertificateManagerState || {});
1356
- var CertificateManager = class {
1264
+ var CertificateManager = class _CertificateManager {
1265
+ // ── Global instance registry ─────────────────────────────────
1266
+ // Tracks all initialized CertificateManager instances so their
1267
+ // file watchers can be closed automatically on process exit,
1268
+ // even if the consumer forgets to call dispose().
1269
+ static #activeInstances = /* @__PURE__ */ new Set();
1270
+ static #cleanupInstalled = false;
1271
+ static #installProcessCleanup() {
1272
+ if (_CertificateManager.#cleanupInstalled) return;
1273
+ _CertificateManager.#cleanupInstalled = true;
1274
+ const closeDanglingWatchers = () => {
1275
+ for (const cm of _CertificateManager.#activeInstances) {
1276
+ for (const w of cm.#watchers) {
1277
+ try {
1278
+ w.close();
1279
+ } catch {
1280
+ }
1281
+ }
1282
+ cm.#watchers.splice(0);
1283
+ cm.state = 4 /* Disposed */;
1284
+ }
1285
+ _CertificateManager.#activeInstances.clear();
1286
+ };
1287
+ process.on("beforeExit", closeDanglingWatchers);
1288
+ for (const signal of ["SIGINT", "SIGTERM"]) {
1289
+ process.once(signal, () => {
1290
+ closeDanglingWatchers();
1291
+ process.exit();
1292
+ });
1293
+ }
1294
+ }
1295
+ /**
1296
+ * Dispose **all** active CertificateManager instances,
1297
+ * closing their file watchers and freeing resources.
1298
+ *
1299
+ * This is mainly useful in test tear-down to ensure the
1300
+ * Node.js process can exit cleanly.
1301
+ */
1302
+ static async disposeAll() {
1303
+ const instances = [..._CertificateManager.#activeInstances];
1304
+ await Promise.all(instances.map((cm) => cm.dispose()));
1305
+ }
1306
+ /**
1307
+ * Assert that all CertificateManager instances have been
1308
+ * properly disposed. Throws an Error listing the locations
1309
+ * of any leaked instances.
1310
+ *
1311
+ * Intended for use in test `afterAll()` / `afterEach()`
1312
+ * hooks to catch missing `dispose()` calls early.
1313
+ *
1314
+ * @example
1315
+ * ```ts
1316
+ * after(() => {
1317
+ * CertificateManager.checkAllDisposed();
1318
+ * });
1319
+ * ```
1320
+ */
1321
+ static checkAllDisposed() {
1322
+ if (_CertificateManager.#activeInstances.size === 0) return;
1323
+ const locations = [..._CertificateManager.#activeInstances].map((cm) => cm.rootDir);
1324
+ throw new Error(
1325
+ `${_CertificateManager.#activeInstances.size} CertificateManager instance(s) not disposed:
1326
+ - ${locations.join("\n - ")}`
1327
+ );
1328
+ }
1329
+ // ─────────────────────────────────────────────────────────────
1330
+ /**
1331
+ * When `true` (the default), any certificate that is not
1332
+ * already in the trusted or rejected store is automatically
1333
+ * written to the rejected folder the first time it is seen.
1334
+ */
1357
1335
  untrustUnknownCertificate = true;
1336
+ /** Current lifecycle state of this instance. */
1358
1337
  state = 0 /* Uninitialized */;
1338
+ /** @deprecated Use {@link folderPollingInterval} instead (typo fix). */
1359
1339
  folderPoolingInterval = 5e3;
1340
+ /** Interval in milliseconds for file-system polling (when enabled). */
1341
+ get folderPollingInterval() {
1342
+ return this.folderPoolingInterval;
1343
+ }
1344
+ set folderPollingInterval(value) {
1345
+ this.folderPoolingInterval = value;
1346
+ }
1347
+ /** RSA key size used when generating the private key. */
1360
1348
  keySize;
1361
- location;
1362
- _watchers = [];
1363
- _readCertificatesCalled = false;
1364
- _filenameToHash = {};
1365
- _thumbs = {
1366
- rejected: {},
1367
- trusted: {},
1349
+ #location;
1350
+ #watchers = [];
1351
+ #readCertificatesCalled = false;
1352
+ #filenameToHash = /* @__PURE__ */ new Map();
1353
+ #initializingPromise;
1354
+ #thumbs = {
1355
+ rejected: /* @__PURE__ */ new Map(),
1356
+ trusted: /* @__PURE__ */ new Map(),
1368
1357
  issuers: {
1369
- certs: {}
1358
+ certs: /* @__PURE__ */ new Map()
1370
1359
  },
1371
- crl: {},
1372
- issuersCrl: {}
1360
+ crl: /* @__PURE__ */ new Map(),
1361
+ issuersCrl: /* @__PURE__ */ new Map()
1373
1362
  };
1363
+ /**
1364
+ * Create a new CertificateManager.
1365
+ *
1366
+ * The constructor creates the root directory if it does not
1367
+ * exist but does **not** initialise the PKI store — call
1368
+ * {@link initialize} before using any other method.
1369
+ *
1370
+ * @param options - configuration options
1371
+ */
1374
1372
  constructor(options) {
1375
1373
  options.keySize = options.keySize || 2048;
1376
- assert10(Object.prototype.hasOwnProperty.call(options, "location"));
1377
- assert10(Object.prototype.hasOwnProperty.call(options, "keySize"));
1378
- assert10(this.state === 0 /* Uninitialized */);
1379
- this.location = makePath(options.location, "");
1374
+ if (!options.location) {
1375
+ throw new Error("CertificateManager: missing 'location' option");
1376
+ }
1377
+ this.#location = makePath(options.location, "");
1380
1378
  this.keySize = options.keySize;
1381
1379
  mkdirRecursiveSync(options.location);
1382
- if (!fs9.existsSync(this.location)) {
1383
- throw new Error(`CertificateManager cannot access location ${this.location}`);
1380
+ if (!fs9.existsSync(this.#location)) {
1381
+ throw new Error(`CertificateManager cannot access location ${this.#location}`);
1384
1382
  }
1385
1383
  }
1384
+ /** Path to the OpenSSL configuration file. */
1386
1385
  get configFile() {
1387
- return path7.join(this.rootDir, "own/openssl.cnf");
1386
+ return path6.join(this.rootDir, "own/openssl.cnf");
1388
1387
  }
1388
+ /** Root directory of the PKI store. */
1389
1389
  get rootDir() {
1390
- return this.location;
1390
+ return this.#location;
1391
1391
  }
1392
+ /** Path to the private key file (`own/private/private_key.pem`). */
1392
1393
  get privateKey() {
1393
- return path7.join(this.rootDir, "own/private/private_key.pem");
1394
+ return path6.join(this.rootDir, "own/private/private_key.pem");
1394
1395
  }
1396
+ /** Path to the OpenSSL random seed file. */
1395
1397
  get randomFile() {
1396
- return path7.join(this.rootDir, "./random.rnd");
1397
- }
1398
- /**
1399
- * returns the certificate status trusted/rejected
1400
- * @param certificate
1401
- */
1402
- async getCertificateStatus(certificate) {
1403
- await this.initialize();
1404
- let status = await this._checkRejectedOrTrusted(certificate);
1405
- if (status === "unknown") {
1406
- assert10(certificate instanceof Buffer);
1407
- const pem = toPem(certificate, "CERTIFICATE");
1408
- const fingerprint2 = makeFingerprint(certificate);
1409
- const filename = path7.join(this.rejectedFolder, `${buildIdealCertificateName(certificate)}.pem`);
1410
- await fs9.promises.writeFile(filename, pem);
1411
- this._thumbs.rejected[fingerprint2] = {
1412
- certificate,
1413
- filename
1414
- };
1415
- status = "rejected";
1416
- }
1417
- return status;
1398
+ return path6.join(this.rootDir, "./random.rnd");
1418
1399
  }
1419
1400
  /**
1420
1401
  * Move a certificate to the rejected store.
1421
- * If the certificate was previously trusted, it will be moved.
1402
+ * If the certificate was previously trusted, it will be removed from the trusted folder.
1422
1403
  * @param certificate - the DER-encoded certificate
1423
1404
  */
1424
1405
  async rejectCertificate(certificate) {
1425
- await this._moveCertificate(certificate, "rejected");
1406
+ await this.#moveCertificate(certificate, "rejected");
1426
1407
  }
1427
1408
  /**
1428
1409
  * Move a certificate to the trusted store.
1429
- * If the certificate was previously rejected, it will be moved.
1410
+ * If the certificate was previously rejected, it will be removed from the rejected folder.
1430
1411
  * @param certificate - the DER-encoded certificate
1431
1412
  */
1432
1413
  async trustCertificate(certificate) {
1433
- await this._moveCertificate(certificate, "trusted");
1414
+ await this.#moveCertificate(certificate, "trusted");
1434
1415
  }
1435
1416
  /** Path to the rejected certificates folder. */
1436
1417
  get rejectedFolder() {
1437
- return path7.join(this.rootDir, "rejected");
1418
+ return path6.join(this.rootDir, "rejected");
1438
1419
  }
1439
1420
  /** Path to the trusted certificates folder. */
1440
1421
  get trustedFolder() {
1441
- return path7.join(this.rootDir, "trusted/certs");
1422
+ return path6.join(this.rootDir, "trusted/certs");
1442
1423
  }
1443
1424
  /** Path to the trusted CRL folder. */
1444
1425
  get crlFolder() {
1445
- return path7.join(this.rootDir, "trusted/crl");
1426
+ return path6.join(this.rootDir, "trusted/crl");
1446
1427
  }
1447
1428
  /** Path to the issuer (CA) certificates folder. */
1448
1429
  get issuersCertFolder() {
1449
- return path7.join(this.rootDir, "issuers/certs");
1430
+ return path6.join(this.rootDir, "issuers/certs");
1450
1431
  }
1451
1432
  /** Path to the issuer CRL folder. */
1452
1433
  get issuersCrlFolder() {
1453
- return path7.join(this.rootDir, "issuers/crl");
1434
+ return path6.join(this.rootDir, "issuers/crl");
1435
+ }
1436
+ /** Path to the own certificate folder. */
1437
+ get ownCertFolder() {
1438
+ return path6.join(this.rootDir, "own/certs");
1439
+ }
1440
+ get ownPrivateFolder() {
1441
+ return path6.join(this.rootDir, "own/private");
1454
1442
  }
1455
1443
  /**
1456
1444
  * Check if a certificate is in the trusted store.
@@ -1461,33 +1449,27 @@ var CertificateManager = class {
1461
1449
  * or `"BadCertificateInvalid"` if the certificate cannot be parsed.
1462
1450
  */
1463
1451
  async isCertificateTrusted(certificate) {
1464
- const fingerprint2 = makeFingerprint(certificate);
1465
- const certificateInTrust = this._thumbs.trusted[fingerprint2]?.certificate;
1466
- if (certificateInTrust) {
1452
+ const fingerprint = makeFingerprint(certificate);
1453
+ if (this.#thumbs.trusted.has(fingerprint)) {
1467
1454
  return "Good";
1468
- } else {
1469
- const certificateInRejected = this._thumbs.rejected[fingerprint2];
1470
- if (!certificateInRejected) {
1471
- const certificateFilenameInRejected = path7.join(
1472
- this.rejectedFolder,
1473
- `${buildIdealCertificateName(certificate)}.pem`
1474
- );
1475
- if (!this.untrustUnknownCertificate) {
1476
- return "Good";
1477
- }
1478
- try {
1479
- const _certificateInfo = exploreCertificateInfo(certificate);
1480
- _certificateInfo;
1481
- } catch (_err) {
1482
- return "BadCertificateInvalid";
1483
- }
1484
- debugLog("certificate has never been seen before and is now rejected (untrusted) ", certificateFilenameInRejected);
1485
- await fsWriteFile(certificateFilenameInRejected, toPem(certificate, "CERTIFICATE"));
1455
+ }
1456
+ if (!this.#thumbs.rejected.has(fingerprint)) {
1457
+ if (!this.untrustUnknownCertificate) {
1458
+ return "Good";
1459
+ }
1460
+ try {
1461
+ exploreCertificateInfo(certificate);
1462
+ } catch (_err) {
1463
+ return "BadCertificateInvalid";
1486
1464
  }
1487
- return "BadCertificateUntrusted";
1465
+ const filename = path6.join(this.rejectedFolder, `${buildIdealCertificateName(certificate)}.pem`);
1466
+ debugLog("certificate has never been seen before and is now rejected (untrusted) ", filename);
1467
+ await fsWriteFile(filename, toPem(certificate, "CERTIFICATE"));
1468
+ this.#thumbs.rejected.set(fingerprint, { certificate, filename });
1488
1469
  }
1470
+ return "BadCertificateUntrusted";
1489
1471
  }
1490
- async _innerVerifyCertificateAsync(certificate, _isIssuer, level, options) {
1472
+ async #innerVerifyCertificateAsync(certificate, _isIssuer, level, options) {
1491
1473
  if (level >= 5) {
1492
1474
  return "BadSecurityChecksFailed" /* BadSecurityChecksFailed */;
1493
1475
  }
@@ -1522,7 +1504,7 @@ var CertificateManager = class {
1522
1504
  } else {
1523
1505
  debugLog(" the issuer certificate has been found in the issuer.cert folder !");
1524
1506
  }
1525
- const issuerStatus = await this._innerVerifyCertificateAsync(issuerCertificate, true, level + 1, options);
1507
+ const issuerStatus = await this.#innerVerifyCertificateAsync(issuerCertificate, true, level + 1, options);
1526
1508
  if (issuerStatus === "BadCertificateRevocationUnknown" /* BadCertificateRevocationUnknown */) {
1527
1509
  return "BadCertificateIssuerRevocationUnknown" /* BadCertificateIssuerRevocationUnknown */;
1528
1510
  }
@@ -1556,7 +1538,7 @@ var CertificateManager = class {
1556
1538
  debugLog("revokedStatus", revokedStatus);
1557
1539
  return revokedStatus;
1558
1540
  }
1559
- const issuerTrustedStatus = await this._checkRejectedOrTrusted(issuerCertificate);
1541
+ const issuerTrustedStatus = await this.#checkRejectedOrTrusted(issuerCertificate);
1560
1542
  debugLog("issuerTrustedStatus", issuerTrustedStatus);
1561
1543
  if (issuerTrustedStatus === "unknown") {
1562
1544
  hasTrustedIssuer = false;
@@ -1575,12 +1557,14 @@ var CertificateManager = class {
1575
1557
  debugLog("revokedStatus of self signed certificate:", revokedStatus);
1576
1558
  }
1577
1559
  }
1578
- const status = await this._checkRejectedOrTrusted(certificate);
1560
+ const status = await this.#checkRejectedOrTrusted(certificate);
1579
1561
  if (status === "rejected") {
1580
- return "BadCertificateUntrusted" /* BadCertificateUntrusted */;
1562
+ if (!(options.acceptCertificateWithValidIssuerChain && hasValidIssuer && hasTrustedIssuer)) {
1563
+ return "BadCertificateUntrusted" /* BadCertificateUntrusted */;
1564
+ }
1581
1565
  }
1582
1566
  const _c2 = chain[1] ? exploreCertificateInfo(chain[1]) : "non";
1583
- _c2;
1567
+ debugLog("chain[1] info=", _c2);
1584
1568
  const certificateInfo = exploreCertificateInfo(certificate);
1585
1569
  const now = /* @__PURE__ */ new Date();
1586
1570
  let isTimeInvalid = false;
@@ -1603,7 +1587,6 @@ var CertificateManager = class {
1603
1587
  if (status === "trusted") {
1604
1588
  return isTimeInvalid ? "BadCertificateTimeInvalid" /* BadCertificateTimeInvalid */ : "Good" /* Good */;
1605
1589
  }
1606
- assert10(status === "unknown");
1607
1590
  if (hasIssuerKey) {
1608
1591
  if (!hasTrustedIssuer) {
1609
1592
  return "BadCertificateUntrusted" /* BadCertificateUntrusted */;
@@ -1611,78 +1594,103 @@ var CertificateManager = class {
1611
1594
  if (!hasValidIssuer) {
1612
1595
  return "BadCertificateUntrusted" /* BadCertificateUntrusted */;
1613
1596
  }
1597
+ if (!options.acceptCertificateWithValidIssuerChain) {
1598
+ return "BadCertificateUntrusted" /* BadCertificateUntrusted */;
1599
+ }
1614
1600
  return isTimeInvalid ? "BadCertificateTimeInvalid" /* BadCertificateTimeInvalid */ : "Good" /* Good */;
1615
1601
  } else {
1616
1602
  return "BadCertificateUntrusted" /* BadCertificateUntrusted */;
1617
1603
  }
1618
1604
  }
1605
+ /**
1606
+ * Internal verification hook called by {@link verifyCertificate}.
1607
+ *
1608
+ * Subclasses can override this to inject additional validation
1609
+ * logic (e.g. application-level policy checks) while still
1610
+ * delegating to the default chain/CRL/trust verification.
1611
+ *
1612
+ * @param certificate - the DER-encoded certificate to verify
1613
+ * @param options - verification options forwarded from the
1614
+ * public API
1615
+ * @returns the verification status code
1616
+ */
1619
1617
  async verifyCertificateAsync(certificate, options) {
1620
- const status1 = await this._innerVerifyCertificateAsync(certificate, false, 0, options);
1618
+ const status1 = await this.#innerVerifyCertificateAsync(certificate, false, 0, options);
1621
1619
  return status1;
1622
1620
  }
1623
1621
  /**
1624
- * Verify certificate validity
1625
- * @method verifyCertificate
1626
- * @param certificate
1622
+ * Verify a certificate against the PKI trust store.
1623
+ *
1624
+ * This performs a full validation including trust status,
1625
+ * issuer chain, CRL revocation checks, and time validity.
1626
+ *
1627
+ * @param certificate - the DER-encoded certificate to verify
1628
+ * @param options - optional flags to relax validation rules
1629
+ * @returns the verification status code
1627
1630
  */
1628
1631
  async verifyCertificate(certificate, options) {
1629
1632
  if (!certificate) {
1630
1633
  return "BadSecurityChecksFailed" /* BadSecurityChecksFailed */;
1631
1634
  }
1632
- return await this.verifyCertificateAsync(certificate, options || {});
1635
+ try {
1636
+ const status = await this.verifyCertificateAsync(certificate, options || {});
1637
+ return status;
1638
+ } catch (error) {
1639
+ warningLog(`verifyCertificate error: ${error.message}`);
1640
+ return "BadCertificateInvalid" /* BadCertificateInvalid */;
1641
+ }
1633
1642
  }
1634
- /*
1635
- *
1636
- * PKI
1637
- * +---> trusted
1638
- * +---> rejected
1639
- * +---> own
1640
- * +---> cert
1641
- * +---> own
1643
+ /**
1644
+ * Initialize the PKI directory structure, generate the
1645
+ * private key (if missing), and start file-system watchers.
1642
1646
  *
1647
+ * This method is idempotent — subsequent calls are no-ops.
1648
+ * It must be called before any certificate operations.
1643
1649
  */
1644
1650
  async initialize() {
1645
1651
  if (this.state !== 0 /* Uninitialized */) {
1646
1652
  return;
1647
1653
  }
1648
1654
  this.state = 1 /* Initializing */;
1649
- await this._initialize();
1655
+ this.#initializingPromise = this.#initialize();
1656
+ await this.#initializingPromise;
1657
+ this.#initializingPromise = void 0;
1650
1658
  this.state = 2 /* Initialized */;
1659
+ _CertificateManager.#activeInstances.add(this);
1660
+ _CertificateManager.#installProcessCleanup();
1651
1661
  }
1652
- async _initialize() {
1662
+ async #initialize() {
1653
1663
  this.state = 1 /* Initializing */;
1654
- const pkiDir = this.location;
1664
+ const pkiDir = this.#location;
1655
1665
  mkdirRecursiveSync(pkiDir);
1656
- mkdirRecursiveSync(path7.join(pkiDir, "own"));
1657
- mkdirRecursiveSync(path7.join(pkiDir, "own/certs"));
1658
- mkdirRecursiveSync(path7.join(pkiDir, "own/private"));
1659
- mkdirRecursiveSync(path7.join(pkiDir, "rejected"));
1660
- mkdirRecursiveSync(path7.join(pkiDir, "trusted"));
1661
- mkdirRecursiveSync(path7.join(pkiDir, "trusted/certs"));
1662
- mkdirRecursiveSync(path7.join(pkiDir, "trusted/crl"));
1663
- mkdirRecursiveSync(path7.join(pkiDir, "issuers"));
1664
- mkdirRecursiveSync(path7.join(pkiDir, "issuers/certs"));
1665
- mkdirRecursiveSync(path7.join(pkiDir, "issuers/crl"));
1666
+ mkdirRecursiveSync(path6.join(pkiDir, "own"));
1667
+ mkdirRecursiveSync(path6.join(pkiDir, "own/certs"));
1668
+ mkdirRecursiveSync(path6.join(pkiDir, "own/private"));
1669
+ mkdirRecursiveSync(path6.join(pkiDir, "rejected"));
1670
+ mkdirRecursiveSync(path6.join(pkiDir, "trusted"));
1671
+ mkdirRecursiveSync(path6.join(pkiDir, "trusted/certs"));
1672
+ mkdirRecursiveSync(path6.join(pkiDir, "trusted/crl"));
1673
+ mkdirRecursiveSync(path6.join(pkiDir, "issuers"));
1674
+ mkdirRecursiveSync(path6.join(pkiDir, "issuers/certs"));
1675
+ mkdirRecursiveSync(path6.join(pkiDir, "issuers/crl"));
1666
1676
  if (!fs9.existsSync(this.configFile) || !fs9.existsSync(this.privateKey)) {
1667
1677
  return await this.withLock2(async () => {
1668
- assert10(this.state !== 3 /* Disposing */);
1669
- if (this.state === 4 /* Disposed */) {
1678
+ if (this.state === 3 /* Disposing */ || this.state === 4 /* Disposed */) {
1670
1679
  return;
1671
1680
  }
1672
- assert10(this.state === 1 /* Initializing */);
1673
1681
  if (!fs9.existsSync(this.configFile)) {
1674
1682
  fs9.writeFileSync(this.configFile, configurationFileSimpleTemplate);
1675
1683
  }
1676
1684
  if (!fs9.existsSync(this.privateKey)) {
1677
1685
  debugLog("generating private key ...");
1678
1686
  await generatePrivateKeyFile2(this.privateKey, this.keySize);
1679
- await this._readCertificates();
1687
+ await this.#readCertificates();
1680
1688
  } else {
1681
- await this._readCertificates();
1689
+ await this.#readCertificates();
1682
1690
  }
1683
1691
  });
1684
1692
  } else {
1685
- await this._readCertificates();
1693
+ await this.#readCertificates();
1686
1694
  }
1687
1695
  }
1688
1696
  /**
@@ -1699,37 +1707,69 @@ var CertificateManager = class {
1699
1707
  return;
1700
1708
  }
1701
1709
  if (this.state === 1 /* Initializing */) {
1702
- await new Promise((resolve) => setTimeout(resolve, 100));
1703
- return await this.dispose();
1710
+ if (this.#initializingPromise) {
1711
+ await this.#initializingPromise;
1712
+ }
1704
1713
  }
1705
1714
  try {
1706
1715
  this.state = 3 /* Disposing */;
1707
- await Promise.all(this._watchers.map((w) => w.close()));
1708
- this._watchers.forEach((w) => {
1716
+ await Promise.all(this.#watchers.map((w) => w.close()));
1717
+ this.#watchers.forEach((w) => {
1709
1718
  w.removeAllListeners();
1710
1719
  });
1711
- this._watchers.splice(0);
1720
+ this.#watchers.splice(0);
1712
1721
  } finally {
1713
1722
  this.state = 4 /* Disposed */;
1723
+ _CertificateManager.#activeInstances.delete(this);
1714
1724
  }
1715
1725
  }
1726
+ /**
1727
+ * Force a full re-scan of all PKI folders, rebuilding
1728
+ * the in-memory `_thumbs` index from scratch.
1729
+ *
1730
+ * Call this after external processes have modified the
1731
+ * PKI folders (e.g. via `writeTrustList` or CLI tools)
1732
+ * to ensure the CertificateManager sees the latest
1733
+ * state without waiting for file-system events.
1734
+ */
1735
+ async reloadCertificates() {
1736
+ await Promise.all(this.#watchers.map((w) => w.close()));
1737
+ for (const w of this.#watchers) {
1738
+ w.removeAllListeners();
1739
+ }
1740
+ this.#watchers.splice(0);
1741
+ this.#thumbs.rejected.clear();
1742
+ this.#thumbs.trusted.clear();
1743
+ this.#thumbs.issuers.certs.clear();
1744
+ this.#thumbs.crl.clear();
1745
+ this.#thumbs.issuersCrl.clear();
1746
+ this.#filenameToHash.clear();
1747
+ this.#readCertificatesCalled = false;
1748
+ await this.#readCertificates();
1749
+ }
1716
1750
  async withLock2(action) {
1717
- const lockFileName = path7.join(this.rootDir, "mutex.lock");
1751
+ const lockFileName = path6.join(this.rootDir, "mutex.lock");
1718
1752
  return withLock({ fileToLock: lockFileName }, async () => {
1719
1753
  return await action();
1720
1754
  });
1721
1755
  }
1722
1756
  /**
1757
+ * Create a self-signed certificate for this PKI's private key.
1723
1758
  *
1724
- * create a self-signed certificate for the CertificateManager private key
1759
+ * The certificate is written to `params.outputFile` or
1760
+ * `own/certs/self_signed_certificate.pem` by default.
1725
1761
  *
1762
+ * @param params - certificate parameters (subject, SANs,
1763
+ * validity, etc.)
1726
1764
  */
1727
1765
  async createSelfSignedCertificate(params) {
1728
- assert10(typeof params.applicationUri === "string", "expecting applicationUri");
1766
+ if (typeof params.applicationUri !== "string") {
1767
+ throw new Error("createSelfSignedCertificate: expecting applicationUri to be a string");
1768
+ }
1729
1769
  if (!fs9.existsSync(this.privateKey)) {
1730
1770
  throw new Error(`Cannot find private key ${this.privateKey}`);
1731
1771
  }
1732
- let certificateFilename = path7.join(this.rootDir, "own/certs/self_signed_certificate.pem");
1772
+ let certificateFilename = path6.join(this.rootDir, "own/certs/self_signed_certificate.pem");
1733
1773
  certificateFilename = params.outputFile || certificateFilename;
1734
1774
  const _params = params;
1735
1775
  _params.rootDir = this.rootDir;
@@ -1740,22 +1780,31 @@ var CertificateManager = class {
1740
1780
  await createSelfSignedCertificate(certificateFilename, _params);
1741
1781
  });
1742
1782
  }
1783
+ /**
1784
+ * Create a Certificate Signing Request (CSR) using this
1785
+ * PKI's private key and configuration.
1786
+ *
1787
+ * The CSR file is written to `own/certs/` with a timestamped
1788
+ * filename.
1789
+ *
1790
+ * @param params - CSR parameters (subject, SANs)
1791
+ * @returns the filesystem path to the generated CSR file
1792
+ */
1743
1793
  async createCertificateRequest(params) {
1744
- assert10(params);
1794
+ if (!params) {
1795
+ throw new Error("params is required");
1796
+ }
1745
1797
  const _params = params;
1746
1798
  if (Object.prototype.hasOwnProperty.call(_params, "rootDir")) {
1747
1799
  throw new Error("rootDir should not be specified ");
1748
1800
  }
1749
- assert10(!_params.rootDir);
1750
- assert10(!_params.configFile);
1751
- assert10(!_params.privateKey);
1752
- _params.rootDir = path7.resolve(this.rootDir);
1753
- _params.configFile = path7.resolve(this.configFile);
1754
- _params.privateKey = path7.resolve(this.privateKey);
1801
+ _params.rootDir = path6.resolve(this.rootDir);
1802
+ _params.configFile = path6.resolve(this.configFile);
1803
+ _params.privateKey = path6.resolve(this.privateKey);
1755
1804
  return await this.withLock2(async () => {
1756
1805
  const now = /* @__PURE__ */ new Date();
1757
- const today2 = `${now.toISOString().slice(0, 10)}_${now.getTime()}`;
1758
- const certificateSigningRequestFilename = path7.join(this.rootDir, "own/certs", `certificate_${today2}.csr`);
1806
+ const today = `${now.toISOString().slice(0, 10)}_${now.getTime()}`;
1807
+ const certificateSigningRequestFilename = path6.join(this.rootDir, "own/certs", `certificate_${today}.csr`);
1759
1808
  await createCertificateSigningRequestAsync(certificateSigningRequestFilename, _params);
1760
1809
  return certificateSigningRequestFilename;
1761
1810
  });
@@ -1776,13 +1825,13 @@ var CertificateManager = class {
1776
1825
  }
1777
1826
  }
1778
1827
  const pemCertificate = toPem(certificate, "CERTIFICATE");
1779
- const fingerprint2 = makeFingerprint(certificate);
1780
- if (this._thumbs.issuers.certs[fingerprint2]) {
1828
+ const fingerprint = makeFingerprint(certificate);
1829
+ if (this.#thumbs.issuers.certs.has(fingerprint)) {
1781
1830
  return "Good" /* Good */;
1782
1831
  }
1783
- const filename = path7.join(this.issuersCertFolder, `issuer_${buildIdealCertificateName(certificate)}.pem`);
1832
+ const filename = path6.join(this.issuersCertFolder, `issuer_${buildIdealCertificateName(certificate)}.pem`);
1784
1833
  await fs9.promises.writeFile(filename, pemCertificate, "ascii");
1785
- this._thumbs.issuers.certs[fingerprint2] = { certificate, filename };
1834
+ this.#thumbs.issuers.certs.set(fingerprint, { certificate, filename });
1786
1835
  if (addInTrustList) {
1787
1836
  await this.trustCertificate(certificate);
1788
1837
  }
@@ -1796,18 +1845,18 @@ var CertificateManager = class {
1796
1845
  async addRevocationList(crl, target = "issuers") {
1797
1846
  return await this.withLock2(async () => {
1798
1847
  try {
1799
- const index = target === "trusted" ? this._thumbs.crl : this._thumbs.issuersCrl;
1848
+ const index = target === "trusted" ? this.#thumbs.crl : this.#thumbs.issuersCrl;
1800
1849
  const folder = target === "trusted" ? this.crlFolder : this.issuersCrlFolder;
1801
1850
  const crlInfo = exploreCertificateRevocationList(crl);
1802
1851
  const key = crlInfo.tbsCertList.issuerFingerprint;
1803
- if (!index[key]) {
1804
- index[key] = { crls: [], serialNumbers: {} };
1852
+ if (!index.has(key)) {
1853
+ index.set(key, { crls: [], serialNumbers: {} });
1805
1854
  }
1806
1855
  const pemCertificate = toPem(crl, "X509 CRL");
1807
- const filename = path7.join(folder, `crl_${buildIdealCertificateName(crl)}.pem`);
1856
+ const filename = path6.join(folder, `crl_${buildIdealCertificateName(crl)}.pem`);
1808
1857
  await fs9.promises.writeFile(filename, pemCertificate, "ascii");
1809
- await this._on_crl_file_added(index, filename);
1810
- await this.waitAndCheckCRLProcessingStatus();
1858
+ await this.#onCrlFileAdded(index, filename);
1859
+ await this.#waitAndCheckCRLProcessingStatus();
1811
1860
  return "Good" /* Good */;
1812
1861
  } catch (err) {
1813
1862
  debugLog(err);
@@ -1826,9 +1875,9 @@ var CertificateManager = class {
1826
1875
  try {
1827
1876
  const files = await fs9.promises.readdir(folder);
1828
1877
  for (const file of files) {
1829
- const ext = path7.extname(file).toLowerCase();
1878
+ const ext = path6.extname(file).toLowerCase();
1830
1879
  if (ext === ".crl" || ext === ".pem" || ext === ".der") {
1831
- await fs9.promises.unlink(path7.join(folder, file));
1880
+ await fs9.promises.unlink(path6.join(folder, file));
1832
1881
  }
1833
1882
  }
1834
1883
  } catch (err) {
@@ -1836,15 +1885,13 @@ var CertificateManager = class {
1836
1885
  throw err;
1837
1886
  }
1838
1887
  }
1839
- for (const key of Object.keys(index)) {
1840
- delete index[key];
1841
- }
1888
+ index.clear();
1842
1889
  };
1843
1890
  if (target === "issuers" || target === "all") {
1844
- await clearFolder(this.issuersCrlFolder, this._thumbs.issuersCrl);
1891
+ await clearFolder(this.issuersCrlFolder, this.#thumbs.issuersCrl);
1845
1892
  }
1846
1893
  if (target === "trusted" || target === "all") {
1847
- await clearFolder(this.crlFolder, this._thumbs.crl);
1894
+ await clearFolder(this.crlFolder, this.#thumbs.crl);
1848
1895
  }
1849
1896
  }
1850
1897
  /**
@@ -1853,9 +1900,9 @@ var CertificateManager = class {
1853
1900
  * @param thumbprint - hex-encoded SHA-1 thumbprint (lowercase)
1854
1901
  */
1855
1902
  async hasIssuer(thumbprint) {
1856
- await this._readCertificates();
1903
+ await this.#readCertificates();
1857
1904
  const normalized = thumbprint.toLowerCase();
1858
- return Object.prototype.hasOwnProperty.call(this._thumbs.issuers.certs, normalized);
1905
+ return this.#thumbs.issuers.certs.has(normalized);
1859
1906
  }
1860
1907
  /**
1861
1908
  * Remove a trusted certificate identified by its SHA-1 thumbprint.
@@ -1865,9 +1912,9 @@ var CertificateManager = class {
1865
1912
  * @returns the removed certificate buffer, or `null` if not found
1866
1913
  */
1867
1914
  async removeTrustedCertificate(thumbprint) {
1868
- await this._readCertificates();
1915
+ await this.#readCertificates();
1869
1916
  const normalized = thumbprint.toLowerCase();
1870
- const entry = this._thumbs.trusted[normalized];
1917
+ const entry = this.#thumbs.trusted.get(normalized);
1871
1918
  if (!entry) {
1872
1919
  return null;
1873
1920
  }
@@ -1878,7 +1925,7 @@ var CertificateManager = class {
1878
1925
  throw err;
1879
1926
  }
1880
1927
  }
1881
- delete this._thumbs.trusted[normalized];
1928
+ this.#thumbs.trusted.delete(normalized);
1882
1929
  return entry.certificate;
1883
1930
  }
1884
1931
  /**
@@ -1889,9 +1936,9 @@ var CertificateManager = class {
1889
1936
  * @returns the removed certificate buffer, or `null` if not found
1890
1937
  */
1891
1938
  async removeIssuer(thumbprint) {
1892
- await this._readCertificates();
1939
+ await this.#readCertificates();
1893
1940
  const normalized = thumbprint.toLowerCase();
1894
- const entry = this._thumbs.issuers.certs[normalized];
1941
+ const entry = this.#thumbs.issuers.certs.get(normalized);
1895
1942
  if (!entry) {
1896
1943
  return null;
1897
1944
  }
@@ -1902,7 +1949,7 @@ var CertificateManager = class {
1902
1949
  throw err;
1903
1950
  }
1904
1951
  }
1905
- delete this._thumbs.issuers.certs[normalized];
1952
+ this.#thumbs.issuers.certs.delete(normalized);
1906
1953
  return entry.certificate;
1907
1954
  }
1908
1955
  /**
@@ -1915,7 +1962,7 @@ var CertificateManager = class {
1915
1962
  const issuerInfo = exploreCertificate(issuerCertificate);
1916
1963
  const issuerFingerprint = issuerInfo.tbsCertificate.subjectFingerPrint;
1917
1964
  const processIndex = async (index) => {
1918
- const crlData = index[issuerFingerprint];
1965
+ const crlData = index.get(issuerFingerprint);
1919
1966
  if (!crlData) return;
1920
1967
  for (const crlEntry of crlData.crls) {
1921
1968
  try {
@@ -1926,13 +1973,13 @@ var CertificateManager = class {
1926
1973
  }
1927
1974
  }
1928
1975
  }
1929
- delete index[issuerFingerprint];
1976
+ index.delete(issuerFingerprint);
1930
1977
  };
1931
1978
  if (target === "issuers" || target === "all") {
1932
- await processIndex(this._thumbs.issuersCrl);
1979
+ await processIndex(this.#thumbs.issuersCrl);
1933
1980
  }
1934
1981
  if (target === "trusted" || target === "all") {
1935
- await processIndex(this._thumbs.crl);
1982
+ await processIndex(this.#thumbs.crl);
1936
1983
  }
1937
1984
  }
1938
1985
  /**
@@ -1964,9 +2011,23 @@ var CertificateManager = class {
1964
2011
  return "BadCertificateInvalid" /* BadCertificateInvalid */;
1965
2012
  }
1966
2013
  if (certificates.length > 1) {
2014
+ const issuerFolder = this.issuersCertFolder;
2015
+ const issuerThumbprints = /* @__PURE__ */ new Set();
2016
+ const files = await fs9.promises.readdir(issuerFolder);
2017
+ for (const file of files) {
2018
+ const ext = path6.extname(file).toLowerCase();
2019
+ if (ext === ".pem" || ext === ".der") {
2020
+ try {
2021
+ const issuerCert = readCertificate(path6.join(issuerFolder, file));
2022
+ const fp = makeFingerprint(issuerCert);
2023
+ issuerThumbprints.add(fp);
2024
+ } catch (_err) {
2025
+ }
2026
+ }
2027
+ }
1967
2028
  for (const issuerCert of certificates.slice(1)) {
1968
2029
  const thumbprint = makeFingerprint(issuerCert);
1969
- if (!Object.prototype.hasOwnProperty.call(this._thumbs.issuers.certs, thumbprint)) {
2030
+ if (!issuerThumbprints.has(thumbprint)) {
1970
2031
  return "BadCertificateChainIncomplete" /* BadCertificateChainIncomplete */;
1971
2032
  }
1972
2033
  }
@@ -1987,8 +2048,8 @@ var CertificateManager = class {
1987
2048
  * signed by this issuer.
1988
2049
  */
1989
2050
  async isIssuerInUseByTrustedCertificate(issuerCertificate) {
1990
- await this._readCertificates();
1991
- for (const entry of Object.values(this._thumbs.trusted)) {
2051
+ await this.#readCertificates();
2052
+ for (const entry of this.#thumbs.trusted.values()) {
1992
2053
  if (!entry.certificate) continue;
1993
2054
  try {
1994
2055
  if (verifyCertificateSignature(entry.certificate, issuerCertificate)) {
@@ -2023,7 +2084,7 @@ var CertificateManager = class {
2023
2084
  debugLog("Certificate has no extension 3");
2024
2085
  return null;
2025
2086
  }
2026
- const issuerCertificates = Object.values(this._thumbs.issuers.certs);
2087
+ const issuerCertificates = [...this.#thumbs.issuers.certs.values()];
2027
2088
  const selectedIssuerCertificates = findMatchingIssuerKey(issuerCertificates, wantedIssuerKey);
2028
2089
  if (selectedIssuerCertificates.length > 0) {
2029
2090
  if (selectedIssuerCertificates.length > 1) {
@@ -2031,7 +2092,7 @@ var CertificateManager = class {
2031
2092
  }
2032
2093
  return selectedIssuerCertificates[0].certificate || null;
2033
2094
  }
2034
- const trustedCertificates = Object.values(this._thumbs.trusted);
2095
+ const trustedCertificates = [...this.#thumbs.trusted.values()];
2035
2096
  const selectedTrustedCertificates = findMatchingIssuerKey(trustedCertificates, wantedIssuerKey);
2036
2097
  if (selectedTrustedCertificates.length > 1) {
2037
2098
  warningLog(
@@ -2043,53 +2104,78 @@ var CertificateManager = class {
2043
2104
  return selectedTrustedCertificates.length > 0 ? selectedTrustedCertificates[0].certificate : null;
2044
2105
  }
2045
2106
  /**
2107
+ *
2108
+ * check if the certificate explicitly appear in the trust list, the reject list or none.
2109
+ * In case of being in the reject and trusted list at the same time is consider: rejected.
2046
2110
  * @internal
2047
2111
  * @private
2048
2112
  */
2049
- async _checkRejectedOrTrusted(certificate) {
2050
- assert10(certificate instanceof Buffer);
2051
- const fingerprint2 = makeFingerprint(certificate);
2052
- debugLog("_checkRejectedOrTrusted fingerprint ", short(fingerprint2));
2053
- await this._readCertificates();
2054
- if (Object.prototype.hasOwnProperty.call(this._thumbs.rejected, fingerprint2)) {
2113
+ async #checkRejectedOrTrusted(certificate) {
2114
+ const fingerprint = makeFingerprint(certificate);
2115
+ debugLog("#checkRejectedOrTrusted fingerprint ", short(fingerprint));
2116
+ await this.#readCertificates();
2117
+ if (this.#thumbs.rejected.has(fingerprint)) {
2055
2118
  return "rejected";
2056
2119
  }
2057
- if (Object.prototype.hasOwnProperty.call(this._thumbs.trusted, fingerprint2)) {
2120
+ if (this.#thumbs.trusted.has(fingerprint)) {
2058
2121
  return "trusted";
2059
2122
  }
2060
2123
  return "unknown";
2061
2124
  }
2062
- async _moveCertificate(certificate, newStatus) {
2063
- assert10(certificate instanceof Buffer);
2064
- const fingerprint2 = makeFingerprint(certificate);
2065
- const status = await this.getCertificateStatus(certificate);
2066
- debugLog("_moveCertificate", fingerprint2.substring(0, 10), "from", status, "to", newStatus);
2067
- assert10(status === "rejected" || status === "trusted");
2068
- if (status !== newStatus) {
2069
- const indexSrc = status === "rejected" ? this._thumbs.rejected : this._thumbs.trusted;
2070
- const certificateSrc = indexSrc[fingerprint2]?.filename;
2071
- if (!certificateSrc) {
2072
- debugLog(" cannot find certificate ", fingerprint2.substring(0, 10), " in", this._thumbs, [status]);
2073
- throw new Error("internal");
2125
+ async #moveCertificate(certificate, newStatus) {
2126
+ await this.withLock2(async () => {
2127
+ const fingerprint = makeFingerprint(certificate);
2128
+ let status = await this.#checkRejectedOrTrusted(certificate);
2129
+ if (status === "unknown") {
2130
+ const pem = toPem(certificate, "CERTIFICATE");
2131
+ const filename = path6.join(this.rejectedFolder, `${buildIdealCertificateName(certificate)}.pem`);
2132
+ await fs9.promises.writeFile(filename, pem);
2133
+ this.#thumbs.rejected.set(fingerprint, { certificate, filename });
2134
+ status = "rejected";
2074
2135
  }
2075
- const destFolder = newStatus === "rejected" ? this.rejectedFolder : newStatus === "trusted" ? this.trustedFolder : this.rejectedFolder;
2076
- const certificateDest = path7.join(destFolder, path7.basename(certificateSrc));
2077
- debugLog("_moveCertificate1", fingerprint2.substring(0, 10), "old name", certificateSrc);
2078
- debugLog("_moveCertificate1", fingerprint2.substring(0, 10), "new name", certificateDest);
2079
- await fs9.promises.rename(certificateSrc, certificateDest);
2080
- delete indexSrc[fingerprint2];
2081
- const indexDest = newStatus === "rejected" ? this._thumbs.rejected : newStatus === "trusted" ? this._thumbs.trusted : this._thumbs.rejected;
2082
- indexDest[fingerprint2] = {
2083
- certificate,
2084
- filename: certificateDest
2085
- };
2086
- }
2136
+ debugLog("#moveCertificate", fingerprint.substring(0, 10), "from", status, "to", newStatus);
2137
+ if (status !== "rejected" && status !== "trusted") {
2138
+ throw new Error(`#moveCertificate: unexpected status '${status}' for certificate ${fingerprint.substring(0, 10)}`);
2139
+ }
2140
+ if (status !== newStatus) {
2141
+ const indexSrc = status === "rejected" ? this.#thumbs.rejected : this.#thumbs.trusted;
2142
+ const srcEntry = indexSrc.get(fingerprint);
2143
+ if (!srcEntry) {
2144
+ debugLog(" cannot find certificate ", fingerprint.substring(0, 10), " in", status);
2145
+ throw new Error(`#moveCertificate: certificate ${fingerprint.substring(0, 10)} not found in ${status} index`);
2146
+ }
2147
+ const destFolder = newStatus === "trusted" ? this.trustedFolder : this.rejectedFolder;
2148
+ const certificateDest = path6.join(destFolder, path6.basename(srcEntry.filename));
2149
+ debugLog("#moveCertificate", fingerprint.substring(0, 10), "old name", srcEntry.filename);
2150
+ debugLog("#moveCertificate", fingerprint.substring(0, 10), "new name", certificateDest);
2151
+ await fs9.promises.rename(srcEntry.filename, certificateDest);
2152
+ indexSrc.delete(fingerprint);
2153
+ const indexDest = newStatus === "trusted" ? this.#thumbs.trusted : this.#thumbs.rejected;
2154
+ indexDest.set(fingerprint, { certificate, filename: certificateDest });
2155
+ }
2156
+ });
2087
2157
  }
2088
- _findAssociatedCRLs(issuerCertificate) {
2158
+ #findAssociatedCRLs(issuerCertificate) {
2089
2159
  const issuerCertificateInfo = exploreCertificate(issuerCertificate);
2090
2160
  const key = issuerCertificateInfo.tbsCertificate.subjectFingerPrint;
2091
- return this._thumbs.issuersCrl[key] ? this._thumbs.issuersCrl[key] : this._thumbs.crl[key] ? this._thumbs.crl[key] : null;
2161
+ return this.#thumbs.issuersCrl.get(key) ?? this.#thumbs.crl.get(key) ?? null;
2092
2162
  }
2163
+ /**
2164
+ * Check whether a certificate has been revoked by its issuer's CRL.
2165
+ *
2166
+ * - Self-signed certificates are never considered revoked.
2167
+ * - If no `issuerCertificate` is provided, the method attempts
2168
+ * to find it via {@link findIssuerCertificate}.
2169
+ *
2170
+ * @param certificate - the DER-encoded certificate to check
2171
+ * @param issuerCertificate - optional issuer certificate; looked
2172
+ * up automatically when omitted
2173
+ * @returns `Good` if not revoked, `BadCertificateRevoked` if the
2174
+ * serial number appears in a CRL,
2175
+ * `BadCertificateRevocationUnknown` if no CRL is available,
2176
+ * or `BadCertificateChainIncomplete` if the issuer cannot be
2177
+ * found.
2178
+ */
2093
2179
  async isCertificateRevoked(certificate, issuerCertificate) {
2094
2180
  if (isSelfSigned3(certificate)) {
2095
2181
  return "Good" /* Good */;
@@ -2100,913 +2186,251 @@ var CertificateManager = class {
2100
2186
  if (!issuerCertificate) {
2101
2187
  return "BadCertificateChainIncomplete" /* BadCertificateChainIncomplete */;
2102
2188
  }
2103
- const crls = this._findAssociatedCRLs(issuerCertificate);
2189
+ const crls = this.#findAssociatedCRLs(issuerCertificate);
2104
2190
  if (!crls) {
2105
2191
  return "BadCertificateRevocationUnknown" /* BadCertificateRevocationUnknown */;
2106
2192
  }
2107
2193
  const certInfo = exploreCertificate(certificate);
2108
2194
  const serialNumber = certInfo.tbsCertificate.serialNumber || certInfo.tbsCertificate.extensions?.authorityKeyIdentifier?.serial || "";
2109
2195
  const key = certInfo.tbsCertificate.extensions?.authorityKeyIdentifier?.authorityCertIssuerFingerPrint || "<unknown>";
2110
- const crl2 = this._thumbs.crl[key] || null;
2196
+ const crl2 = this.#thumbs.crl.get(key) ?? null;
2111
2197
  if (crls.serialNumbers[serialNumber] || crl2?.serialNumbers[serialNumber]) {
2112
2198
  return "BadCertificateRevoked" /* BadCertificateRevoked */;
2113
2199
  }
2114
2200
  return "Good" /* Good */;
2115
2201
  }
2116
- _pending_crl_to_process = 0;
2117
- _on_crl_process;
2118
- queue = [];
2119
- _on_crl_file_added(index, filename) {
2120
- this.queue.push({ index, filename });
2121
- this._pending_crl_to_process += 1;
2122
- if (this._pending_crl_to_process === 1) {
2123
- this._process_next_crl();
2202
+ #pendingCrlToProcess = 0;
2203
+ #onCrlProcess;
2204
+ #queue = [];
2205
+ #onCrlFileAdded(index, filename) {
2206
+ this.#queue.push({ index, filename });
2207
+ this.#pendingCrlToProcess += 1;
2208
+ if (this.#pendingCrlToProcess === 1) {
2209
+ this.#processNextCrl();
2124
2210
  }
2125
2211
  }
2126
- async _process_next_crl() {
2212
+ async #processNextCrl() {
2127
2213
  try {
2128
- const nextCRL = this.queue.shift();
2214
+ const nextCRL = this.#queue.shift();
2129
2215
  if (!nextCRL) return;
2130
2216
  const { index, filename } = nextCRL;
2131
2217
  const crl = await readCertificateRevocationList(filename);
2132
2218
  const crlInfo = exploreCertificateRevocationList(crl);
2133
2219
  debugLog(chalk6.cyan("add CRL in folder "), filename);
2134
- const fingerprint2 = crlInfo.tbsCertList.issuerFingerprint;
2135
- index[fingerprint2] = index[fingerprint2] || {
2136
- crls: [],
2137
- serialNumbers: {}
2138
- };
2139
- index[fingerprint2].crls.push({ crlInfo, filename });
2140
- const serialNumbers = index[fingerprint2].serialNumbers;
2220
+ const fingerprint = crlInfo.tbsCertList.issuerFingerprint;
2221
+ if (!index.has(fingerprint)) {
2222
+ index.set(fingerprint, { crls: [], serialNumbers: {} });
2223
+ }
2224
+ const data = index.get(fingerprint) || { crls: [], serialNumbers: {} };
2225
+ data.crls.push({ crlInfo, filename });
2141
2226
  for (const revokedCertificate of crlInfo.tbsCertList.revokedCertificates) {
2142
2227
  const serialNumber = revokedCertificate.userCertificate;
2143
- if (!serialNumbers[serialNumber]) {
2144
- serialNumbers[serialNumber] = revokedCertificate.revocationDate;
2228
+ if (!data.serialNumbers[serialNumber]) {
2229
+ data.serialNumbers[serialNumber] = revokedCertificate.revocationDate;
2145
2230
  }
2146
2231
  }
2147
- debugLog(chalk6.cyan("CRL"), fingerprint2, "serial numbers = ", Object.keys(serialNumbers));
2232
+ debugLog(chalk6.cyan("CRL"), fingerprint, "serial numbers = ", Object.keys(data.serialNumbers));
2148
2233
  } catch (err) {
2149
2234
  debugLog("CRL filename error =");
2150
2235
  debugLog(err);
2151
2236
  }
2152
- this._pending_crl_to_process -= 1;
2153
- if (this._pending_crl_to_process === 0) {
2154
- if (this._on_crl_process) {
2155
- this._on_crl_process();
2156
- this._on_crl_process = void 0;
2237
+ this.#pendingCrlToProcess -= 1;
2238
+ if (this.#pendingCrlToProcess === 0) {
2239
+ if (this.#onCrlProcess) {
2240
+ this.#onCrlProcess();
2241
+ this.#onCrlProcess = void 0;
2157
2242
  }
2158
2243
  } else {
2159
- this._process_next_crl();
2244
+ this.#processNextCrl();
2160
2245
  }
2161
2246
  }
2162
- async _readCertificates() {
2163
- if (this._readCertificatesCalled) {
2247
+ async #readCertificates() {
2248
+ if (this.#readCertificatesCalled) {
2164
2249
  return;
2165
2250
  }
2166
- this._readCertificatesCalled = true;
2167
- const options = {
2168
- usePolling: true,
2169
- interval: Math.min(10 * 60 * 1e3, Math.max(100, this.folderPoolingInterval)),
2170
- persistent: false,
2171
- awaitWriteFinish: {
2172
- stabilityThreshold: 2e3,
2173
- pollInterval: 600
2174
- }
2251
+ this.#readCertificatesCalled = true;
2252
+ const usePolling = process.env.OPCUA_PKI_USE_POLLING === "true";
2253
+ const chokidarOptions = {
2254
+ usePolling,
2255
+ ...usePolling ? { interval: Math.min(10 * 60 * 1e3, Math.max(100, this.folderPoolingInterval)) } : {},
2256
+ persistent: false
2175
2257
  };
2176
- async function _walkCRLFiles(folder, index) {
2177
- await new Promise((resolve, _reject) => {
2178
- const w = chokidar.watch(folder, options);
2179
- w.on("unlink", (filename, stat) => {
2180
- filename;
2181
- stat;
2182
- });
2183
- w.on("add", (filename, stat) => {
2184
- stat;
2185
- this._on_crl_file_added(index, filename);
2186
- });
2187
- w.on("change", (path9, stat) => {
2188
- debugLog("change in folder ", folder, path9, stat);
2189
- });
2190
- this._watchers.push(w);
2191
- w.on("ready", () => {
2192
- resolve();
2193
- });
2258
+ const createUnreffedWatcher = (folder) => {
2259
+ const capturedHandles = [];
2260
+ const origWatch = fs9.watch;
2261
+ fs9.watch = ((...args) => {
2262
+ const handle = origWatch.apply(fs9, args);
2263
+ capturedHandles.push(handle);
2264
+ return handle;
2194
2265
  });
2195
- }
2196
- async function _walkAllFiles(folder, index) {
2197
- const w = chokidar.watch(folder, options);
2198
- w.on("unlink", (filename, stat) => {
2199
- stat;
2200
- debugLog(chalk6.cyan(`unlink in folder ${folder}`), filename);
2201
- const h = this._filenameToHash[filename];
2202
- if (h && index[h]) {
2203
- delete index[h];
2266
+ const w = chokidar.watch(folder, chokidarOptions);
2267
+ const unreffAll = () => {
2268
+ fs9.watch = origWatch;
2269
+ for (const h of capturedHandles) {
2270
+ h.unref();
2204
2271
  }
2205
- });
2206
- w.on("add", (filename, stat) => {
2207
- stat;
2208
- debugLog(chalk6.cyan(`add in folder ${folder}`), filename);
2209
- try {
2210
- const certificate = readCertificate(filename);
2211
- const info = exploreCertificate(certificate);
2212
- const fingerprint2 = makeFingerprint(certificate);
2213
- index[fingerprint2] = {
2214
- certificate,
2215
- filename
2216
- };
2217
- this._filenameToHash[filename] = fingerprint2;
2218
- debugLog(
2219
- chalk6.magenta("CERT"),
2220
- info.tbsCertificate.subjectFingerPrint,
2221
- info.tbsCertificate.serialNumber,
2222
- info.tbsCertificate.extensions?.authorityKeyIdentifier?.authorityCertIssuerFingerPrint
2223
- );
2224
- } catch (err) {
2225
- debugLog(`Walk files in folder ${folder} with file ${filename}`);
2226
- debugLog(err);
2272
+ };
2273
+ return { w, capturedHandles, unreffAll };
2274
+ };
2275
+ const promises = [
2276
+ this.#walkAllFiles(this.trustedFolder, this.#thumbs.trusted, createUnreffedWatcher),
2277
+ this.#walkAllFiles(this.issuersCertFolder, this.#thumbs.issuers.certs, createUnreffedWatcher),
2278
+ this.#walkAllFiles(this.rejectedFolder, this.#thumbs.rejected, createUnreffedWatcher),
2279
+ this.#walkCRLFiles(this.crlFolder, this.#thumbs.crl, createUnreffedWatcher),
2280
+ this.#walkCRLFiles(this.issuersCrlFolder, this.#thumbs.issuersCrl, createUnreffedWatcher)
2281
+ ];
2282
+ await Promise.all(promises);
2283
+ await this.#waitAndCheckCRLProcessingStatus();
2284
+ }
2285
+ async #walkCRLFiles(folder, index, createUnreffedWatcher) {
2286
+ await new Promise((resolve, _reject) => {
2287
+ const { w, unreffAll } = createUnreffedWatcher(folder);
2288
+ w.on("unlink", (filename) => {
2289
+ for (const [key, data] of index.entries()) {
2290
+ data.crls = data.crls.filter((c) => c.filename !== filename);
2291
+ if (data.crls.length === 0) {
2292
+ index.delete(key);
2293
+ }
2227
2294
  }
2228
2295
  });
2229
- w.on("change", (path9, stat) => {
2230
- stat;
2231
- debugLog("change in folder ", folder, path9);
2296
+ w.on("add", (filename) => {
2297
+ this.#onCrlFileAdded(index, filename);
2232
2298
  });
2233
- this._watchers.push(w);
2234
- await new Promise((resolve, _reject) => {
2235
- w.on("ready", () => {
2236
- debugLog("ready");
2237
- debugLog(Object.entries(index).map((kv) => kv[0].substring(0, 10)));
2238
- resolve();
2239
- });
2299
+ w.on("change", (changedPath) => {
2300
+ debugLog("change in folder ", folder, changedPath);
2301
+ });
2302
+ this.#watchers.push(w);
2303
+ w.on("ready", () => {
2304
+ unreffAll();
2305
+ resolve();
2240
2306
  });
2241
- }
2242
- const promises = [
2243
- _walkAllFiles.bind(this, this.trustedFolder, this._thumbs.trusted)(),
2244
- _walkAllFiles.bind(this, this.issuersCertFolder, this._thumbs.issuers.certs)(),
2245
- _walkAllFiles.bind(this, this.rejectedFolder, this._thumbs.rejected)(),
2246
- _walkCRLFiles.bind(this, this.crlFolder, this._thumbs.crl)(),
2247
- _walkCRLFiles.bind(this, this.issuersCrlFolder, this._thumbs.issuersCrl)()
2248
- ];
2249
- await Promise.all(promises);
2250
- await this.waitAndCheckCRLProcessingStatus();
2251
- }
2252
- // make sure that all crls have been processed.
2253
- async waitAndCheckCRLProcessingStatus() {
2254
- return new Promise((resolve, reject) => {
2255
- if (this._pending_crl_to_process === 0) {
2256
- setImmediate(resolve);
2257
- return;
2258
- }
2259
- if (this._on_crl_process) {
2260
- return reject(new Error("Internal Error"));
2261
- }
2262
- this._on_crl_process = resolve;
2263
- });
2264
- }
2265
- };
2266
-
2267
- // packages/node-opcua-pki/lib/ca/crypto_create_CA.ts
2268
- import commandLineArgs from "command-line-args";
2269
- import commandLineUsage from "command-line-usage";
2270
- var epilog = "Copyright (c) sterfive - node-opcua - 2017-2026";
2271
- function get_offset_date(date, nbDays) {
2272
- const d = new Date(date.getTime());
2273
- d.setDate(d.getDate() + nbDays);
2274
- return d;
2275
- }
2276
- var today = /* @__PURE__ */ new Date();
2277
- var yesterday = get_offset_date(today, -1);
2278
- var two_years_ago = get_offset_date(today, -2 * 365);
2279
- var next_year = get_offset_date(today, 365);
2280
- var gLocalConfig = {};
2281
- var g_certificateAuthority;
2282
- async function construct_CertificateAuthority2(subject) {
2283
- assert11(typeof gLocalConfig.CAFolder === "string", "expecting a CAFolder in config");
2284
- assert11(typeof gLocalConfig.keySize === "number", "expecting a keySize in config");
2285
- if (!g_certificateAuthority) {
2286
- g_certificateAuthority = new CertificateAuthority({
2287
- keySize: gLocalConfig.keySize,
2288
- location: gLocalConfig.CAFolder,
2289
- subject
2290
- });
2291
- await g_certificateAuthority.initialize();
2292
- }
2293
- }
2294
- var certificateManager;
2295
- async function construct_CertificateManager() {
2296
- assert11(typeof gLocalConfig.PKIFolder === "string", "expecting a PKIFolder in config");
2297
- if (!certificateManager) {
2298
- certificateManager = new CertificateManager({
2299
- keySize: gLocalConfig.keySize,
2300
- location: gLocalConfig.PKIFolder
2301
2307
  });
2302
- await certificateManager.initialize();
2303
2308
  }
2304
- }
2305
- function default_template_content() {
2306
- if (process.pkg?.entrypoint) {
2307
- const a = fs10.readFileSync(path8.join(__dirname, "../../bin/pki_config.example.js"), "utf8");
2308
- return a;
2309
- }
2310
- function find_default_config_template() {
2311
- const rootFolder = find_module_root_folder();
2312
- const configName = "pki_config.example.js";
2313
- let default_config_template2 = path8.join(rootFolder, "bin", configName);
2314
- if (!fs10.existsSync(default_config_template2)) {
2315
- default_config_template2 = path8.join(__dirname, "..", configName);
2316
- if (!fs10.existsSync(default_config_template2)) {
2317
- default_config_template2 = path8.join(__dirname, `../bin/${configName}`);
2309
+ async #walkAllFiles(folder, index, createUnreffedWatcher) {
2310
+ const { w, unreffAll } = createUnreffedWatcher(folder);
2311
+ w.on("unlink", (filename) => {
2312
+ debugLog(chalk6.cyan(`unlink in folder ${folder}`), filename);
2313
+ const h = this.#filenameToHash.get(filename);
2314
+ if (h && index.has(h)) {
2315
+ index.delete(h);
2318
2316
  }
2319
- }
2320
- return default_config_template2;
2321
- }
2322
- const default_config_template = find_default_config_template();
2323
- assert11(fs10.existsSync(default_config_template));
2324
- const default_config_template_content = fs10.readFileSync(default_config_template, "utf8");
2325
- return default_config_template_content;
2326
- }
2327
- function find_module_root_folder() {
2328
- let rootFolder = path8.join(__dirname);
2329
- for (let i = 0; i < 4; i++) {
2330
- if (fs10.existsSync(path8.join(rootFolder, "package.json"))) {
2331
- return rootFolder;
2332
- }
2333
- rootFolder = path8.join(rootFolder, "..");
2334
- }
2335
- assert11(fs10.existsSync(path8.join(rootFolder, "package.json")), "root folder must have a package.json file");
2336
- return rootFolder;
2337
- }
2338
- async function readConfiguration(argv) {
2339
- if (argv.silent) {
2340
- g_config.silent = true;
2341
- } else {
2342
- g_config.silent = false;
2343
- }
2344
- const fqdn2 = await extractFullyQualifiedDomainName();
2345
- const hostname = os4.hostname();
2346
- let certificateDir;
2347
- function performSubstitution(str) {
2348
- str = str.replace("{CWD}", process.cwd());
2349
- if (certificateDir) {
2350
- str = str.replace("{root}", certificateDir);
2351
- }
2352
- if (gLocalConfig?.PKIFolder) {
2353
- str = str.replace("{PKIFolder}", gLocalConfig.PKIFolder);
2354
- }
2355
- str = str.replace("{hostname}", hostname);
2356
- str = str.replace("%FQDN%", fqdn2);
2357
- return str;
2358
- }
2359
- function prepare(file) {
2360
- const tmp = path8.resolve(performSubstitution(file));
2361
- return makePath(tmp);
2362
- }
2363
- certificateDir = argv.root;
2364
- assert11(typeof certificateDir === "string");
2365
- certificateDir = prepare(certificateDir);
2366
- mkdirRecursiveSync(certificateDir);
2367
- assert11(fs10.existsSync(certificateDir));
2368
- const default_config = path8.join(certificateDir, "config.js");
2369
- if (!fs10.existsSync(default_config)) {
2370
- debugLog(chalk7.yellow(" Creating default g_config file "), chalk7.cyan(default_config));
2371
- const default_config_template_content = default_template_content();
2372
- fs10.writeFileSync(default_config, default_config_template_content);
2373
- } else {
2374
- debugLog(chalk7.yellow(" using g_config file "), chalk7.cyan(default_config));
2375
- }
2376
- if (!fs10.existsSync(default_config)) {
2377
- debugLog(chalk7.redBright(" cannot find config file ", default_config));
2378
- }
2379
- const defaultRandomFile = path8.join(path8.dirname(default_config), "random.rnd");
2380
- setEnv("RANDFILE", defaultRandomFile);
2381
- const _require = createRequire(__filename);
2382
- gLocalConfig = _require(default_config);
2383
- gLocalConfig.subject = new Subject5(gLocalConfig.subject || "");
2384
- if (argv.subject) {
2385
- gLocalConfig.subject = new Subject5(argv.subject);
2386
- }
2387
- if (!gLocalConfig.subject.commonName) {
2388
- throw new Error("subject must have a Common Name");
2389
- }
2390
- gLocalConfig.certificateDir = certificateDir;
2391
- let CAFolder = argv.CAFolder || path8.join(certificateDir, "CA");
2392
- CAFolder = prepare(CAFolder);
2393
- gLocalConfig.CAFolder = CAFolder;
2394
- gLocalConfig.PKIFolder = path8.join(gLocalConfig.certificateDir, "PKI");
2395
- if (argv.PKIFolder) {
2396
- gLocalConfig.PKIFolder = prepare(argv.PKIFolder);
2397
- }
2398
- gLocalConfig.PKIFolder = prepare(gLocalConfig.PKIFolder);
2399
- if (argv.privateKey) {
2400
- gLocalConfig.privateKey = prepare(argv.privateKey);
2401
- }
2402
- if (argv.applicationUri) {
2403
- gLocalConfig.applicationUri = performSubstitution(argv.applicationUri);
2404
- }
2405
- if (argv.output) {
2406
- gLocalConfig.outputFile = argv.output;
2407
- }
2408
- gLocalConfig.altNames = [];
2409
- if (argv.altNames) {
2410
- gLocalConfig.altNames = argv.altNames.split(";");
2411
- }
2412
- gLocalConfig.dns = [getFullyQualifiedDomainName()];
2413
- if (argv.dns) {
2414
- gLocalConfig.dns = argv.dns.split(",").map(performSubstitution);
2415
- }
2416
- gLocalConfig.ip = [];
2417
- if (argv.ip) {
2418
- gLocalConfig.ip = argv.ip.split(",");
2419
- }
2420
- if (argv.keySize) {
2421
- const v = argv.keySize;
2422
- if (v !== 1024 && v !== 2048 && v !== 3072 && v !== 4096) {
2423
- throw new Error(`invalid keysize specified ${v} should be 1024,2048,3072 or 4096`);
2424
- }
2425
- gLocalConfig.keySize = argv.keySize;
2426
- }
2427
- if (argv.validity) {
2428
- gLocalConfig.validity = argv.validity;
2429
- }
2430
- }
2431
- async function createDefaultCertificate(base_name, prefix, key_length, applicationUri, dev) {
2432
- assert11(key_length === 1024 || key_length === 2048 || key_length === 3072 || key_length === 4096);
2433
- const private_key_file = makePath(base_name, `${prefix}key_${key_length}.pem`);
2434
- const public_key_file = makePath(base_name, `${prefix}public_key_${key_length}.pub`);
2435
- const certificate_file = makePath(base_name, `${prefix}cert_${key_length}.pem`);
2436
- const certificate_file_outofdate = makePath(base_name, `${prefix}cert_${key_length}_outofdate.pem`);
2437
- const certificate_file_not_active_yet = makePath(base_name, `${prefix}cert_${key_length}_not_active_yet.pem`);
2438
- const certificate_revoked = makePath(base_name, `${prefix}cert_${key_length}_revoked.pem`);
2439
- const self_signed_certificate_file = makePath(base_name, `${prefix}selfsigned_cert_${key_length}.pem`);
2440
- const fqdn2 = getFullyQualifiedDomainName();
2441
- const hostname = os4.hostname();
2442
- const dns2 = [
2443
- // for conformance reason, localhost shall not be present in the DNS field of COP
2444
- // ***FORBIDEN** "localhost",
2445
- getFullyQualifiedDomainName()
2446
- ];
2447
- if (hostname !== fqdn2) {
2448
- dns2.push(hostname);
2449
- }
2450
- const ip = [];
2451
- async function createCertificateIfNotExist(certificate, private_key, applicationUri2, startDate, validity) {
2452
- if (fs10.existsSync(certificate)) {
2453
- warningLog(chalk7.yellow(" certificate"), chalk7.cyan(certificate), chalk7.yellow(" already exists => skipping"));
2454
- return "";
2455
- } else {
2456
- return await createCertificate(certificate, private_key, applicationUri2, startDate, validity);
2457
- }
2458
- }
2459
- async function createCertificate(certificate, privateKey, applicationUri2, startDate, validity) {
2460
- const certificateSigningRequestFile = `${certificate}.csr`;
2461
- const configFile = makePath(base_name, "../certificates/PKI/own/openssl.cnf");
2462
- const dns3 = [os4.hostname()];
2463
- const ip2 = ["127.0.0.1"];
2464
- const params = {
2465
- applicationUri: applicationUri2,
2466
- privateKey,
2467
- rootDir: ".",
2468
- configFile,
2469
- dns: dns3,
2470
- ip: ip2,
2471
- purpose: CertificatePurpose2.ForApplication
2472
- };
2473
- await createCertificateSigningRequestWithOpenSSL(certificateSigningRequestFile, params);
2474
- return await g_certificateAuthority.signCertificateRequest(certificate, certificateSigningRequestFile, {
2475
- applicationUri: applicationUri2,
2476
- dns: dns3,
2477
- ip: ip2,
2478
- startDate,
2479
- validity
2480
2317
  });
2481
- }
2482
- async function createSelfSignedCertificate2(certificate, private_key, applicationUri2, startDate, validity) {
2483
- await g_certificateAuthority.createSelfSignedCertificate(certificate, private_key, {
2484
- applicationUri: applicationUri2,
2485
- dns: dns2,
2486
- ip,
2487
- startDate,
2488
- validity
2318
+ w.on("add", (filename) => {
2319
+ debugLog(chalk6.cyan(`add in folder ${folder}`), filename);
2320
+ try {
2321
+ const certificate = readCertificate(filename);
2322
+ const info = exploreCertificate(certificate);
2323
+ const fingerprint = makeFingerprint(certificate);
2324
+ index.set(fingerprint, { certificate, filename, info });
2325
+ this.#filenameToHash.set(filename, fingerprint);
2326
+ debugLog(
2327
+ chalk6.magenta("CERT"),
2328
+ info.tbsCertificate.subjectFingerPrint,
2329
+ info.tbsCertificate.serialNumber,
2330
+ info.tbsCertificate.extensions?.authorityKeyIdentifier?.authorityCertIssuerFingerPrint
2331
+ );
2332
+ } catch (err) {
2333
+ debugLog(`Walk files in folder ${folder} with file ${filename}`);
2334
+ debugLog(err);
2335
+ }
2489
2336
  });
2490
- }
2491
- async function revoke_certificate(certificate) {
2492
- await g_certificateAuthority.revokeCertificate(certificate, {});
2493
- }
2494
- async function createPrivateKeyIfNotExist(privateKey, keyLength) {
2495
- if (fs10.existsSync(privateKey)) {
2496
- warningLog(chalk7.yellow(" privateKey"), chalk7.cyan(privateKey), chalk7.yellow(" already exists => skipping"));
2497
- return;
2498
- } else {
2499
- await generatePrivateKeyFile3(privateKey, keyLength);
2500
- }
2501
- }
2502
- displaySubtitle(` create private key :${private_key_file}`);
2503
- await createPrivateKeyIfNotExist(private_key_file, key_length);
2504
- displaySubtitle(` extract public key ${public_key_file} from private key `);
2505
- await getPublicKeyFromPrivateKey(private_key_file, public_key_file);
2506
- displaySubtitle(` create Certificate ${certificate_file}`);
2507
- await createCertificateIfNotExist(certificate_file, private_key_file, applicationUri, yesterday, 365);
2508
- displaySubtitle(` create self signed Certificate ${self_signed_certificate_file}`);
2509
- if (fs10.existsSync(self_signed_certificate_file)) {
2510
- return;
2511
- }
2512
- await createSelfSignedCertificate2(self_signed_certificate_file, private_key_file, applicationUri, yesterday, 365);
2513
- if (dev) {
2514
- await createCertificateIfNotExist(certificate_file_outofdate, private_key_file, applicationUri, two_years_ago, 365);
2515
- await createCertificateIfNotExist(certificate_file_not_active_yet, private_key_file, applicationUri, next_year, 365);
2516
- if (!fs10.existsSync(certificate_revoked)) {
2517
- const certificate = await createCertificateIfNotExist(
2518
- certificate_revoked,
2519
- private_key_file,
2520
- `${applicationUri}Revoked`,
2521
- // make sure we used a uniq URI here
2522
- yesterday,
2523
- 365
2524
- );
2525
- warningLog(" certificate to revoke => ", certificate);
2526
- revoke_certificate(certificate_revoked);
2527
- }
2528
- }
2529
- }
2530
- async function wrap(func) {
2531
- try {
2532
- await func();
2533
- } catch (err) {
2534
- console.log(err.message);
2535
- }
2536
- }
2537
- async function create_default_certificates(dev) {
2538
- assert11(gLocalConfig);
2539
- const base_name = gLocalConfig.certificateDir || "";
2540
- assert11(fs10.existsSync(base_name));
2541
- let clientURN;
2542
- let serverURN;
2543
- let discoveryServerURN;
2544
- wrap(async () => {
2545
- await extractFullyQualifiedDomainName();
2546
- const hostname = os4.hostname();
2547
- const fqdn2 = getFullyQualifiedDomainName();
2548
- warningLog(chalk7.yellow(" hostname = "), chalk7.cyan(hostname));
2549
- warningLog(chalk7.yellow(" fqdn = "), chalk7.cyan(fqdn2));
2550
- clientURN = makeApplicationUrn(hostname, "NodeOPCUA-Client");
2551
- serverURN = makeApplicationUrn(hostname, "NodeOPCUA-Server");
2552
- discoveryServerURN = makeApplicationUrn(hostname, "NodeOPCUA-DiscoveryServer");
2553
- displayTitle("Create Application Certificate for Server & its private key");
2554
- await createDefaultCertificate(base_name, "client_", 1024, clientURN, dev);
2555
- await createDefaultCertificate(base_name, "client_", 2048, clientURN, dev);
2556
- await createDefaultCertificate(base_name, "client_", 3072, clientURN, dev);
2557
- await createDefaultCertificate(base_name, "client_", 4096, clientURN, dev);
2558
- displayTitle("Create Application Certificate for Client & its private key");
2559
- await createDefaultCertificate(base_name, "server_", 1024, serverURN, dev);
2560
- await createDefaultCertificate(base_name, "server_", 2048, serverURN, dev);
2561
- await createDefaultCertificate(base_name, "server_", 3072, serverURN, dev);
2562
- await createDefaultCertificate(base_name, "server_", 4096, serverURN, dev);
2563
- displayTitle("Create Application Certificate for DiscoveryServer & its private key");
2564
- await createDefaultCertificate(base_name, "discoveryServer_", 1024, discoveryServerURN, dev);
2565
- await createDefaultCertificate(base_name, "discoveryServer_", 2048, discoveryServerURN, dev);
2566
- await createDefaultCertificate(base_name, "discoveryServer_", 3072, discoveryServerURN, dev);
2567
- await createDefaultCertificate(base_name, "discoveryServer_", 4096, discoveryServerURN, dev);
2568
- });
2569
- }
2570
- async function createDefaultCertificates(dev) {
2571
- await construct_CertificateAuthority2("");
2572
- await construct_CertificateManager();
2573
- await create_default_certificates(dev);
2574
- }
2575
- var commonOptions = [
2576
- {
2577
- name: "root",
2578
- alias: "r",
2579
- type: String,
2580
- defaultValue: "{CWD}/certificates",
2581
- description: "the location of the Certificate folder"
2582
- },
2583
- {
2584
- name: "CAFolder",
2585
- alias: "c",
2586
- type: String,
2587
- defaultValue: "{root}/CA",
2588
- description: "the location of the Certificate Authority folder"
2589
- },
2590
- { name: "PKIFolder", type: String, defaultValue: "{root}/PKI", description: "the location of the Public Key Infrastructure" },
2591
- { name: "silent", type: Boolean, defaultValue: false, description: "minimize output" },
2592
- {
2593
- name: "privateKey",
2594
- alias: "p",
2595
- type: String,
2596
- defaultValue: "{PKIFolder}/own/private_key.pem",
2597
- description: "the private key to use to generate certificate"
2598
- },
2599
- {
2600
- name: "keySize",
2601
- alias: "k",
2602
- type: Number,
2603
- defaultValue: 2048,
2604
- description: "the private key size in bits (1024|2048|3072|4096)"
2605
- },
2606
- { name: "help", alias: "h", type: Boolean, description: "display this help" }
2607
- ];
2608
- function getOptions(names) {
2609
- return commonOptions.filter((o) => names.includes(o.name) || o.name === "help" || o.name === "silent");
2610
- }
2611
- function showHelp(command, description, options, usage) {
2612
- const sections = [
2613
- {
2614
- header: `Command: ${command}`,
2615
- content: description
2616
- },
2617
- {
2618
- header: "Usage",
2619
- content: usage || `$0 ${command} [options]`
2620
- },
2621
- {
2622
- header: "Options",
2623
- optionList: options
2624
- }
2625
- ];
2626
- console.log(commandLineUsage(sections));
2627
- }
2628
- async function main(argumentsList) {
2629
- const mainDefinitions = [{ name: "command", defaultOption: true }];
2630
- let mainOptions;
2631
- try {
2632
- mainOptions = commandLineArgs(mainDefinitions, { argv: argumentsList, stopAtFirstUnknown: true });
2633
- } catch (err) {
2634
- console.log(err.message);
2635
- return;
2636
- }
2637
- const argv = mainOptions._unknown || [];
2638
- const command = mainOptions.command;
2639
- if (!command || command === "help") {
2640
- console.log(
2641
- commandLineUsage([
2642
- {
2643
- header: "node-opcua-pki",
2644
- content: `PKI management for node-opcua
2645
-
2646
- ${epilog}`
2647
- },
2648
- {
2649
- header: "Commands",
2650
- content: [
2651
- { name: "demo", summary: "create default certificate for node-opcua demos" },
2652
- { name: "createCA", summary: "create a Certificate Authority" },
2653
- { name: "createPKI", summary: "create a Public Key Infrastructure" },
2654
- { name: "certificate", summary: "create a new certificate" },
2655
- { name: "revoke <certificateFile>", summary: "revoke a existing certificate" },
2656
- { name: "csr", summary: "create a certificate signing request" },
2657
- { name: "sign", summary: "validate a certificate signing request and generate a certificate" },
2658
- { name: "dump <certificateFile>", summary: "display a certificate" },
2659
- { name: "toder <pemCertificate>", summary: "convert a certificate to a DER format with finger print" },
2660
- { name: "fingerprint <certificateFile>", summary: "print the certificate fingerprint" },
2661
- { name: "version", summary: "display the version number" }
2662
- ]
2663
- }
2664
- ])
2665
- );
2666
- return;
2667
- }
2668
- if (command === "version") {
2669
- const rootFolder = find_module_root_folder();
2670
- const pkg = JSON.parse(fs10.readFileSync(path8.join(rootFolder, "package.json"), "utf-8"));
2671
- console.log(pkg.version);
2672
- return;
2673
- }
2674
- if (command === "demo") {
2675
- const optionsDef = [
2676
- ...getOptions(["root", "silent"]),
2677
- { name: "dev", type: Boolean, description: "create all sort of fancy certificates for dev testing purposes" },
2678
- { name: "clean", type: Boolean, description: "Purge existing directory [use with care!]" }
2679
- ];
2680
- const local_argv = commandLineArgs(optionsDef, { argv });
2681
- if (local_argv.help)
2682
- return showHelp(
2683
- "demo",
2684
- "create default certificate for node-opcua demos",
2685
- optionsDef,
2686
- "$0 demo [--dev] [--silent] [--clean]"
2687
- );
2688
- await wrap(async () => {
2689
- await ensure_openssl_installed();
2690
- displayChapter("Create Demo certificates");
2691
- displayTitle("reading configuration");
2692
- await readConfiguration(local_argv);
2693
- if (local_argv.clean) {
2694
- displayTitle("Cleaning old certificates");
2695
- assert11(gLocalConfig);
2696
- const certificateDir = gLocalConfig.certificateDir || "";
2697
- const files = await fs10.promises.readdir(certificateDir);
2698
- for (const file of files) {
2699
- if (file.includes(".pem") || file.includes(".pub")) {
2700
- await fs10.promises.unlink(path8.join(certificateDir, file));
2701
- }
2337
+ w.on("change", (changedPath) => {
2338
+ debugLog(chalk6.cyan(`change in folder ${folder}`), changedPath);
2339
+ try {
2340
+ const certificate = readCertificate(changedPath);
2341
+ const newFingerprint = makeFingerprint(certificate);
2342
+ const oldHash = this.#filenameToHash.get(changedPath);
2343
+ if (oldHash && oldHash !== newFingerprint) {
2344
+ index.delete(oldHash);
2702
2345
  }
2703
- mkdirRecursiveSync(certificateDir);
2346
+ index.set(newFingerprint, { certificate, filename: changedPath, info: exploreCertificate(certificate) });
2347
+ this.#filenameToHash.set(changedPath, newFingerprint);
2348
+ } catch (err) {
2349
+ debugLog(`change event: failed to re-read ${changedPath}`, err);
2704
2350
  }
2705
- displayTitle("create certificates");
2706
- await createDefaultCertificates(local_argv.dev);
2707
- displayChapter("Demo certificates CREATED");
2708
2351
  });
2709
- return;
2710
- }
2711
- if (command === "createCA") {
2712
- const optionsDef = [
2713
- ...getOptions(["root", "CAFolder", "keySize", "silent"]),
2714
- { name: "subject", type: String, defaultValue: defaultSubject, description: "the CA certificate subject" }
2715
- ];
2716
- const local_argv = commandLineArgs(optionsDef, { argv });
2717
- if (local_argv.help) return showHelp("createCA", "create a Certificate Authority", optionsDef);
2718
- await wrap(async () => {
2719
- await ensure_openssl_installed();
2720
- await readConfiguration(local_argv);
2721
- await construct_CertificateAuthority2(local_argv.subject);
2722
- });
2723
- return;
2724
- }
2725
- if (command === "createPKI") {
2726
- const optionsDef = getOptions(["root", "PKIFolder", "keySize", "silent"]);
2727
- const local_argv = commandLineArgs(optionsDef, { argv });
2728
- if (local_argv.help) return showHelp("createPKI", "create a Public Key Infrastructure", optionsDef);
2729
- await wrap(async () => {
2730
- await readConfiguration(local_argv);
2731
- await construct_CertificateManager();
2732
- });
2733
- return;
2734
- }
2735
- if (command === "certificate") {
2736
- const optionsDef = [
2737
- ...getOptions(["root", "CAFolder", "PKIFolder", "privateKey", "silent"]),
2738
- {
2739
- name: "applicationUri",
2740
- alias: "a",
2741
- type: String,
2742
- defaultValue: "urn:{hostname}:Node-OPCUA-Server",
2743
- description: "the application URI"
2744
- },
2745
- {
2746
- name: "output",
2747
- alias: "o",
2748
- type: String,
2749
- defaultValue: "my_certificate.pem",
2750
- description: "the name of the generated certificate =>"
2751
- },
2752
- {
2753
- name: "selfSigned",
2754
- alias: "s",
2755
- type: Boolean,
2756
- defaultValue: false,
2757
- description: "if true, certificate will be self-signed"
2758
- },
2759
- { name: "validity", alias: "v", type: Number, description: "the certificate validity in days" },
2760
- {
2761
- name: "dns",
2762
- type: String,
2763
- defaultValue: "{hostname}",
2764
- description: "the list of valid domain name (comma separated)"
2765
- },
2766
- { name: "ip", type: String, defaultValue: "", description: "the list of valid IPs (comma separated)" },
2767
- {
2768
- name: "subject",
2769
- type: String,
2770
- defaultValue: "",
2771
- description: "the certificate subject ( for instance C=FR/ST=Centre/L=Orleans/O=SomeOrganization/CN=Hello )"
2772
- }
2773
- ];
2774
- const local_argv = commandLineArgs(optionsDef, { argv });
2775
- if (local_argv.help || !local_argv.applicationUri || !local_argv.output)
2776
- return showHelp("certificate", "create a new certificate", optionsDef);
2777
- async function command_certificate(local_argv2) {
2778
- const selfSigned = !!local_argv2.selfSigned;
2779
- if (!selfSigned) {
2780
- await command_full_certificate(local_argv2);
2781
- } else {
2782
- await command_selfsigned_certificate(local_argv2);
2783
- }
2784
- }
2785
- async function command_selfsigned_certificate(local_argv2) {
2786
- const _fqdn = await extractFullyQualifiedDomainName();
2787
- await readConfiguration(local_argv2);
2788
- await construct_CertificateManager();
2789
- displaySubtitle(` create self signed Certificate ${gLocalConfig.outputFile}`);
2790
- let subject = local_argv2.subject && local_argv2.subject.length > 1 ? new Subject5(local_argv2.subject) : gLocalConfig.subject || "";
2791
- subject = JSON.parse(JSON.stringify(subject));
2792
- const params = {
2793
- applicationUri: gLocalConfig.applicationUri || "",
2794
- dns: gLocalConfig.dns || [],
2795
- ip: gLocalConfig.ip || [],
2796
- outputFile: gLocalConfig.outputFile || "self_signed_certificate.pem",
2797
- startDate: gLocalConfig.startDate || /* @__PURE__ */ new Date(),
2798
- subject,
2799
- validity: gLocalConfig.validity || 365
2800
- };
2801
- await certificateManager.createSelfSignedCertificate(params);
2802
- }
2803
- async function command_full_certificate(local_argv2) {
2804
- await readConfiguration(local_argv2);
2805
- await construct_CertificateManager();
2806
- await construct_CertificateAuthority2("");
2807
- assert11(fs10.existsSync(gLocalConfig.CAFolder || ""), " CA folder must exist");
2808
- gLocalConfig.privateKey = void 0;
2809
- gLocalConfig.subject = local_argv2.subject && local_argv2.subject.length > 1 ? local_argv2.subject : gLocalConfig.subject;
2810
- const csr_file = await certificateManager.createCertificateRequest(
2811
- gLocalConfig
2812
- );
2813
- if (!csr_file) {
2814
- return;
2815
- }
2816
- warningLog(" csr_file = ", csr_file);
2817
- const certificate = csr_file.replace(".csr", ".pem");
2818
- if (fs10.existsSync(certificate)) {
2819
- throw new Error(` File ${certificate} already exist`);
2820
- }
2821
- await g_certificateAuthority.signCertificateRequest(
2822
- certificate,
2823
- csr_file,
2824
- gLocalConfig
2825
- );
2826
- assert11(typeof gLocalConfig.outputFile === "string");
2827
- fs10.writeFileSync(gLocalConfig.outputFile || "", fs10.readFileSync(certificate, "ascii"));
2828
- }
2829
- await wrap(async () => await command_certificate(local_argv));
2830
- return;
2831
- }
2832
- if (command === "revoke") {
2833
- const optionsDef = [{ name: "certificateFile", type: String, defaultOption: true }, ...getOptions(["root", "CAFolder"])];
2834
- const local_argv = commandLineArgs(optionsDef, { argv });
2835
- if (local_argv.help || !local_argv.certificateFile)
2836
- return showHelp(
2837
- "revoke <certificateFile>",
2838
- "revoke a existing certificate",
2839
- optionsDef,
2840
- "$0 revoke my_certificate.pem"
2841
- );
2842
- async function revoke_certificate(certificate) {
2843
- await g_certificateAuthority.revokeCertificate(certificate, {});
2844
- }
2845
- await wrap(async () => {
2846
- const certificate = path8.resolve(local_argv.certificateFile);
2847
- warningLog(chalk7.yellow(" Certificate to revoke : "), chalk7.cyan(certificate));
2848
- if (!fs10.existsSync(certificate)) {
2849
- throw new Error(`cannot find certificate to revoke ${certificate}`);
2850
- }
2851
- await readConfiguration(local_argv);
2852
- await construct_CertificateAuthority2("");
2853
- await revoke_certificate(certificate);
2854
- warningLog("done ... ");
2855
- warningLog(" crl = ", g_certificateAuthority.revocationList);
2856
- warningLog("\nyou should now publish the new Certificate Revocation List");
2352
+ this.#watchers.push(w);
2353
+ await new Promise((resolve, _reject) => {
2354
+ w.on("ready", () => {
2355
+ unreffAll();
2356
+ debugLog("ready");
2357
+ debugLog([...index.keys()].map((k) => k.substring(0, 10)));
2358
+ resolve();
2359
+ });
2857
2360
  });
2858
- return;
2859
2361
  }
2860
- if (command === "csr") {
2861
- const optionsDef = [
2862
- ...getOptions(["root", "PKIFolder", "privateKey", "silent"]),
2863
- {
2864
- name: "applicationUri",
2865
- alias: "a",
2866
- type: String,
2867
- defaultValue: "urn:{hostname}:Node-OPCUA-Server",
2868
- description: "the application URI"
2869
- },
2870
- {
2871
- name: "output",
2872
- alias: "o",
2873
- type: String,
2874
- defaultValue: "my_certificate_signing_request.csr",
2875
- description: "the name of the generated signing_request"
2876
- },
2877
- {
2878
- name: "dns",
2879
- type: String,
2880
- defaultValue: "{hostname}",
2881
- description: "the list of valid domain name (comma separated)"
2882
- },
2883
- { name: "ip", type: String, defaultValue: "", description: "the list of valid IPs (comma separated)" },
2884
- {
2885
- name: "subject",
2886
- type: String,
2887
- defaultValue: "/CN=Certificate",
2888
- description: "the certificate subject ( for instance /C=FR/ST=Centre/L=Orleans/O=SomeOrganization/CN=Hello )"
2889
- }
2890
- ];
2891
- const local_argv = commandLineArgs(optionsDef, { argv });
2892
- if (local_argv.help) return showHelp("csr", "create a certificate signing request", optionsDef);
2893
- await wrap(async () => {
2894
- await readConfiguration(local_argv);
2895
- if (!fs10.existsSync(gLocalConfig.PKIFolder || "")) {
2896
- warningLog("PKI folder must exist");
2897
- }
2898
- await construct_CertificateManager();
2899
- if (!gLocalConfig.outputFile || fs10.existsSync(gLocalConfig.outputFile)) {
2900
- throw new Error(` File ${gLocalConfig.outputFile} already exist`);
2901
- }
2902
- gLocalConfig.privateKey = void 0;
2903
- gLocalConfig.subject = local_argv.subject && local_argv.subject.length > 1 ? local_argv.subject : gLocalConfig.subject;
2904
- const internal_csr_file = await certificateManager.createCertificateRequest(
2905
- gLocalConfig
2906
- );
2907
- if (!internal_csr_file) {
2908
- return;
2909
- }
2910
- if (!gLocalConfig.outputFile) {
2911
- warningLog("please specify a output file");
2362
+ // make sure that all crls have been processed.
2363
+ async #waitAndCheckCRLProcessingStatus() {
2364
+ return new Promise((resolve, reject) => {
2365
+ if (this.#pendingCrlToProcess === 0) {
2366
+ setImmediate(resolve);
2912
2367
  return;
2913
2368
  }
2914
- const csr = await fs10.promises.readFile(internal_csr_file, "utf-8");
2915
- fs10.writeFileSync(gLocalConfig.outputFile || "", csr, "utf-8");
2916
- warningLog("Subject = ", gLocalConfig.subject);
2917
- warningLog("applicationUri = ", gLocalConfig.applicationUri);
2918
- warningLog("altNames = ", gLocalConfig.altNames);
2919
- warningLog("dns = ", gLocalConfig.dns);
2920
- warningLog("ip = ", gLocalConfig.ip);
2921
- warningLog("CSR file = ", gLocalConfig.outputFile);
2922
- });
2923
- return;
2924
- }
2925
- if (command === "sign") {
2926
- const optionsDef = [
2927
- ...getOptions(["root", "CAFolder", "silent"]),
2928
- { name: "csr", alias: "i", type: String, defaultValue: "my_certificate_signing_request.csr", description: "the csr" },
2929
- {
2930
- name: "output",
2931
- alias: "o",
2932
- type: String,
2933
- defaultValue: "my_certificate.pem",
2934
- description: "the name of the generated certificate"
2935
- },
2936
- { name: "validity", alias: "v", type: Number, defaultValue: 365, description: "the certificate validity in days" }
2937
- ];
2938
- const local_argv = commandLineArgs(optionsDef, { argv });
2939
- if (local_argv.help || !local_argv.csr || !local_argv.output)
2940
- return showHelp("sign", "validate a certificate signing request and generate a certificate", optionsDef);
2941
- await wrap(async () => {
2942
- await readConfiguration(local_argv);
2943
- if (!fs10.existsSync(gLocalConfig.CAFolder || "")) {
2944
- throw new Error(`CA folder must exist:${gLocalConfig.CAFolder}`);
2945
- }
2946
- await construct_CertificateAuthority2("");
2947
- const csr_file = path8.resolve(local_argv.csr || "");
2948
- if (!fs10.existsSync(csr_file)) {
2949
- throw new Error(`Certificate signing request doesn't exist: ${csr_file}`);
2950
- }
2951
- const certificate = path8.resolve(local_argv.output || csr_file.replace(".csr", ".pem"));
2952
- if (fs10.existsSync(certificate)) {
2953
- throw new Error(` File ${certificate} already exist`);
2369
+ if (this.#onCrlProcess) {
2370
+ return reject(new Error("Internal Error"));
2954
2371
  }
2955
- await g_certificateAuthority.signCertificateRequest(
2956
- certificate,
2957
- csr_file,
2958
- gLocalConfig
2959
- );
2960
- assert11(typeof gLocalConfig.outputFile === "string");
2961
- fs10.writeFileSync(gLocalConfig.outputFile || "", fs10.readFileSync(certificate, "ascii"));
2962
- });
2963
- return;
2964
- }
2965
- if (command === "dump") {
2966
- const optionsDef = [
2967
- { name: "certificateFile", type: String, defaultOption: true },
2968
- { name: "help", alias: "h", type: Boolean }
2969
- ];
2970
- const local_argv = commandLineArgs(optionsDef, { argv });
2971
- if (local_argv.help || !local_argv.certificateFile)
2972
- return showHelp("dump <certificateFile>", "display a certificate", optionsDef);
2973
- await wrap(async () => {
2974
- const data = await dumpCertificate(local_argv.certificateFile);
2975
- warningLog(data);
2976
- });
2977
- return;
2978
- }
2979
- if (command === "toder") {
2980
- const optionsDef = [
2981
- { name: "pemCertificate", type: String, defaultOption: true },
2982
- { name: "help", alias: "h", type: Boolean }
2983
- ];
2984
- const local_argv = commandLineArgs(optionsDef, { argv });
2985
- if (local_argv.help || !local_argv.pemCertificate)
2986
- return showHelp("toder <pemCertificate>", "convert a certificate to a DER format with finger print", optionsDef);
2987
- await wrap(async () => {
2988
- await toDer(local_argv.pemCertificate);
2372
+ this.#onCrlProcess = resolve;
2989
2373
  });
2990
- return;
2991
- }
2992
- if (command === "fingerprint") {
2993
- const optionsDef = [
2994
- { name: "certificateFile", type: String, defaultOption: true },
2995
- { name: "help", alias: "h", type: Boolean }
2996
- ];
2997
- const local_argv = commandLineArgs(optionsDef, { argv });
2998
- if (local_argv.help || !local_argv.certificateFile)
2999
- return showHelp("fingerprint <certificateFile>", "print the certificate fingerprint", optionsDef);
3000
- await wrap(async () => {
3001
- const certificate = local_argv.certificateFile;
3002
- const data = await fingerprint(certificate);
3003
- if (!data) return;
3004
- const s = data.split("=")[1].split(":").join("").trim();
3005
- warningLog(s);
3006
- });
3007
- return;
3008
2374
  }
3009
- console.log(`Unknown command: ${command}`);
2375
+ };
2376
+
2377
+ // packages/node-opcua-pki/lib/pki/toolbox_pfx.ts
2378
+ import assert9 from "assert";
2379
+ import fs10 from "fs";
2380
+ var q2 = quote;
2381
+ var n3 = makePath;
2382
+ async function createPFX(options) {
2383
+ const { certificateFile, privateKeyFile, outputFile, passphrase = "", caCertificateFiles } = options;
2384
+ assert9(fs10.existsSync(certificateFile), `Certificate file does not exist: ${certificateFile}`);
2385
+ assert9(fs10.existsSync(privateKeyFile), `Private key file does not exist: ${privateKeyFile}`);
2386
+ let cmd = `pkcs12 -export`;
2387
+ cmd += ` -in ${q2(n3(certificateFile))}`;
2388
+ cmd += ` -inkey ${q2(n3(privateKeyFile))}`;
2389
+ if (caCertificateFiles) {
2390
+ for (const caFile of caCertificateFiles) {
2391
+ assert9(fs10.existsSync(caFile), `CA certificate file does not exist: ${caFile}`);
2392
+ cmd += ` -certfile ${q2(n3(caFile))}`;
2393
+ }
2394
+ }
2395
+ cmd += ` -out ${q2(n3(outputFile))}`;
2396
+ cmd += ` -passout pass:${passphrase}`;
2397
+ await execute_openssl(cmd, {});
2398
+ }
2399
+ async function extractCertificateFromPFX(options) {
2400
+ const { pfxFile, passphrase = "" } = options;
2401
+ assert9(fs10.existsSync(pfxFile), `PFX file does not exist: ${pfxFile}`);
2402
+ const cmd = `pkcs12 -in ${q2(n3(pfxFile))} -clcerts -nokeys -nodes -passin pass:${passphrase}`;
2403
+ return await execute_openssl(cmd, {});
2404
+ }
2405
+ async function extractPrivateKeyFromPFX(options) {
2406
+ const { pfxFile, passphrase = "" } = options;
2407
+ assert9(fs10.existsSync(pfxFile), `PFX file does not exist: ${pfxFile}`);
2408
+ const cmd = `pkcs12 -in ${q2(n3(pfxFile))} -nocerts -nodes -passin pass:${passphrase}`;
2409
+ return await execute_openssl(cmd, {});
2410
+ }
2411
+ async function extractCACertificatesFromPFX(options) {
2412
+ const { pfxFile, passphrase = "" } = options;
2413
+ assert9(fs10.existsSync(pfxFile), `PFX file does not exist: ${pfxFile}`);
2414
+ const cmd = `pkcs12 -in ${q2(n3(pfxFile))} -cacerts -nokeys -nodes -passin pass:${passphrase}`;
2415
+ return await execute_openssl(cmd, {});
2416
+ }
2417
+ async function extractAllFromPFX(options) {
2418
+ const [certificate, privateKey, caCertificates] = await Promise.all([
2419
+ extractCertificateFromPFX(options),
2420
+ extractPrivateKeyFromPFX(options),
2421
+ extractCACertificatesFromPFX(options)
2422
+ ]);
2423
+ return { certificate, privateKey, caCertificates };
2424
+ }
2425
+ async function convertPFXtoPEM(pfxFile, pemFile, passphrase = "") {
2426
+ assert9(fs10.existsSync(pfxFile), `PFX file does not exist: ${pfxFile}`);
2427
+ const cmd = `pkcs12 -in ${q2(n3(pfxFile))} -out ${q2(n3(pemFile))} -nodes -passin pass:${passphrase}`;
2428
+ await execute_openssl(cmd, {});
2429
+ }
2430
+ async function dumpPFX(pfxFile, passphrase = "") {
2431
+ assert9(fs10.existsSync(pfxFile), `PFX file does not exist: ${pfxFile}`);
2432
+ const cmd = `pkcs12 -in ${q2(n3(pfxFile))} -info -nodes -passin pass:${passphrase}`;
2433
+ return await execute_openssl(cmd, {});
3010
2434
  }
3011
2435
  export {
3012
2436
  CertificateAuthority,
@@ -3016,22 +2440,15 @@ export {
3016
2440
  VerificationStatus,
3017
2441
  adjustApplicationUri,
3018
2442
  adjustDate,
3019
- certificateFileExist,
3020
- debugLog,
3021
- display,
3022
- displayChapter,
3023
- displayDebug,
3024
- displayError,
3025
- displaySubtitle,
3026
- displayTitle,
3027
- doDebug,
2443
+ convertPFXtoPEM,
2444
+ createPFX,
2445
+ dumpPFX,
2446
+ extractAllFromPFX,
2447
+ extractCACertificatesFromPFX,
2448
+ extractCertificateFromPFX,
2449
+ extractPrivateKeyFromPFX,
3028
2450
  findIssuerCertificateInChain,
3029
- g_config,
3030
2451
  install_prerequisite,
3031
- makePath,
3032
- mkdirRecursiveSync,
3033
- main as pki_main,
3034
- quote,
3035
- warningLog
2452
+ quote
3036
2453
  };
3037
2454
  //# sourceMappingURL=index.mjs.map