node-opcua-pki 6.4.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,97 +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 assert10 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
1073
  import fs9 from "fs";
1171
- import path7 from "path";
1074
+ import path6 from "path";
1172
1075
  import { withLock } from "@ster5/global-mutex";
1173
1076
  import chalk6 from "chalk";
1174
1077
  import chokidar from "chokidar";
@@ -1187,18 +1090,18 @@ import {
1187
1090
  } from "node-opcua-crypto";
1188
1091
 
1189
1092
  // packages/node-opcua-pki/lib/toolbox/without_openssl/create_certificate_signing_request.ts
1190
- import assert8 from "assert";
1093
+ import assert7 from "assert";
1191
1094
  import fs7 from "fs";
1192
1095
  import { createCertificateSigningRequest, pemToPrivateKey, Subject as Subject3 } from "node-opcua-crypto";
1193
1096
  async function createCertificateSigningRequestAsync(certificateSigningRequestFilename, params) {
1194
- assert8(params);
1195
- assert8(params.rootDir);
1196
- assert8(params.configFile);
1197
- assert8(params.privateKey);
1198
- assert8(typeof params.privateKey === "string");
1199
- assert8(fs7.existsSync(params.privateKey), `Private key must exist${params.privateKey}`);
1200
- assert8(fs7.existsSync(params.rootDir), "RootDir key must exist");
1201
- 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");
1202
1105
  const subject = params.subject ? new Subject3(params.subject).toString() : void 0;
1203
1106
  displaySubtitle("- Creating a Certificate Signing Request with subtile");
1204
1107
  const privateKeyPem = await fs7.promises.readFile(params.privateKey, "utf-8");
@@ -1217,7 +1120,7 @@ async function createCertificateSigningRequestAsync(certificateSigningRequestFil
1217
1120
  }
1218
1121
 
1219
1122
  // packages/node-opcua-pki/lib/toolbox/without_openssl/create_self_signed_certificate.ts
1220
- import assert9 from "assert";
1123
+ import assert8 from "assert";
1221
1124
  import fs8 from "fs";
1222
1125
  import {
1223
1126
  CertificatePurpose,
@@ -1227,17 +1130,17 @@ import {
1227
1130
  } from "node-opcua-crypto";
1228
1131
  async function createSelfSignedCertificateAsync(certificate, params) {
1229
1132
  params.purpose = params.purpose || CertificatePurpose.ForApplication;
1230
- assert9(params.purpose, "Please provide a Certificate Purpose");
1231
- assert9(fs8.existsSync(params.configFile));
1232
- assert9(fs8.existsSync(params.rootDir));
1233
- 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));
1234
1137
  if (!params.subject) {
1235
1138
  throw Error("Missing subject");
1236
1139
  }
1237
- assert9(typeof params.applicationUri === "string");
1238
- assert9(Array.isArray(params.dns));
1140
+ assert8(typeof params.applicationUri === "string");
1141
+ assert8(Array.isArray(params.dns));
1239
1142
  adjustDate(params);
1240
- assert9(Object.prototype.hasOwnProperty.call(params, "validity"));
1143
+ assert8(Object.prototype.hasOwnProperty.call(params, "validity"));
1241
1144
  let subject = new Subject4(params.subject);
1242
1145
  subject = subject.toString();
1243
1146
  const purpose = params.purpose;
@@ -1294,20 +1197,21 @@ var VerificationStatus = /* @__PURE__ */ ((VerificationStatus2) => {
1294
1197
  return VerificationStatus2;
1295
1198
  })(VerificationStatus || {});
1296
1199
  function makeFingerprint(certificate) {
1297
- return makeSHA1Thumbprint(certificate).toString("hex");
1200
+ const chain = split_der(certificate);
1201
+ return makeSHA1Thumbprint(chain[0]).toString("hex");
1298
1202
  }
1299
1203
  function short(stringToShorten) {
1300
1204
  return stringToShorten.substring(0, 10);
1301
1205
  }
1302
1206
  var forbiddenChars = /[\x00-\x1F<>:"/\\|?*]/g;
1303
1207
  function buildIdealCertificateName(certificate) {
1304
- const fingerprint2 = makeFingerprint(certificate);
1208
+ const fingerprint = makeFingerprint(certificate);
1305
1209
  try {
1306
1210
  const commonName = exploreCertificate(certificate).tbsCertificate.subject.commonName || "";
1307
1211
  const sanitizedCommonName = commonName.replace(forbiddenChars, "_");
1308
- return `${sanitizedCommonName}[${fingerprint2}]`;
1212
+ return `${sanitizedCommonName}[${fingerprint}]`;
1309
1213
  } catch (_err) {
1310
- return `invalid_certificate_[${fingerprint2}]`;
1214
+ return `invalid_certificate_[${fingerprint}]`;
1311
1215
  }
1312
1216
  }
1313
1217
  function findMatchingIssuerKey(entries, wantedIssuerKey) {
@@ -1357,8 +1261,79 @@ var CertificateManagerState = /* @__PURE__ */ ((CertificateManagerState2) => {
1357
1261
  CertificateManagerState2[CertificateManagerState2["Disposed"] = 4] = "Disposed";
1358
1262
  return CertificateManagerState2;
1359
1263
  })(CertificateManagerState || {});
1360
- 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
+ */
1361
1335
  untrustUnknownCertificate = true;
1336
+ /** Current lifecycle state of this instance. */
1362
1337
  state = 0 /* Uninitialized */;
1363
1338
  /** @deprecated Use {@link folderPollingInterval} instead (typo fix). */
1364
1339
  folderPoolingInterval = 5e3;
@@ -1369,13 +1344,14 @@ var CertificateManager = class {
1369
1344
  set folderPollingInterval(value) {
1370
1345
  this.folderPoolingInterval = value;
1371
1346
  }
1347
+ /** RSA key size used when generating the private key. */
1372
1348
  keySize;
1373
- location;
1374
- _watchers = [];
1375
- _readCertificatesCalled = false;
1376
- _filenameToHash = /* @__PURE__ */ new Map();
1377
- _initializingPromise;
1378
- _thumbs = {
1349
+ #location;
1350
+ #watchers = [];
1351
+ #readCertificatesCalled = false;
1352
+ #filenameToHash = /* @__PURE__ */ new Map();
1353
+ #initializingPromise;
1354
+ #thumbs = {
1379
1355
  rejected: /* @__PURE__ */ new Map(),
1380
1356
  trusted: /* @__PURE__ */ new Map(),
1381
1357
  issuers: {
@@ -1384,82 +1360,85 @@ var CertificateManager = class {
1384
1360
  crl: /* @__PURE__ */ new Map(),
1385
1361
  issuersCrl: /* @__PURE__ */ new Map()
1386
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
+ */
1387
1372
  constructor(options) {
1388
1373
  options.keySize = options.keySize || 2048;
1389
1374
  if (!options.location) {
1390
1375
  throw new Error("CertificateManager: missing 'location' option");
1391
1376
  }
1392
- this.location = makePath(options.location, "");
1377
+ this.#location = makePath(options.location, "");
1393
1378
  this.keySize = options.keySize;
1394
1379
  mkdirRecursiveSync(options.location);
1395
- if (!fs9.existsSync(this.location)) {
1396
- 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}`);
1397
1382
  }
1398
1383
  }
1384
+ /** Path to the OpenSSL configuration file. */
1399
1385
  get configFile() {
1400
- return path7.join(this.rootDir, "own/openssl.cnf");
1386
+ return path6.join(this.rootDir, "own/openssl.cnf");
1401
1387
  }
1388
+ /** Root directory of the PKI store. */
1402
1389
  get rootDir() {
1403
- return this.location;
1390
+ return this.#location;
1404
1391
  }
1392
+ /** Path to the private key file (`own/private/private_key.pem`). */
1405
1393
  get privateKey() {
1406
- return path7.join(this.rootDir, "own/private/private_key.pem");
1394
+ return path6.join(this.rootDir, "own/private/private_key.pem");
1407
1395
  }
1396
+ /** Path to the OpenSSL random seed file. */
1408
1397
  get randomFile() {
1409
- return path7.join(this.rootDir, "./random.rnd");
1410
- }
1411
- /**
1412
- * returns the certificate status trusted/rejected
1413
- * @param certificate
1414
- */
1415
- async getCertificateStatus(certificate) {
1416
- await this.initialize();
1417
- let status = await this._checkRejectedOrTrusted(certificate);
1418
- if (status === "unknown") {
1419
- const pem = toPem(certificate, "CERTIFICATE");
1420
- const fingerprint2 = makeFingerprint(certificate);
1421
- const filename = path7.join(this.rejectedFolder, `${buildIdealCertificateName(certificate)}.pem`);
1422
- await fs9.promises.writeFile(filename, pem);
1423
- this._thumbs.rejected.set(fingerprint2, { certificate, filename });
1424
- status = "rejected";
1425
- }
1426
- return status;
1398
+ return path6.join(this.rootDir, "./random.rnd");
1427
1399
  }
1428
1400
  /**
1429
1401
  * Move a certificate to the rejected store.
1430
- * 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.
1431
1403
  * @param certificate - the DER-encoded certificate
1432
1404
  */
1433
1405
  async rejectCertificate(certificate) {
1434
- await this._moveCertificate(certificate, "rejected");
1406
+ await this.#moveCertificate(certificate, "rejected");
1435
1407
  }
1436
1408
  /**
1437
1409
  * Move a certificate to the trusted store.
1438
- * 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.
1439
1411
  * @param certificate - the DER-encoded certificate
1440
1412
  */
1441
1413
  async trustCertificate(certificate) {
1442
- await this._moveCertificate(certificate, "trusted");
1414
+ await this.#moveCertificate(certificate, "trusted");
1443
1415
  }
1444
1416
  /** Path to the rejected certificates folder. */
1445
1417
  get rejectedFolder() {
1446
- return path7.join(this.rootDir, "rejected");
1418
+ return path6.join(this.rootDir, "rejected");
1447
1419
  }
1448
1420
  /** Path to the trusted certificates folder. */
1449
1421
  get trustedFolder() {
1450
- return path7.join(this.rootDir, "trusted/certs");
1422
+ return path6.join(this.rootDir, "trusted/certs");
1451
1423
  }
1452
1424
  /** Path to the trusted CRL folder. */
1453
1425
  get crlFolder() {
1454
- return path7.join(this.rootDir, "trusted/crl");
1426
+ return path6.join(this.rootDir, "trusted/crl");
1455
1427
  }
1456
1428
  /** Path to the issuer (CA) certificates folder. */
1457
1429
  get issuersCertFolder() {
1458
- return path7.join(this.rootDir, "issuers/certs");
1430
+ return path6.join(this.rootDir, "issuers/certs");
1459
1431
  }
1460
1432
  /** Path to the issuer CRL folder. */
1461
1433
  get issuersCrlFolder() {
1462
- 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");
1463
1442
  }
1464
1443
  /**
1465
1444
  * Check if a certificate is in the trusted store.
@@ -1470,11 +1449,11 @@ var CertificateManager = class {
1470
1449
  * or `"BadCertificateInvalid"` if the certificate cannot be parsed.
1471
1450
  */
1472
1451
  async isCertificateTrusted(certificate) {
1473
- const fingerprint2 = makeFingerprint(certificate);
1474
- if (this._thumbs.trusted.has(fingerprint2)) {
1452
+ const fingerprint = makeFingerprint(certificate);
1453
+ if (this.#thumbs.trusted.has(fingerprint)) {
1475
1454
  return "Good";
1476
1455
  }
1477
- if (!this._thumbs.rejected.has(fingerprint2)) {
1456
+ if (!this.#thumbs.rejected.has(fingerprint)) {
1478
1457
  if (!this.untrustUnknownCertificate) {
1479
1458
  return "Good";
1480
1459
  }
@@ -1483,17 +1462,14 @@ var CertificateManager = class {
1483
1462
  } catch (_err) {
1484
1463
  return "BadCertificateInvalid";
1485
1464
  }
1486
- const filename = path7.join(
1487
- this.rejectedFolder,
1488
- `${buildIdealCertificateName(certificate)}.pem`
1489
- );
1465
+ const filename = path6.join(this.rejectedFolder, `${buildIdealCertificateName(certificate)}.pem`);
1490
1466
  debugLog("certificate has never been seen before and is now rejected (untrusted) ", filename);
1491
1467
  await fsWriteFile(filename, toPem(certificate, "CERTIFICATE"));
1492
- this._thumbs.rejected.set(fingerprint2, { certificate, filename });
1468
+ this.#thumbs.rejected.set(fingerprint, { certificate, filename });
1493
1469
  }
1494
1470
  return "BadCertificateUntrusted";
1495
1471
  }
1496
- async _innerVerifyCertificateAsync(certificate, _isIssuer, level, options) {
1472
+ async #innerVerifyCertificateAsync(certificate, _isIssuer, level, options) {
1497
1473
  if (level >= 5) {
1498
1474
  return "BadSecurityChecksFailed" /* BadSecurityChecksFailed */;
1499
1475
  }
@@ -1528,7 +1504,7 @@ var CertificateManager = class {
1528
1504
  } else {
1529
1505
  debugLog(" the issuer certificate has been found in the issuer.cert folder !");
1530
1506
  }
1531
- const issuerStatus = await this._innerVerifyCertificateAsync(issuerCertificate, true, level + 1, options);
1507
+ const issuerStatus = await this.#innerVerifyCertificateAsync(issuerCertificate, true, level + 1, options);
1532
1508
  if (issuerStatus === "BadCertificateRevocationUnknown" /* BadCertificateRevocationUnknown */) {
1533
1509
  return "BadCertificateIssuerRevocationUnknown" /* BadCertificateIssuerRevocationUnknown */;
1534
1510
  }
@@ -1562,7 +1538,7 @@ var CertificateManager = class {
1562
1538
  debugLog("revokedStatus", revokedStatus);
1563
1539
  return revokedStatus;
1564
1540
  }
1565
- const issuerTrustedStatus = await this._checkRejectedOrTrusted(issuerCertificate);
1541
+ const issuerTrustedStatus = await this.#checkRejectedOrTrusted(issuerCertificate);
1566
1542
  debugLog("issuerTrustedStatus", issuerTrustedStatus);
1567
1543
  if (issuerTrustedStatus === "unknown") {
1568
1544
  hasTrustedIssuer = false;
@@ -1581,9 +1557,11 @@ var CertificateManager = class {
1581
1557
  debugLog("revokedStatus of self signed certificate:", revokedStatus);
1582
1558
  }
1583
1559
  }
1584
- const status = await this._checkRejectedOrTrusted(certificate);
1560
+ const status = await this.#checkRejectedOrTrusted(certificate);
1585
1561
  if (status === "rejected") {
1586
- return "BadCertificateUntrusted" /* BadCertificateUntrusted */;
1562
+ if (!(options.acceptCertificateWithValidIssuerChain && hasValidIssuer && hasTrustedIssuer)) {
1563
+ return "BadCertificateUntrusted" /* BadCertificateUntrusted */;
1564
+ }
1587
1565
  }
1588
1566
  const _c2 = chain[1] ? exploreCertificateInfo(chain[1]) : "non";
1589
1567
  debugLog("chain[1] info=", _c2);
@@ -1616,60 +1594,85 @@ var CertificateManager = class {
1616
1594
  if (!hasValidIssuer) {
1617
1595
  return "BadCertificateUntrusted" /* BadCertificateUntrusted */;
1618
1596
  }
1597
+ if (!options.acceptCertificateWithValidIssuerChain) {
1598
+ return "BadCertificateUntrusted" /* BadCertificateUntrusted */;
1599
+ }
1619
1600
  return isTimeInvalid ? "BadCertificateTimeInvalid" /* BadCertificateTimeInvalid */ : "Good" /* Good */;
1620
1601
  } else {
1621
1602
  return "BadCertificateUntrusted" /* BadCertificateUntrusted */;
1622
1603
  }
1623
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
+ */
1624
1617
  async verifyCertificateAsync(certificate, options) {
1625
- const status1 = await this._innerVerifyCertificateAsync(certificate, false, 0, options);
1618
+ const status1 = await this.#innerVerifyCertificateAsync(certificate, false, 0, options);
1626
1619
  return status1;
1627
1620
  }
1628
1621
  /**
1629
- * Verify certificate validity
1630
- * @method verifyCertificate
1631
- * @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
1632
1630
  */
1633
1631
  async verifyCertificate(certificate, options) {
1634
1632
  if (!certificate) {
1635
1633
  return "BadSecurityChecksFailed" /* BadSecurityChecksFailed */;
1636
1634
  }
1637
- 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
+ }
1638
1642
  }
1639
- /*
1640
- *
1641
- * PKI
1642
- * +---> trusted
1643
- * +---> rejected
1644
- * +---> own
1645
- * +---> cert
1646
- * +---> own
1643
+ /**
1644
+ * Initialize the PKI directory structure, generate the
1645
+ * private key (if missing), and start file-system watchers.
1647
1646
  *
1647
+ * This method is idempotent — subsequent calls are no-ops.
1648
+ * It must be called before any certificate operations.
1648
1649
  */
1649
1650
  async initialize() {
1650
1651
  if (this.state !== 0 /* Uninitialized */) {
1651
1652
  return;
1652
1653
  }
1653
1654
  this.state = 1 /* Initializing */;
1654
- this._initializingPromise = this._initialize();
1655
- await this._initializingPromise;
1656
- this._initializingPromise = void 0;
1655
+ this.#initializingPromise = this.#initialize();
1656
+ await this.#initializingPromise;
1657
+ this.#initializingPromise = void 0;
1657
1658
  this.state = 2 /* Initialized */;
1659
+ _CertificateManager.#activeInstances.add(this);
1660
+ _CertificateManager.#installProcessCleanup();
1658
1661
  }
1659
- async _initialize() {
1662
+ async #initialize() {
1660
1663
  this.state = 1 /* Initializing */;
1661
- const pkiDir = this.location;
1664
+ const pkiDir = this.#location;
1662
1665
  mkdirRecursiveSync(pkiDir);
1663
- mkdirRecursiveSync(path7.join(pkiDir, "own"));
1664
- mkdirRecursiveSync(path7.join(pkiDir, "own/certs"));
1665
- mkdirRecursiveSync(path7.join(pkiDir, "own/private"));
1666
- mkdirRecursiveSync(path7.join(pkiDir, "rejected"));
1667
- mkdirRecursiveSync(path7.join(pkiDir, "trusted"));
1668
- mkdirRecursiveSync(path7.join(pkiDir, "trusted/certs"));
1669
- mkdirRecursiveSync(path7.join(pkiDir, "trusted/crl"));
1670
- mkdirRecursiveSync(path7.join(pkiDir, "issuers"));
1671
- mkdirRecursiveSync(path7.join(pkiDir, "issuers/certs"));
1672
- 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"));
1673
1676
  if (!fs9.existsSync(this.configFile) || !fs9.existsSync(this.privateKey)) {
1674
1677
  return await this.withLock2(async () => {
1675
1678
  if (this.state === 3 /* Disposing */ || this.state === 4 /* Disposed */) {
@@ -1681,13 +1684,13 @@ var CertificateManager = class {
1681
1684
  if (!fs9.existsSync(this.privateKey)) {
1682
1685
  debugLog("generating private key ...");
1683
1686
  await generatePrivateKeyFile2(this.privateKey, this.keySize);
1684
- await this._readCertificates();
1687
+ await this.#readCertificates();
1685
1688
  } else {
1686
- await this._readCertificates();
1689
+ await this.#readCertificates();
1687
1690
  }
1688
1691
  });
1689
1692
  } else {
1690
- await this._readCertificates();
1693
+ await this.#readCertificates();
1691
1694
  }
1692
1695
  }
1693
1696
  /**
@@ -1704,19 +1707,20 @@ var CertificateManager = class {
1704
1707
  return;
1705
1708
  }
1706
1709
  if (this.state === 1 /* Initializing */) {
1707
- if (this._initializingPromise) {
1708
- await this._initializingPromise;
1710
+ if (this.#initializingPromise) {
1711
+ await this.#initializingPromise;
1709
1712
  }
1710
1713
  }
1711
1714
  try {
1712
1715
  this.state = 3 /* Disposing */;
1713
- await Promise.all(this._watchers.map((w) => w.close()));
1714
- this._watchers.forEach((w) => {
1716
+ await Promise.all(this.#watchers.map((w) => w.close()));
1717
+ this.#watchers.forEach((w) => {
1715
1718
  w.removeAllListeners();
1716
1719
  });
1717
- this._watchers.splice(0);
1720
+ this.#watchers.splice(0);
1718
1721
  } finally {
1719
1722
  this.state = 4 /* Disposed */;
1723
+ _CertificateManager.#activeInstances.delete(this);
1720
1724
  }
1721
1725
  }
1722
1726
  /**
@@ -1729,30 +1733,34 @@ var CertificateManager = class {
1729
1733
  * state without waiting for file-system events.
1730
1734
  */
1731
1735
  async reloadCertificates() {
1732
- await Promise.all(this._watchers.map((w) => w.close()));
1733
- for (const w of this._watchers) {
1736
+ await Promise.all(this.#watchers.map((w) => w.close()));
1737
+ for (const w of this.#watchers) {
1734
1738
  w.removeAllListeners();
1735
1739
  }
1736
- this._watchers.splice(0);
1737
- this._thumbs.rejected.clear();
1738
- this._thumbs.trusted.clear();
1739
- this._thumbs.issuers.certs.clear();
1740
- this._thumbs.crl.clear();
1741
- this._thumbs.issuersCrl.clear();
1742
- this._filenameToHash.clear();
1743
- this._readCertificatesCalled = false;
1744
- await this._readCertificates();
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();
1745
1749
  }
1746
1750
  async withLock2(action) {
1747
- const lockFileName = path7.join(this.rootDir, "mutex.lock");
1751
+ const lockFileName = path6.join(this.rootDir, "mutex.lock");
1748
1752
  return withLock({ fileToLock: lockFileName }, async () => {
1749
1753
  return await action();
1750
1754
  });
1751
1755
  }
1752
1756
  /**
1757
+ * Create a self-signed certificate for this PKI's private key.
1753
1758
  *
1754
- * 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.
1755
1761
  *
1762
+ * @param params - certificate parameters (subject, SANs,
1763
+ * validity, etc.)
1756
1764
  */
1757
1765
  async createSelfSignedCertificate(params) {
1758
1766
  if (typeof params.applicationUri !== "string") {
@@ -1761,7 +1769,7 @@ var CertificateManager = class {
1761
1769
  if (!fs9.existsSync(this.privateKey)) {
1762
1770
  throw new Error(`Cannot find private key ${this.privateKey}`);
1763
1771
  }
1764
- 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");
1765
1773
  certificateFilename = params.outputFile || certificateFilename;
1766
1774
  const _params = params;
1767
1775
  _params.rootDir = this.rootDir;
@@ -1772,6 +1780,16 @@ var CertificateManager = class {
1772
1780
  await createSelfSignedCertificate(certificateFilename, _params);
1773
1781
  });
1774
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
+ */
1775
1793
  async createCertificateRequest(params) {
1776
1794
  if (!params) {
1777
1795
  throw new Error("params is required");
@@ -1780,13 +1798,13 @@ var CertificateManager = class {
1780
1798
  if (Object.prototype.hasOwnProperty.call(_params, "rootDir")) {
1781
1799
  throw new Error("rootDir should not be specified ");
1782
1800
  }
1783
- _params.rootDir = path7.resolve(this.rootDir);
1784
- _params.configFile = path7.resolve(this.configFile);
1785
- _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);
1786
1804
  return await this.withLock2(async () => {
1787
1805
  const now = /* @__PURE__ */ new Date();
1788
- const today2 = `${now.toISOString().slice(0, 10)}_${now.getTime()}`;
1789
- 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`);
1790
1808
  await createCertificateSigningRequestAsync(certificateSigningRequestFilename, _params);
1791
1809
  return certificateSigningRequestFilename;
1792
1810
  });
@@ -1807,13 +1825,13 @@ var CertificateManager = class {
1807
1825
  }
1808
1826
  }
1809
1827
  const pemCertificate = toPem(certificate, "CERTIFICATE");
1810
- const fingerprint2 = makeFingerprint(certificate);
1811
- if (this._thumbs.issuers.certs.has(fingerprint2)) {
1828
+ const fingerprint = makeFingerprint(certificate);
1829
+ if (this.#thumbs.issuers.certs.has(fingerprint)) {
1812
1830
  return "Good" /* Good */;
1813
1831
  }
1814
- const filename = path7.join(this.issuersCertFolder, `issuer_${buildIdealCertificateName(certificate)}.pem`);
1832
+ const filename = path6.join(this.issuersCertFolder, `issuer_${buildIdealCertificateName(certificate)}.pem`);
1815
1833
  await fs9.promises.writeFile(filename, pemCertificate, "ascii");
1816
- this._thumbs.issuers.certs.set(fingerprint2, { certificate, filename });
1834
+ this.#thumbs.issuers.certs.set(fingerprint, { certificate, filename });
1817
1835
  if (addInTrustList) {
1818
1836
  await this.trustCertificate(certificate);
1819
1837
  }
@@ -1827,7 +1845,7 @@ var CertificateManager = class {
1827
1845
  async addRevocationList(crl, target = "issuers") {
1828
1846
  return await this.withLock2(async () => {
1829
1847
  try {
1830
- const index = target === "trusted" ? this._thumbs.crl : this._thumbs.issuersCrl;
1848
+ const index = target === "trusted" ? this.#thumbs.crl : this.#thumbs.issuersCrl;
1831
1849
  const folder = target === "trusted" ? this.crlFolder : this.issuersCrlFolder;
1832
1850
  const crlInfo = exploreCertificateRevocationList(crl);
1833
1851
  const key = crlInfo.tbsCertList.issuerFingerprint;
@@ -1835,10 +1853,10 @@ var CertificateManager = class {
1835
1853
  index.set(key, { crls: [], serialNumbers: {} });
1836
1854
  }
1837
1855
  const pemCertificate = toPem(crl, "X509 CRL");
1838
- const filename = path7.join(folder, `crl_${buildIdealCertificateName(crl)}.pem`);
1856
+ const filename = path6.join(folder, `crl_${buildIdealCertificateName(crl)}.pem`);
1839
1857
  await fs9.promises.writeFile(filename, pemCertificate, "ascii");
1840
- await this._on_crl_file_added(index, filename);
1841
- await this.waitAndCheckCRLProcessingStatus();
1858
+ await this.#onCrlFileAdded(index, filename);
1859
+ await this.#waitAndCheckCRLProcessingStatus();
1842
1860
  return "Good" /* Good */;
1843
1861
  } catch (err) {
1844
1862
  debugLog(err);
@@ -1857,9 +1875,9 @@ var CertificateManager = class {
1857
1875
  try {
1858
1876
  const files = await fs9.promises.readdir(folder);
1859
1877
  for (const file of files) {
1860
- const ext = path7.extname(file).toLowerCase();
1878
+ const ext = path6.extname(file).toLowerCase();
1861
1879
  if (ext === ".crl" || ext === ".pem" || ext === ".der") {
1862
- await fs9.promises.unlink(path7.join(folder, file));
1880
+ await fs9.promises.unlink(path6.join(folder, file));
1863
1881
  }
1864
1882
  }
1865
1883
  } catch (err) {
@@ -1870,10 +1888,10 @@ var CertificateManager = class {
1870
1888
  index.clear();
1871
1889
  };
1872
1890
  if (target === "issuers" || target === "all") {
1873
- await clearFolder(this.issuersCrlFolder, this._thumbs.issuersCrl);
1891
+ await clearFolder(this.issuersCrlFolder, this.#thumbs.issuersCrl);
1874
1892
  }
1875
1893
  if (target === "trusted" || target === "all") {
1876
- await clearFolder(this.crlFolder, this._thumbs.crl);
1894
+ await clearFolder(this.crlFolder, this.#thumbs.crl);
1877
1895
  }
1878
1896
  }
1879
1897
  /**
@@ -1882,9 +1900,9 @@ var CertificateManager = class {
1882
1900
  * @param thumbprint - hex-encoded SHA-1 thumbprint (lowercase)
1883
1901
  */
1884
1902
  async hasIssuer(thumbprint) {
1885
- await this._readCertificates();
1903
+ await this.#readCertificates();
1886
1904
  const normalized = thumbprint.toLowerCase();
1887
- return this._thumbs.issuers.certs.has(normalized);
1905
+ return this.#thumbs.issuers.certs.has(normalized);
1888
1906
  }
1889
1907
  /**
1890
1908
  * Remove a trusted certificate identified by its SHA-1 thumbprint.
@@ -1894,9 +1912,9 @@ var CertificateManager = class {
1894
1912
  * @returns the removed certificate buffer, or `null` if not found
1895
1913
  */
1896
1914
  async removeTrustedCertificate(thumbprint) {
1897
- await this._readCertificates();
1915
+ await this.#readCertificates();
1898
1916
  const normalized = thumbprint.toLowerCase();
1899
- const entry = this._thumbs.trusted.get(normalized);
1917
+ const entry = this.#thumbs.trusted.get(normalized);
1900
1918
  if (!entry) {
1901
1919
  return null;
1902
1920
  }
@@ -1907,7 +1925,7 @@ var CertificateManager = class {
1907
1925
  throw err;
1908
1926
  }
1909
1927
  }
1910
- this._thumbs.trusted.delete(normalized);
1928
+ this.#thumbs.trusted.delete(normalized);
1911
1929
  return entry.certificate;
1912
1930
  }
1913
1931
  /**
@@ -1918,9 +1936,9 @@ var CertificateManager = class {
1918
1936
  * @returns the removed certificate buffer, or `null` if not found
1919
1937
  */
1920
1938
  async removeIssuer(thumbprint) {
1921
- await this._readCertificates();
1939
+ await this.#readCertificates();
1922
1940
  const normalized = thumbprint.toLowerCase();
1923
- const entry = this._thumbs.issuers.certs.get(normalized);
1941
+ const entry = this.#thumbs.issuers.certs.get(normalized);
1924
1942
  if (!entry) {
1925
1943
  return null;
1926
1944
  }
@@ -1931,7 +1949,7 @@ var CertificateManager = class {
1931
1949
  throw err;
1932
1950
  }
1933
1951
  }
1934
- this._thumbs.issuers.certs.delete(normalized);
1952
+ this.#thumbs.issuers.certs.delete(normalized);
1935
1953
  return entry.certificate;
1936
1954
  }
1937
1955
  /**
@@ -1958,10 +1976,10 @@ var CertificateManager = class {
1958
1976
  index.delete(issuerFingerprint);
1959
1977
  };
1960
1978
  if (target === "issuers" || target === "all") {
1961
- await processIndex(this._thumbs.issuersCrl);
1979
+ await processIndex(this.#thumbs.issuersCrl);
1962
1980
  }
1963
1981
  if (target === "trusted" || target === "all") {
1964
- await processIndex(this._thumbs.crl);
1982
+ await processIndex(this.#thumbs.crl);
1965
1983
  }
1966
1984
  }
1967
1985
  /**
@@ -1997,10 +2015,10 @@ var CertificateManager = class {
1997
2015
  const issuerThumbprints = /* @__PURE__ */ new Set();
1998
2016
  const files = await fs9.promises.readdir(issuerFolder);
1999
2017
  for (const file of files) {
2000
- const ext = path7.extname(file).toLowerCase();
2018
+ const ext = path6.extname(file).toLowerCase();
2001
2019
  if (ext === ".pem" || ext === ".der") {
2002
2020
  try {
2003
- const issuerCert = readCertificate(path7.join(issuerFolder, file));
2021
+ const issuerCert = readCertificate(path6.join(issuerFolder, file));
2004
2022
  const fp = makeFingerprint(issuerCert);
2005
2023
  issuerThumbprints.add(fp);
2006
2024
  } catch (_err) {
@@ -2030,8 +2048,8 @@ var CertificateManager = class {
2030
2048
  * signed by this issuer.
2031
2049
  */
2032
2050
  async isIssuerInUseByTrustedCertificate(issuerCertificate) {
2033
- await this._readCertificates();
2034
- for (const entry of this._thumbs.trusted.values()) {
2051
+ await this.#readCertificates();
2052
+ for (const entry of this.#thumbs.trusted.values()) {
2035
2053
  if (!entry.certificate) continue;
2036
2054
  try {
2037
2055
  if (verifyCertificateSignature(entry.certificate, issuerCertificate)) {
@@ -2066,7 +2084,7 @@ var CertificateManager = class {
2066
2084
  debugLog("Certificate has no extension 3");
2067
2085
  return null;
2068
2086
  }
2069
- const issuerCertificates = [...this._thumbs.issuers.certs.values()];
2087
+ const issuerCertificates = [...this.#thumbs.issuers.certs.values()];
2070
2088
  const selectedIssuerCertificates = findMatchingIssuerKey(issuerCertificates, wantedIssuerKey);
2071
2089
  if (selectedIssuerCertificates.length > 0) {
2072
2090
  if (selectedIssuerCertificates.length > 1) {
@@ -2074,7 +2092,7 @@ var CertificateManager = class {
2074
2092
  }
2075
2093
  return selectedIssuerCertificates[0].certificate || null;
2076
2094
  }
2077
- const trustedCertificates = [...this._thumbs.trusted.values()];
2095
+ const trustedCertificates = [...this.#thumbs.trusted.values()];
2078
2096
  const selectedTrustedCertificates = findMatchingIssuerKey(trustedCertificates, wantedIssuerKey);
2079
2097
  if (selectedTrustedCertificates.length > 1) {
2080
2098
  warningLog(
@@ -2086,52 +2104,78 @@ var CertificateManager = class {
2086
2104
  return selectedTrustedCertificates.length > 0 ? selectedTrustedCertificates[0].certificate : null;
2087
2105
  }
2088
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.
2089
2110
  * @internal
2090
2111
  * @private
2091
2112
  */
2092
- async _checkRejectedOrTrusted(certificate) {
2093
- const fingerprint2 = makeFingerprint(certificate);
2094
- debugLog("_checkRejectedOrTrusted fingerprint ", short(fingerprint2));
2095
- await this._readCertificates();
2096
- if (this._thumbs.rejected.has(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)) {
2097
2118
  return "rejected";
2098
2119
  }
2099
- if (this._thumbs.trusted.has(fingerprint2)) {
2120
+ if (this.#thumbs.trusted.has(fingerprint)) {
2100
2121
  return "trusted";
2101
2122
  }
2102
2123
  return "unknown";
2103
2124
  }
2104
- async _moveCertificate(certificate, newStatus) {
2125
+ async #moveCertificate(certificate, newStatus) {
2105
2126
  await this.withLock2(async () => {
2106
- const fingerprint2 = makeFingerprint(certificate);
2107
- const status = await this.getCertificateStatus(certificate);
2108
- debugLog("_moveCertificate", fingerprint2.substring(0, 10), "from", status, "to", newStatus);
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";
2135
+ }
2136
+ debugLog("#moveCertificate", fingerprint.substring(0, 10), "from", status, "to", newStatus);
2109
2137
  if (status !== "rejected" && status !== "trusted") {
2110
- throw new Error(`_moveCertificate: unexpected status '${status}' for certificate ${fingerprint2.substring(0, 10)}`);
2138
+ throw new Error(`#moveCertificate: unexpected status '${status}' for certificate ${fingerprint.substring(0, 10)}`);
2111
2139
  }
2112
2140
  if (status !== newStatus) {
2113
- const indexSrc = status === "rejected" ? this._thumbs.rejected : this._thumbs.trusted;
2114
- const srcEntry = indexSrc.get(fingerprint2);
2141
+ const indexSrc = status === "rejected" ? this.#thumbs.rejected : this.#thumbs.trusted;
2142
+ const srcEntry = indexSrc.get(fingerprint);
2115
2143
  if (!srcEntry) {
2116
- debugLog(" cannot find certificate ", fingerprint2.substring(0, 10), " in", status);
2117
- throw new Error(`_moveCertificate: certificate ${fingerprint2.substring(0, 10)} not found in ${status} index`);
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`);
2118
2146
  }
2119
2147
  const destFolder = newStatus === "trusted" ? this.trustedFolder : this.rejectedFolder;
2120
- const certificateDest = path7.join(destFolder, path7.basename(srcEntry.filename));
2121
- debugLog("_moveCertificate", fingerprint2.substring(0, 10), "old name", srcEntry.filename);
2122
- debugLog("_moveCertificate", fingerprint2.substring(0, 10), "new name", certificateDest);
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);
2123
2151
  await fs9.promises.rename(srcEntry.filename, certificateDest);
2124
- indexSrc.delete(fingerprint2);
2125
- const indexDest = newStatus === "trusted" ? this._thumbs.trusted : this._thumbs.rejected;
2126
- indexDest.set(fingerprint2, { certificate, filename: certificateDest });
2152
+ indexSrc.delete(fingerprint);
2153
+ const indexDest = newStatus === "trusted" ? this.#thumbs.trusted : this.#thumbs.rejected;
2154
+ indexDest.set(fingerprint, { certificate, filename: certificateDest });
2127
2155
  }
2128
2156
  });
2129
2157
  }
2130
- _findAssociatedCRLs(issuerCertificate) {
2158
+ #findAssociatedCRLs(issuerCertificate) {
2131
2159
  const issuerCertificateInfo = exploreCertificate(issuerCertificate);
2132
2160
  const key = issuerCertificateInfo.tbsCertificate.subjectFingerPrint;
2133
- return this._thumbs.issuersCrl.get(key) ?? this._thumbs.crl.get(key) ?? null;
2161
+ return this.#thumbs.issuersCrl.get(key) ?? this.#thumbs.crl.get(key) ?? null;
2134
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
+ */
2135
2179
  async isCertificateRevoked(certificate, issuerCertificate) {
2136
2180
  if (isSelfSigned3(certificate)) {
2137
2181
  return "Good" /* Good */;
@@ -2142,42 +2186,42 @@ var CertificateManager = class {
2142
2186
  if (!issuerCertificate) {
2143
2187
  return "BadCertificateChainIncomplete" /* BadCertificateChainIncomplete */;
2144
2188
  }
2145
- const crls = this._findAssociatedCRLs(issuerCertificate);
2189
+ const crls = this.#findAssociatedCRLs(issuerCertificate);
2146
2190
  if (!crls) {
2147
2191
  return "BadCertificateRevocationUnknown" /* BadCertificateRevocationUnknown */;
2148
2192
  }
2149
2193
  const certInfo = exploreCertificate(certificate);
2150
2194
  const serialNumber = certInfo.tbsCertificate.serialNumber || certInfo.tbsCertificate.extensions?.authorityKeyIdentifier?.serial || "";
2151
2195
  const key = certInfo.tbsCertificate.extensions?.authorityKeyIdentifier?.authorityCertIssuerFingerPrint || "<unknown>";
2152
- const crl2 = this._thumbs.crl.get(key) ?? null;
2196
+ const crl2 = this.#thumbs.crl.get(key) ?? null;
2153
2197
  if (crls.serialNumbers[serialNumber] || crl2?.serialNumbers[serialNumber]) {
2154
2198
  return "BadCertificateRevoked" /* BadCertificateRevoked */;
2155
2199
  }
2156
2200
  return "Good" /* Good */;
2157
2201
  }
2158
- _pending_crl_to_process = 0;
2159
- _on_crl_process;
2160
- queue = [];
2161
- _on_crl_file_added(index, filename) {
2162
- this.queue.push({ index, filename });
2163
- this._pending_crl_to_process += 1;
2164
- if (this._pending_crl_to_process === 1) {
2165
- 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();
2166
2210
  }
2167
2211
  }
2168
- async _process_next_crl() {
2212
+ async #processNextCrl() {
2169
2213
  try {
2170
- const nextCRL = this.queue.shift();
2214
+ const nextCRL = this.#queue.shift();
2171
2215
  if (!nextCRL) return;
2172
2216
  const { index, filename } = nextCRL;
2173
2217
  const crl = await readCertificateRevocationList(filename);
2174
2218
  const crlInfo = exploreCertificateRevocationList(crl);
2175
2219
  debugLog(chalk6.cyan("add CRL in folder "), filename);
2176
- const fingerprint2 = crlInfo.tbsCertList.issuerFingerprint;
2177
- if (!index.has(fingerprint2)) {
2178
- index.set(fingerprint2, { crls: [], serialNumbers: {} });
2220
+ const fingerprint = crlInfo.tbsCertList.issuerFingerprint;
2221
+ if (!index.has(fingerprint)) {
2222
+ index.set(fingerprint, { crls: [], serialNumbers: {} });
2179
2223
  }
2180
- const data = index.get(fingerprint2) || { crls: [], serialNumbers: {} };
2224
+ const data = index.get(fingerprint) || { crls: [], serialNumbers: {} };
2181
2225
  data.crls.push({ crlInfo, filename });
2182
2226
  for (const revokedCertificate of crlInfo.tbsCertList.revokedCertificates) {
2183
2227
  const serialNumber = revokedCertificate.userCertificate;
@@ -2185,875 +2229,208 @@ var CertificateManager = class {
2185
2229
  data.serialNumbers[serialNumber] = revokedCertificate.revocationDate;
2186
2230
  }
2187
2231
  }
2188
- debugLog(chalk6.cyan("CRL"), fingerprint2, "serial numbers = ", Object.keys(data.serialNumbers));
2232
+ debugLog(chalk6.cyan("CRL"), fingerprint, "serial numbers = ", Object.keys(data.serialNumbers));
2189
2233
  } catch (err) {
2190
2234
  debugLog("CRL filename error =");
2191
2235
  debugLog(err);
2192
2236
  }
2193
- this._pending_crl_to_process -= 1;
2194
- if (this._pending_crl_to_process === 0) {
2195
- if (this._on_crl_process) {
2196
- this._on_crl_process();
2197
- 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;
2198
2242
  }
2199
2243
  } else {
2200
- this._process_next_crl();
2244
+ this.#processNextCrl();
2201
2245
  }
2202
2246
  }
2203
- async _readCertificates() {
2204
- if (this._readCertificatesCalled) {
2247
+ async #readCertificates() {
2248
+ if (this.#readCertificatesCalled) {
2205
2249
  return;
2206
2250
  }
2207
- this._readCertificatesCalled = true;
2251
+ this.#readCertificatesCalled = true;
2208
2252
  const usePolling = process.env.OPCUA_PKI_USE_POLLING === "true";
2209
- const options = {
2253
+ const chokidarOptions = {
2210
2254
  usePolling,
2211
2255
  ...usePolling ? { interval: Math.min(10 * 60 * 1e3, Math.max(100, this.folderPoolingInterval)) } : {},
2212
2256
  persistent: false
2213
2257
  };
2214
- async function _walkCRLFiles(folder, index) {
2215
- await new Promise((resolve, _reject) => {
2216
- const w = chokidar.watch(folder, options);
2217
- w.on("unlink", (filename) => {
2218
- for (const [key, data] of index.entries()) {
2219
- data.crls = data.crls.filter((c) => c.filename !== filename);
2220
- if (data.crls.length === 0) {
2221
- index.delete(key);
2222
- }
2223
- }
2224
- });
2225
- w.on("add", (filename) => {
2226
- this._on_crl_file_added(index, filename);
2227
- });
2228
- w.on("change", (changedPath) => {
2229
- debugLog("change in folder ", folder, changedPath);
2230
- });
2231
- this._watchers.push(w);
2232
- w.on("ready", () => {
2233
- resolve();
2234
- });
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;
2235
2265
  });
2236
- }
2237
- async function _walkAllFiles(folder, index) {
2238
- const w = chokidar.watch(folder, options);
2266
+ const w = chokidar.watch(folder, chokidarOptions);
2267
+ const unreffAll = () => {
2268
+ fs9.watch = origWatch;
2269
+ for (const h of capturedHandles) {
2270
+ h.unref();
2271
+ }
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);
2239
2288
  w.on("unlink", (filename) => {
2240
- debugLog(chalk6.cyan(`unlink in folder ${folder}`), filename);
2241
- const h = this._filenameToHash.get(filename);
2242
- if (h && index.has(h)) {
2243
- index.delete(h);
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
+ }
2244
2294
  }
2245
2295
  });
2246
2296
  w.on("add", (filename) => {
2247
- debugLog(chalk6.cyan(`add in folder ${folder}`), filename);
2248
- try {
2249
- const certificate = readCertificate(filename);
2250
- const info = exploreCertificate(certificate);
2251
- const fingerprint2 = makeFingerprint(certificate);
2252
- index.set(fingerprint2, { certificate, filename, info });
2253
- this._filenameToHash.set(filename, fingerprint2);
2254
- debugLog(
2255
- chalk6.magenta("CERT"),
2256
- info.tbsCertificate.subjectFingerPrint,
2257
- info.tbsCertificate.serialNumber,
2258
- info.tbsCertificate.extensions?.authorityKeyIdentifier?.authorityCertIssuerFingerPrint
2259
- );
2260
- } catch (err) {
2261
- debugLog(`Walk files in folder ${folder} with file ${filename}`);
2262
- debugLog(err);
2263
- }
2297
+ this.#onCrlFileAdded(index, filename);
2264
2298
  });
2265
2299
  w.on("change", (changedPath) => {
2266
- debugLog(chalk6.cyan(`change in folder ${folder}`), changedPath);
2267
- try {
2268
- const certificate = readCertificate(changedPath);
2269
- const newFingerprint = makeFingerprint(certificate);
2270
- const oldHash = this._filenameToHash.get(changedPath);
2271
- if (oldHash && oldHash !== newFingerprint) {
2272
- index.delete(oldHash);
2273
- }
2274
- index.set(newFingerprint, { certificate, filename: changedPath, info: exploreCertificate(certificate) });
2275
- this._filenameToHash.set(changedPath, newFingerprint);
2276
- } catch (err) {
2277
- debugLog(`change event: failed to re-read ${changedPath}`, err);
2278
- }
2300
+ debugLog("change in folder ", folder, changedPath);
2279
2301
  });
2280
- this._watchers.push(w);
2281
- await new Promise((resolve, _reject) => {
2282
- w.on("ready", () => {
2283
- debugLog("ready");
2284
- debugLog([...index.keys()].map((k) => k.substring(0, 10)));
2285
- resolve();
2286
- });
2302
+ this.#watchers.push(w);
2303
+ w.on("ready", () => {
2304
+ unreffAll();
2305
+ resolve();
2287
2306
  });
2288
- }
2289
- const promises = [
2290
- _walkAllFiles.bind(this, this.trustedFolder, this._thumbs.trusted)(),
2291
- _walkAllFiles.bind(this, this.issuersCertFolder, this._thumbs.issuers.certs)(),
2292
- _walkAllFiles.bind(this, this.rejectedFolder, this._thumbs.rejected)(),
2293
- _walkCRLFiles.bind(this, this.crlFolder, this._thumbs.crl)(),
2294
- _walkCRLFiles.bind(this, this.issuersCrlFolder, this._thumbs.issuersCrl)()
2295
- ];
2296
- await Promise.all(promises);
2297
- await this.waitAndCheckCRLProcessingStatus();
2298
- }
2299
- // make sure that all crls have been processed.
2300
- async waitAndCheckCRLProcessingStatus() {
2301
- return new Promise((resolve, reject) => {
2302
- if (this._pending_crl_to_process === 0) {
2303
- setImmediate(resolve);
2304
- return;
2305
- }
2306
- if (this._on_crl_process) {
2307
- return reject(new Error("Internal Error"));
2308
- }
2309
- this._on_crl_process = resolve;
2310
2307
  });
2311
2308
  }
2312
- };
2313
-
2314
- // packages/node-opcua-pki/lib/ca/crypto_create_CA.ts
2315
- import commandLineArgs from "command-line-args";
2316
- import commandLineUsage from "command-line-usage";
2317
- var epilog = "Copyright (c) sterfive - node-opcua - 2017-2026";
2318
- function get_offset_date(date, nbDays) {
2319
- const d = new Date(date.getTime());
2320
- d.setDate(d.getDate() + nbDays);
2321
- return d;
2322
- }
2323
- var today = /* @__PURE__ */ new Date();
2324
- var yesterday = get_offset_date(today, -1);
2325
- var two_years_ago = get_offset_date(today, -2 * 365);
2326
- var next_year = get_offset_date(today, 365);
2327
- var gLocalConfig = {};
2328
- var g_certificateAuthority;
2329
- async function construct_CertificateAuthority2(subject) {
2330
- assert10(typeof gLocalConfig.CAFolder === "string", "expecting a CAFolder in config");
2331
- assert10(typeof gLocalConfig.keySize === "number", "expecting a keySize in config");
2332
- if (!g_certificateAuthority) {
2333
- g_certificateAuthority = new CertificateAuthority({
2334
- keySize: gLocalConfig.keySize,
2335
- location: gLocalConfig.CAFolder,
2336
- subject
2337
- });
2338
- await g_certificateAuthority.initialize();
2339
- }
2340
- }
2341
- var certificateManager;
2342
- async function construct_CertificateManager() {
2343
- assert10(typeof gLocalConfig.PKIFolder === "string", "expecting a PKIFolder in config");
2344
- if (!certificateManager) {
2345
- certificateManager = new CertificateManager({
2346
- keySize: gLocalConfig.keySize,
2347
- location: gLocalConfig.PKIFolder
2348
- });
2349
- await certificateManager.initialize();
2350
- }
2351
- }
2352
- function default_template_content() {
2353
- if (process.pkg?.entrypoint) {
2354
- const a = fs10.readFileSync(path8.join(__dirname, "../../bin/pki_config.example.js"), "utf8");
2355
- return a;
2356
- }
2357
- function find_default_config_template() {
2358
- const rootFolder = find_module_root_folder();
2359
- const configName = "pki_config.example.js";
2360
- let default_config_template2 = path8.join(rootFolder, "bin", configName);
2361
- if (!fs10.existsSync(default_config_template2)) {
2362
- default_config_template2 = path8.join(__dirname, "..", configName);
2363
- if (!fs10.existsSync(default_config_template2)) {
2364
- 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);
2365
2316
  }
2366
- }
2367
- return default_config_template2;
2368
- }
2369
- const default_config_template = find_default_config_template();
2370
- assert10(fs10.existsSync(default_config_template));
2371
- const default_config_template_content = fs10.readFileSync(default_config_template, "utf8");
2372
- return default_config_template_content;
2373
- }
2374
- function find_module_root_folder() {
2375
- let rootFolder = path8.join(__dirname);
2376
- for (let i = 0; i < 4; i++) {
2377
- if (fs10.existsSync(path8.join(rootFolder, "package.json"))) {
2378
- return rootFolder;
2379
- }
2380
- rootFolder = path8.join(rootFolder, "..");
2381
- }
2382
- assert10(fs10.existsSync(path8.join(rootFolder, "package.json")), "root folder must have a package.json file");
2383
- return rootFolder;
2384
- }
2385
- async function readConfiguration(argv) {
2386
- if (argv.silent) {
2387
- g_config.silent = true;
2388
- } else {
2389
- g_config.silent = false;
2390
- }
2391
- const fqdn2 = await extractFullyQualifiedDomainName();
2392
- const hostname = os4.hostname();
2393
- let certificateDir;
2394
- function performSubstitution(str) {
2395
- str = str.replace("{CWD}", process.cwd());
2396
- if (certificateDir) {
2397
- str = str.replace("{root}", certificateDir);
2398
- }
2399
- if (gLocalConfig?.PKIFolder) {
2400
- str = str.replace("{PKIFolder}", gLocalConfig.PKIFolder);
2401
- }
2402
- str = str.replace("{hostname}", hostname);
2403
- str = str.replace("%FQDN%", fqdn2);
2404
- return str;
2405
- }
2406
- function prepare(file) {
2407
- const tmp = path8.resolve(performSubstitution(file));
2408
- return makePath(tmp);
2409
- }
2410
- certificateDir = argv.root;
2411
- assert10(typeof certificateDir === "string");
2412
- certificateDir = prepare(certificateDir);
2413
- mkdirRecursiveSync(certificateDir);
2414
- assert10(fs10.existsSync(certificateDir));
2415
- const default_config = path8.join(certificateDir, "config.js");
2416
- if (!fs10.existsSync(default_config)) {
2417
- debugLog(chalk7.yellow(" Creating default g_config file "), chalk7.cyan(default_config));
2418
- const default_config_template_content = default_template_content();
2419
- fs10.writeFileSync(default_config, default_config_template_content);
2420
- } else {
2421
- debugLog(chalk7.yellow(" using g_config file "), chalk7.cyan(default_config));
2422
- }
2423
- if (!fs10.existsSync(default_config)) {
2424
- debugLog(chalk7.redBright(" cannot find config file ", default_config));
2425
- }
2426
- const defaultRandomFile = path8.join(path8.dirname(default_config), "random.rnd");
2427
- setEnv("RANDFILE", defaultRandomFile);
2428
- const _require = createRequire(__filename);
2429
- gLocalConfig = _require(default_config);
2430
- gLocalConfig.subject = new Subject5(gLocalConfig.subject || "");
2431
- if (argv.subject) {
2432
- gLocalConfig.subject = new Subject5(argv.subject);
2433
- }
2434
- if (!gLocalConfig.subject.commonName) {
2435
- throw new Error("subject must have a Common Name");
2436
- }
2437
- gLocalConfig.certificateDir = certificateDir;
2438
- let CAFolder = argv.CAFolder || path8.join(certificateDir, "CA");
2439
- CAFolder = prepare(CAFolder);
2440
- gLocalConfig.CAFolder = CAFolder;
2441
- gLocalConfig.PKIFolder = path8.join(gLocalConfig.certificateDir, "PKI");
2442
- if (argv.PKIFolder) {
2443
- gLocalConfig.PKIFolder = prepare(argv.PKIFolder);
2444
- }
2445
- gLocalConfig.PKIFolder = prepare(gLocalConfig.PKIFolder);
2446
- if (argv.privateKey) {
2447
- gLocalConfig.privateKey = prepare(argv.privateKey);
2448
- }
2449
- if (argv.applicationUri) {
2450
- gLocalConfig.applicationUri = performSubstitution(argv.applicationUri);
2451
- }
2452
- if (argv.output) {
2453
- gLocalConfig.outputFile = argv.output;
2454
- }
2455
- gLocalConfig.altNames = [];
2456
- if (argv.altNames) {
2457
- gLocalConfig.altNames = argv.altNames.split(";");
2458
- }
2459
- gLocalConfig.dns = [getFullyQualifiedDomainName()];
2460
- if (argv.dns) {
2461
- gLocalConfig.dns = argv.dns.split(",").map(performSubstitution);
2462
- }
2463
- gLocalConfig.ip = [];
2464
- if (argv.ip) {
2465
- gLocalConfig.ip = argv.ip.split(",");
2466
- }
2467
- if (argv.keySize) {
2468
- const v = argv.keySize;
2469
- if (v !== 1024 && v !== 2048 && v !== 3072 && v !== 4096) {
2470
- throw new Error(`invalid keysize specified ${v} should be 1024,2048,3072 or 4096`);
2471
- }
2472
- gLocalConfig.keySize = argv.keySize;
2473
- }
2474
- if (argv.validity) {
2475
- gLocalConfig.validity = argv.validity;
2476
- }
2477
- }
2478
- async function createDefaultCertificate(base_name, prefix, key_length, applicationUri, dev) {
2479
- assert10(key_length === 1024 || key_length === 2048 || key_length === 3072 || key_length === 4096);
2480
- const private_key_file = makePath(base_name, `${prefix}key_${key_length}.pem`);
2481
- const public_key_file = makePath(base_name, `${prefix}public_key_${key_length}.pub`);
2482
- const certificate_file = makePath(base_name, `${prefix}cert_${key_length}.pem`);
2483
- const certificate_file_outofdate = makePath(base_name, `${prefix}cert_${key_length}_outofdate.pem`);
2484
- const certificate_file_not_active_yet = makePath(base_name, `${prefix}cert_${key_length}_not_active_yet.pem`);
2485
- const certificate_revoked = makePath(base_name, `${prefix}cert_${key_length}_revoked.pem`);
2486
- const self_signed_certificate_file = makePath(base_name, `${prefix}selfsigned_cert_${key_length}.pem`);
2487
- const fqdn2 = getFullyQualifiedDomainName();
2488
- const hostname = os4.hostname();
2489
- const dns2 = [
2490
- // for conformance reason, localhost shall not be present in the DNS field of COP
2491
- // ***FORBIDEN** "localhost",
2492
- getFullyQualifiedDomainName()
2493
- ];
2494
- if (hostname !== fqdn2) {
2495
- dns2.push(hostname);
2496
- }
2497
- const ip = [];
2498
- async function createCertificateIfNotExist(certificate, private_key, applicationUri2, startDate, validity) {
2499
- if (fs10.existsSync(certificate)) {
2500
- warningLog(chalk7.yellow(" certificate"), chalk7.cyan(certificate), chalk7.yellow(" already exists => skipping"));
2501
- return "";
2502
- } else {
2503
- return await createCertificate(certificate, private_key, applicationUri2, startDate, validity);
2504
- }
2505
- }
2506
- async function createCertificate(certificate, privateKey, applicationUri2, startDate, validity) {
2507
- const certificateSigningRequestFile = `${certificate}.csr`;
2508
- const configFile = makePath(base_name, "../certificates/PKI/own/openssl.cnf");
2509
- const dns3 = [os4.hostname()];
2510
- const ip2 = ["127.0.0.1"];
2511
- const params = {
2512
- applicationUri: applicationUri2,
2513
- privateKey,
2514
- rootDir: ".",
2515
- configFile,
2516
- dns: dns3,
2517
- ip: ip2,
2518
- purpose: CertificatePurpose2.ForApplication
2519
- };
2520
- await createCertificateSigningRequestWithOpenSSL(certificateSigningRequestFile, params);
2521
- return await g_certificateAuthority.signCertificateRequest(certificate, certificateSigningRequestFile, {
2522
- applicationUri: applicationUri2,
2523
- dns: dns3,
2524
- ip: ip2,
2525
- startDate,
2526
- validity
2527
2317
  });
2528
- }
2529
- async function createSelfSignedCertificate2(certificate, private_key, applicationUri2, startDate, validity) {
2530
- await g_certificateAuthority.createSelfSignedCertificate(certificate, private_key, {
2531
- applicationUri: applicationUri2,
2532
- dns: dns2,
2533
- ip,
2534
- startDate,
2535
- 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
+ }
2536
2336
  });
2537
- }
2538
- async function revoke_certificate(certificate) {
2539
- await g_certificateAuthority.revokeCertificate(certificate, {});
2540
- }
2541
- async function createPrivateKeyIfNotExist(privateKey, keyLength) {
2542
- if (fs10.existsSync(privateKey)) {
2543
- warningLog(chalk7.yellow(" privateKey"), chalk7.cyan(privateKey), chalk7.yellow(" already exists => skipping"));
2544
- return;
2545
- } else {
2546
- await generatePrivateKeyFile3(privateKey, keyLength);
2547
- }
2548
- }
2549
- displaySubtitle(` create private key :${private_key_file}`);
2550
- await createPrivateKeyIfNotExist(private_key_file, key_length);
2551
- displaySubtitle(` extract public key ${public_key_file} from private key `);
2552
- await getPublicKeyFromPrivateKey(private_key_file, public_key_file);
2553
- displaySubtitle(` create Certificate ${certificate_file}`);
2554
- await createCertificateIfNotExist(certificate_file, private_key_file, applicationUri, yesterday, 365);
2555
- displaySubtitle(` create self signed Certificate ${self_signed_certificate_file}`);
2556
- if (fs10.existsSync(self_signed_certificate_file)) {
2557
- return;
2558
- }
2559
- await createSelfSignedCertificate2(self_signed_certificate_file, private_key_file, applicationUri, yesterday, 365);
2560
- if (dev) {
2561
- await createCertificateIfNotExist(certificate_file_outofdate, private_key_file, applicationUri, two_years_ago, 365);
2562
- await createCertificateIfNotExist(certificate_file_not_active_yet, private_key_file, applicationUri, next_year, 365);
2563
- if (!fs10.existsSync(certificate_revoked)) {
2564
- const certificate = await createCertificateIfNotExist(
2565
- certificate_revoked,
2566
- private_key_file,
2567
- `${applicationUri}Revoked`,
2568
- // make sure we used a uniq URI here
2569
- yesterday,
2570
- 365
2571
- );
2572
- warningLog(" certificate to revoke => ", certificate);
2573
- revoke_certificate(certificate_revoked);
2574
- }
2575
- }
2576
- }
2577
- async function wrap(func) {
2578
- try {
2579
- await func();
2580
- } catch (err) {
2581
- console.log(err.message);
2582
- }
2583
- }
2584
- async function create_default_certificates(dev) {
2585
- assert10(gLocalConfig);
2586
- const base_name = gLocalConfig.certificateDir || "";
2587
- assert10(fs10.existsSync(base_name));
2588
- let clientURN;
2589
- let serverURN;
2590
- let discoveryServerURN;
2591
- wrap(async () => {
2592
- await extractFullyQualifiedDomainName();
2593
- const hostname = os4.hostname();
2594
- const fqdn2 = getFullyQualifiedDomainName();
2595
- warningLog(chalk7.yellow(" hostname = "), chalk7.cyan(hostname));
2596
- warningLog(chalk7.yellow(" fqdn = "), chalk7.cyan(fqdn2));
2597
- clientURN = makeApplicationUrn(hostname, "NodeOPCUA-Client");
2598
- serverURN = makeApplicationUrn(hostname, "NodeOPCUA-Server");
2599
- discoveryServerURN = makeApplicationUrn(hostname, "NodeOPCUA-DiscoveryServer");
2600
- displayTitle("Create Application Certificate for Server & its private key");
2601
- await createDefaultCertificate(base_name, "client_", 1024, clientURN, dev);
2602
- await createDefaultCertificate(base_name, "client_", 2048, clientURN, dev);
2603
- await createDefaultCertificate(base_name, "client_", 3072, clientURN, dev);
2604
- await createDefaultCertificate(base_name, "client_", 4096, clientURN, dev);
2605
- displayTitle("Create Application Certificate for Client & its private key");
2606
- await createDefaultCertificate(base_name, "server_", 1024, serverURN, dev);
2607
- await createDefaultCertificate(base_name, "server_", 2048, serverURN, dev);
2608
- await createDefaultCertificate(base_name, "server_", 3072, serverURN, dev);
2609
- await createDefaultCertificate(base_name, "server_", 4096, serverURN, dev);
2610
- displayTitle("Create Application Certificate for DiscoveryServer & its private key");
2611
- await createDefaultCertificate(base_name, "discoveryServer_", 1024, discoveryServerURN, dev);
2612
- await createDefaultCertificate(base_name, "discoveryServer_", 2048, discoveryServerURN, dev);
2613
- await createDefaultCertificate(base_name, "discoveryServer_", 3072, discoveryServerURN, dev);
2614
- await createDefaultCertificate(base_name, "discoveryServer_", 4096, discoveryServerURN, dev);
2615
- });
2616
- }
2617
- async function createDefaultCertificates(dev) {
2618
- await construct_CertificateAuthority2("");
2619
- await construct_CertificateManager();
2620
- await create_default_certificates(dev);
2621
- }
2622
- var commonOptions = [
2623
- {
2624
- name: "root",
2625
- alias: "r",
2626
- type: String,
2627
- defaultValue: "{CWD}/certificates",
2628
- description: "the location of the Certificate folder"
2629
- },
2630
- {
2631
- name: "CAFolder",
2632
- alias: "c",
2633
- type: String,
2634
- defaultValue: "{root}/CA",
2635
- description: "the location of the Certificate Authority folder"
2636
- },
2637
- { name: "PKIFolder", type: String, defaultValue: "{root}/PKI", description: "the location of the Public Key Infrastructure" },
2638
- { name: "silent", type: Boolean, defaultValue: false, description: "minimize output" },
2639
- {
2640
- name: "privateKey",
2641
- alias: "p",
2642
- type: String,
2643
- defaultValue: "{PKIFolder}/own/private_key.pem",
2644
- description: "the private key to use to generate certificate"
2645
- },
2646
- {
2647
- name: "keySize",
2648
- alias: "k",
2649
- type: Number,
2650
- defaultValue: 2048,
2651
- description: "the private key size in bits (1024|2048|3072|4096)"
2652
- },
2653
- { name: "help", alias: "h", type: Boolean, description: "display this help" }
2654
- ];
2655
- function getOptions(names) {
2656
- return commonOptions.filter((o) => names.includes(o.name) || o.name === "help" || o.name === "silent");
2657
- }
2658
- function showHelp(command, description, options, usage) {
2659
- const sections = [
2660
- {
2661
- header: `Command: ${command}`,
2662
- content: description
2663
- },
2664
- {
2665
- header: "Usage",
2666
- content: usage || `$0 ${command} [options]`
2667
- },
2668
- {
2669
- header: "Options",
2670
- optionList: options
2671
- }
2672
- ];
2673
- console.log(commandLineUsage(sections));
2674
- }
2675
- async function main(argumentsList) {
2676
- const mainDefinitions = [{ name: "command", defaultOption: true }];
2677
- let mainOptions;
2678
- try {
2679
- mainOptions = commandLineArgs(mainDefinitions, { argv: argumentsList, stopAtFirstUnknown: true });
2680
- } catch (err) {
2681
- console.log(err.message);
2682
- return;
2683
- }
2684
- const argv = mainOptions._unknown || [];
2685
- const command = mainOptions.command;
2686
- if (!command || command === "help") {
2687
- console.log(
2688
- commandLineUsage([
2689
- {
2690
- header: "node-opcua-pki",
2691
- content: `PKI management for node-opcua
2692
-
2693
- ${epilog}`
2694
- },
2695
- {
2696
- header: "Commands",
2697
- content: [
2698
- { name: "demo", summary: "create default certificate for node-opcua demos" },
2699
- { name: "createCA", summary: "create a Certificate Authority" },
2700
- { name: "createPKI", summary: "create a Public Key Infrastructure" },
2701
- { name: "certificate", summary: "create a new certificate" },
2702
- { name: "revoke <certificateFile>", summary: "revoke a existing certificate" },
2703
- { name: "csr", summary: "create a certificate signing request" },
2704
- { name: "sign", summary: "validate a certificate signing request and generate a certificate" },
2705
- { name: "dump <certificateFile>", summary: "display a certificate" },
2706
- { name: "toder <pemCertificate>", summary: "convert a certificate to a DER format with finger print" },
2707
- { name: "fingerprint <certificateFile>", summary: "print the certificate fingerprint" },
2708
- { name: "version", summary: "display the version number" }
2709
- ]
2710
- }
2711
- ])
2712
- );
2713
- return;
2714
- }
2715
- if (command === "version") {
2716
- const rootFolder = find_module_root_folder();
2717
- const pkg = JSON.parse(fs10.readFileSync(path8.join(rootFolder, "package.json"), "utf-8"));
2718
- console.log(pkg.version);
2719
- return;
2720
- }
2721
- if (command === "demo") {
2722
- const optionsDef = [
2723
- ...getOptions(["root", "silent"]),
2724
- { name: "dev", type: Boolean, description: "create all sort of fancy certificates for dev testing purposes" },
2725
- { name: "clean", type: Boolean, description: "Purge existing directory [use with care!]" }
2726
- ];
2727
- const local_argv = commandLineArgs(optionsDef, { argv });
2728
- if (local_argv.help)
2729
- return showHelp(
2730
- "demo",
2731
- "create default certificate for node-opcua demos",
2732
- optionsDef,
2733
- "$0 demo [--dev] [--silent] [--clean]"
2734
- );
2735
- await wrap(async () => {
2736
- await ensure_openssl_installed();
2737
- displayChapter("Create Demo certificates");
2738
- displayTitle("reading configuration");
2739
- await readConfiguration(local_argv);
2740
- if (local_argv.clean) {
2741
- displayTitle("Cleaning old certificates");
2742
- assert10(gLocalConfig);
2743
- const certificateDir = gLocalConfig.certificateDir || "";
2744
- const files = await fs10.promises.readdir(certificateDir);
2745
- for (const file of files) {
2746
- if (file.includes(".pem") || file.includes(".pub")) {
2747
- await fs10.promises.unlink(path8.join(certificateDir, file));
2748
- }
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);
2749
2345
  }
2750
- 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);
2751
2350
  }
2752
- displayTitle("create certificates");
2753
- await createDefaultCertificates(local_argv.dev);
2754
- displayChapter("Demo certificates CREATED");
2755
- });
2756
- return;
2757
- }
2758
- if (command === "createCA") {
2759
- const optionsDef = [
2760
- ...getOptions(["root", "CAFolder", "keySize", "silent"]),
2761
- { name: "subject", type: String, defaultValue: defaultSubject, description: "the CA certificate subject" }
2762
- ];
2763
- const local_argv = commandLineArgs(optionsDef, { argv });
2764
- if (local_argv.help) return showHelp("createCA", "create a Certificate Authority", optionsDef);
2765
- await wrap(async () => {
2766
- await ensure_openssl_installed();
2767
- await readConfiguration(local_argv);
2768
- await construct_CertificateAuthority2(local_argv.subject);
2769
2351
  });
2770
- return;
2771
- }
2772
- if (command === "createPKI") {
2773
- const optionsDef = getOptions(["root", "PKIFolder", "keySize", "silent"]);
2774
- const local_argv = commandLineArgs(optionsDef, { argv });
2775
- if (local_argv.help) return showHelp("createPKI", "create a Public Key Infrastructure", optionsDef);
2776
- await wrap(async () => {
2777
- await readConfiguration(local_argv);
2778
- await construct_CertificateManager();
2779
- });
2780
- return;
2781
- }
2782
- if (command === "certificate") {
2783
- const optionsDef = [
2784
- ...getOptions(["root", "CAFolder", "PKIFolder", "privateKey", "silent"]),
2785
- {
2786
- name: "applicationUri",
2787
- alias: "a",
2788
- type: String,
2789
- defaultValue: "urn:{hostname}:Node-OPCUA-Server",
2790
- description: "the application URI"
2791
- },
2792
- {
2793
- name: "output",
2794
- alias: "o",
2795
- type: String,
2796
- defaultValue: "my_certificate.pem",
2797
- description: "the name of the generated certificate =>"
2798
- },
2799
- {
2800
- name: "selfSigned",
2801
- alias: "s",
2802
- type: Boolean,
2803
- defaultValue: false,
2804
- description: "if true, certificate will be self-signed"
2805
- },
2806
- { name: "validity", alias: "v", type: Number, description: "the certificate validity in days" },
2807
- {
2808
- name: "dns",
2809
- type: String,
2810
- defaultValue: "{hostname}",
2811
- description: "the list of valid domain name (comma separated)"
2812
- },
2813
- { name: "ip", type: String, defaultValue: "", description: "the list of valid IPs (comma separated)" },
2814
- {
2815
- name: "subject",
2816
- type: String,
2817
- defaultValue: "",
2818
- description: "the certificate subject ( for instance C=FR/ST=Centre/L=Orleans/O=SomeOrganization/CN=Hello )"
2819
- }
2820
- ];
2821
- const local_argv = commandLineArgs(optionsDef, { argv });
2822
- if (local_argv.help || !local_argv.applicationUri || !local_argv.output)
2823
- return showHelp("certificate", "create a new certificate", optionsDef);
2824
- async function command_certificate(local_argv2) {
2825
- const selfSigned = !!local_argv2.selfSigned;
2826
- if (!selfSigned) {
2827
- await command_full_certificate(local_argv2);
2828
- } else {
2829
- await command_selfsigned_certificate(local_argv2);
2830
- }
2831
- }
2832
- async function command_selfsigned_certificate(local_argv2) {
2833
- const _fqdn = await extractFullyQualifiedDomainName();
2834
- await readConfiguration(local_argv2);
2835
- await construct_CertificateManager();
2836
- displaySubtitle(` create self signed Certificate ${gLocalConfig.outputFile}`);
2837
- let subject = local_argv2.subject && local_argv2.subject.length > 1 ? new Subject5(local_argv2.subject) : gLocalConfig.subject || "";
2838
- subject = JSON.parse(JSON.stringify(subject));
2839
- const params = {
2840
- applicationUri: gLocalConfig.applicationUri || "",
2841
- dns: gLocalConfig.dns || [],
2842
- ip: gLocalConfig.ip || [],
2843
- outputFile: gLocalConfig.outputFile || "self_signed_certificate.pem",
2844
- startDate: gLocalConfig.startDate || /* @__PURE__ */ new Date(),
2845
- subject,
2846
- validity: gLocalConfig.validity || 365
2847
- };
2848
- await certificateManager.createSelfSignedCertificate(params);
2849
- }
2850
- async function command_full_certificate(local_argv2) {
2851
- await readConfiguration(local_argv2);
2852
- await construct_CertificateManager();
2853
- await construct_CertificateAuthority2("");
2854
- assert10(fs10.existsSync(gLocalConfig.CAFolder || ""), " CA folder must exist");
2855
- gLocalConfig.privateKey = void 0;
2856
- gLocalConfig.subject = local_argv2.subject && local_argv2.subject.length > 1 ? local_argv2.subject : gLocalConfig.subject;
2857
- const csr_file = await certificateManager.createCertificateRequest(
2858
- gLocalConfig
2859
- );
2860
- if (!csr_file) {
2861
- return;
2862
- }
2863
- warningLog(" csr_file = ", csr_file);
2864
- const certificate = csr_file.replace(".csr", ".pem");
2865
- if (fs10.existsSync(certificate)) {
2866
- throw new Error(` File ${certificate} already exist`);
2867
- }
2868
- await g_certificateAuthority.signCertificateRequest(
2869
- certificate,
2870
- csr_file,
2871
- gLocalConfig
2872
- );
2873
- assert10(typeof gLocalConfig.outputFile === "string");
2874
- fs10.writeFileSync(gLocalConfig.outputFile || "", fs10.readFileSync(certificate, "ascii"));
2875
- }
2876
- await wrap(async () => await command_certificate(local_argv));
2877
- return;
2878
- }
2879
- if (command === "revoke") {
2880
- const optionsDef = [{ name: "certificateFile", type: String, defaultOption: true }, ...getOptions(["root", "CAFolder"])];
2881
- const local_argv = commandLineArgs(optionsDef, { argv });
2882
- if (local_argv.help || !local_argv.certificateFile)
2883
- return showHelp(
2884
- "revoke <certificateFile>",
2885
- "revoke a existing certificate",
2886
- optionsDef,
2887
- "$0 revoke my_certificate.pem"
2888
- );
2889
- async function revoke_certificate(certificate) {
2890
- await g_certificateAuthority.revokeCertificate(certificate, {});
2891
- }
2892
- await wrap(async () => {
2893
- const certificate = path8.resolve(local_argv.certificateFile);
2894
- warningLog(chalk7.yellow(" Certificate to revoke : "), chalk7.cyan(certificate));
2895
- if (!fs10.existsSync(certificate)) {
2896
- throw new Error(`cannot find certificate to revoke ${certificate}`);
2897
- }
2898
- await readConfiguration(local_argv);
2899
- await construct_CertificateAuthority2("");
2900
- await revoke_certificate(certificate);
2901
- warningLog("done ... ");
2902
- warningLog(" crl = ", g_certificateAuthority.revocationList);
2903
- 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
+ });
2904
2360
  });
2905
- return;
2906
2361
  }
2907
- if (command === "csr") {
2908
- const optionsDef = [
2909
- ...getOptions(["root", "PKIFolder", "privateKey", "silent"]),
2910
- {
2911
- name: "applicationUri",
2912
- alias: "a",
2913
- type: String,
2914
- defaultValue: "urn:{hostname}:Node-OPCUA-Server",
2915
- description: "the application URI"
2916
- },
2917
- {
2918
- name: "output",
2919
- alias: "o",
2920
- type: String,
2921
- defaultValue: "my_certificate_signing_request.csr",
2922
- description: "the name of the generated signing_request"
2923
- },
2924
- {
2925
- name: "dns",
2926
- type: String,
2927
- defaultValue: "{hostname}",
2928
- description: "the list of valid domain name (comma separated)"
2929
- },
2930
- { name: "ip", type: String, defaultValue: "", description: "the list of valid IPs (comma separated)" },
2931
- {
2932
- name: "subject",
2933
- type: String,
2934
- defaultValue: "/CN=Certificate",
2935
- description: "the certificate subject ( for instance /C=FR/ST=Centre/L=Orleans/O=SomeOrganization/CN=Hello )"
2936
- }
2937
- ];
2938
- const local_argv = commandLineArgs(optionsDef, { argv });
2939
- if (local_argv.help) return showHelp("csr", "create a certificate signing request", optionsDef);
2940
- await wrap(async () => {
2941
- await readConfiguration(local_argv);
2942
- if (!fs10.existsSync(gLocalConfig.PKIFolder || "")) {
2943
- warningLog("PKI folder must exist");
2944
- }
2945
- await construct_CertificateManager();
2946
- if (!gLocalConfig.outputFile || fs10.existsSync(gLocalConfig.outputFile)) {
2947
- throw new Error(` File ${gLocalConfig.outputFile} already exist`);
2948
- }
2949
- gLocalConfig.privateKey = void 0;
2950
- gLocalConfig.subject = local_argv.subject && local_argv.subject.length > 1 ? local_argv.subject : gLocalConfig.subject;
2951
- const internal_csr_file = await certificateManager.createCertificateRequest(
2952
- gLocalConfig
2953
- );
2954
- if (!internal_csr_file) {
2955
- return;
2956
- }
2957
- if (!gLocalConfig.outputFile) {
2958
- 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);
2959
2367
  return;
2960
2368
  }
2961
- const csr = await fs10.promises.readFile(internal_csr_file, "utf-8");
2962
- fs10.writeFileSync(gLocalConfig.outputFile || "", csr, "utf-8");
2963
- warningLog("Subject = ", gLocalConfig.subject);
2964
- warningLog("applicationUri = ", gLocalConfig.applicationUri);
2965
- warningLog("altNames = ", gLocalConfig.altNames);
2966
- warningLog("dns = ", gLocalConfig.dns);
2967
- warningLog("ip = ", gLocalConfig.ip);
2968
- warningLog("CSR file = ", gLocalConfig.outputFile);
2969
- });
2970
- return;
2971
- }
2972
- if (command === "sign") {
2973
- const optionsDef = [
2974
- ...getOptions(["root", "CAFolder", "silent"]),
2975
- { name: "csr", alias: "i", type: String, defaultValue: "my_certificate_signing_request.csr", description: "the csr" },
2976
- {
2977
- name: "output",
2978
- alias: "o",
2979
- type: String,
2980
- defaultValue: "my_certificate.pem",
2981
- description: "the name of the generated certificate"
2982
- },
2983
- { name: "validity", alias: "v", type: Number, defaultValue: 365, description: "the certificate validity in days" }
2984
- ];
2985
- const local_argv = commandLineArgs(optionsDef, { argv });
2986
- if (local_argv.help || !local_argv.csr || !local_argv.output)
2987
- return showHelp("sign", "validate a certificate signing request and generate a certificate", optionsDef);
2988
- await wrap(async () => {
2989
- await readConfiguration(local_argv);
2990
- if (!fs10.existsSync(gLocalConfig.CAFolder || "")) {
2991
- throw new Error(`CA folder must exist:${gLocalConfig.CAFolder}`);
2992
- }
2993
- await construct_CertificateAuthority2("");
2994
- const csr_file = path8.resolve(local_argv.csr || "");
2995
- if (!fs10.existsSync(csr_file)) {
2996
- throw new Error(`Certificate signing request doesn't exist: ${csr_file}`);
2997
- }
2998
- const certificate = path8.resolve(local_argv.output || csr_file.replace(".csr", ".pem"));
2999
- if (fs10.existsSync(certificate)) {
3000
- throw new Error(` File ${certificate} already exist`);
2369
+ if (this.#onCrlProcess) {
2370
+ return reject(new Error("Internal Error"));
3001
2371
  }
3002
- await g_certificateAuthority.signCertificateRequest(
3003
- certificate,
3004
- csr_file,
3005
- gLocalConfig
3006
- );
3007
- assert10(typeof gLocalConfig.outputFile === "string");
3008
- fs10.writeFileSync(gLocalConfig.outputFile || "", fs10.readFileSync(certificate, "ascii"));
3009
- });
3010
- return;
3011
- }
3012
- if (command === "dump") {
3013
- const optionsDef = [
3014
- { name: "certificateFile", type: String, defaultOption: true },
3015
- { name: "help", alias: "h", type: Boolean }
3016
- ];
3017
- const local_argv = commandLineArgs(optionsDef, { argv });
3018
- if (local_argv.help || !local_argv.certificateFile)
3019
- return showHelp("dump <certificateFile>", "display a certificate", optionsDef);
3020
- await wrap(async () => {
3021
- const data = await dumpCertificate(local_argv.certificateFile);
3022
- warningLog(data);
2372
+ this.#onCrlProcess = resolve;
3023
2373
  });
3024
- return;
3025
- }
3026
- if (command === "toder") {
3027
- const optionsDef = [
3028
- { name: "pemCertificate", type: String, defaultOption: true },
3029
- { name: "help", alias: "h", type: Boolean }
3030
- ];
3031
- const local_argv = commandLineArgs(optionsDef, { argv });
3032
- if (local_argv.help || !local_argv.pemCertificate)
3033
- return showHelp("toder <pemCertificate>", "convert a certificate to a DER format with finger print", optionsDef);
3034
- await wrap(async () => {
3035
- await toDer(local_argv.pemCertificate);
3036
- });
3037
- return;
3038
2374
  }
3039
- if (command === "fingerprint") {
3040
- const optionsDef = [
3041
- { name: "certificateFile", type: String, defaultOption: true },
3042
- { name: "help", alias: "h", type: Boolean }
3043
- ];
3044
- const local_argv = commandLineArgs(optionsDef, { argv });
3045
- if (local_argv.help || !local_argv.certificateFile)
3046
- return showHelp("fingerprint <certificateFile>", "print the certificate fingerprint", optionsDef);
3047
- await wrap(async () => {
3048
- const certificate = local_argv.certificateFile;
3049
- const data = await fingerprint(certificate);
3050
- if (!data) return;
3051
- const s = data.split("=")[1].split(":").join("").trim();
3052
- warningLog(s);
3053
- });
3054
- return;
3055
- }
3056
- 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, {});
3057
2434
  }
3058
2435
  export {
3059
2436
  CertificateAuthority,
@@ -3063,22 +2440,15 @@ export {
3063
2440
  VerificationStatus,
3064
2441
  adjustApplicationUri,
3065
2442
  adjustDate,
3066
- certificateFileExist,
3067
- debugLog,
3068
- display,
3069
- displayChapter,
3070
- displayDebug,
3071
- displayError,
3072
- displaySubtitle,
3073
- displayTitle,
3074
- doDebug,
2443
+ convertPFXtoPEM,
2444
+ createPFX,
2445
+ dumpPFX,
2446
+ extractAllFromPFX,
2447
+ extractCACertificatesFromPFX,
2448
+ extractCertificateFromPFX,
2449
+ extractPrivateKeyFromPFX,
3075
2450
  findIssuerCertificateInChain,
3076
- g_config,
3077
2451
  install_prerequisite,
3078
- makePath,
3079
- mkdirRecursiveSync,
3080
- main as pki_main,
3081
- quote,
3082
- warningLog
2452
+ quote
3083
2453
  };
3084
2454
  //# sourceMappingURL=index.mjs.map