node-opcua-pki 6.3.0 → 6.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -37,23 +37,16 @@ __export(lib_exports, {
37
37
  VerificationStatus: () => VerificationStatus,
38
38
  adjustApplicationUri: () => adjustApplicationUri,
39
39
  adjustDate: () => adjustDate,
40
- certificateFileExist: () => certificateFileExist,
41
- debugLog: () => debugLog,
42
- display: () => display,
43
- displayChapter: () => displayChapter,
44
- displayDebug: () => displayDebug,
45
- displayError: () => displayError,
46
- displaySubtitle: () => displaySubtitle,
47
- displayTitle: () => displayTitle,
48
- doDebug: () => doDebug,
40
+ convertPFXtoPEM: () => convertPFXtoPEM,
41
+ createPFX: () => createPFX,
42
+ dumpPFX: () => dumpPFX,
43
+ extractAllFromPFX: () => extractAllFromPFX,
44
+ extractCACertificatesFromPFX: () => extractCACertificatesFromPFX,
45
+ extractCertificateFromPFX: () => extractCertificateFromPFX,
46
+ extractPrivateKeyFromPFX: () => extractPrivateKeyFromPFX,
49
47
  findIssuerCertificateInChain: () => findIssuerCertificateInChain,
50
- g_config: () => g_config,
51
48
  install_prerequisite: () => install_prerequisite,
52
- makePath: () => makePath,
53
- mkdirRecursiveSync: () => mkdirRecursiveSync,
54
- pki_main: () => main,
55
- quote: () => quote,
56
- warningLog: () => warningLog
49
+ quote: () => quote
57
50
  });
58
51
  module.exports = __toCommonJS(lib_exports);
59
52
 
@@ -142,13 +135,6 @@ function makePath(folderName, filename) {
142
135
 
143
136
  // packages/node-opcua-pki/lib/toolbox/display.ts
144
137
  var import_chalk2 = __toESM(require("chalk"));
145
- function displayChapter(str) {
146
- const l = " ";
147
- warningLog(`${import_chalk2.default.bgWhite(l)} `);
148
- str = ` ${str}${l}`.substring(0, l.length);
149
- warningLog(import_chalk2.default.bgWhite.cyan(str));
150
- warningLog(`${import_chalk2.default.bgWhite(l)} `);
151
- }
152
138
  function displayTitle(str) {
153
139
  if (!g_config.silent) {
154
140
  warningLog("");
@@ -647,12 +633,6 @@ function generateStaticConfig(configPath, options) {
647
633
  return temporaryConfigPath;
648
634
  }
649
635
  }
650
- var q = quote;
651
- var n2 = makePath;
652
- async function getPublicKeyFromPrivateKey(privateKeyFilename, publicKeyFilename) {
653
- (0, import_node_assert4.default)(import_node_fs4.default.existsSync(privateKeyFilename));
654
- await execute_openssl(`rsa -pubout -in ${q(n2(privateKeyFilename))} -out ${q(n2(publicKeyFilename))}`, {});
655
- }
656
636
  function x509Date(date) {
657
637
  date = date || /* @__PURE__ */ new Date();
658
638
  const Y = date.getUTCFullYear();
@@ -670,45 +650,6 @@ function x509Date(date) {
670
650
  return `${w(Y, 4) + w(M, 2) + w(D, 2) + w(h, 2) + w(m, 2) + w(s, 2)}Z`;
671
651
  }
672
652
  }
673
- async function dumpCertificate(certificate) {
674
- (0, import_node_assert4.default)(import_node_fs4.default.existsSync(certificate));
675
- return await execute_openssl(`x509 -in ${q(n2(certificate))} -text -noout`, {});
676
- }
677
- async function toDer(certificatePem) {
678
- (0, import_node_assert4.default)(import_node_fs4.default.existsSync(certificatePem));
679
- const certificateDer = certificatePem.replace(".pem", ".der");
680
- return await execute_openssl(`x509 -outform der -in ${certificatePem} -out ${certificateDer}`, {});
681
- }
682
- async function fingerprint(certificatePem) {
683
- (0, import_node_assert4.default)(import_node_fs4.default.existsSync(certificatePem));
684
- return await execute_openssl(`x509 -fingerprint -noout -in ${certificatePem}`, {});
685
- }
686
-
687
- // packages/node-opcua-pki/lib/toolbox/with_openssl/create_certificate_signing_request.ts
688
- var q2 = quote;
689
- var n3 = makePath;
690
- async function createCertificateSigningRequestWithOpenSSL(certificateSigningRequestFilename, params) {
691
- (0, import_node_assert5.default)(params);
692
- (0, import_node_assert5.default)(params.rootDir);
693
- (0, import_node_assert5.default)(params.configFile);
694
- (0, import_node_assert5.default)(params.privateKey);
695
- (0, import_node_assert5.default)(typeof params.privateKey === "string");
696
- (0, import_node_assert5.default)(import_node_fs5.default.existsSync(params.configFile), `config file must exist ${params.configFile}`);
697
- (0, import_node_assert5.default)(import_node_fs5.default.existsSync(params.privateKey), `Private key must exist${params.privateKey}`);
698
- (0, import_node_assert5.default)(import_node_fs5.default.existsSync(params.rootDir), "RootDir key must exist");
699
- (0, import_node_assert5.default)(typeof certificateSigningRequestFilename === "string");
700
- processAltNames(params);
701
- const configFile = generateStaticConfig(params.configFile, { cwd: params.rootDir });
702
- const options = { cwd: params.rootDir, openssl_conf: import_node_path4.default.relative(params.rootDir, configFile) };
703
- const configOption = ` -config ${q2(n3(configFile))}`;
704
- const subject = params.subject ? new import_node_opcua_crypto.Subject(params.subject).toString() : void 0;
705
- const subjectOptions = subject ? ` -subj "${subject}"` : "";
706
- displaySubtitle("- Creating a Certificate Signing Request with openssl");
707
- await execute_openssl(
708
- "req -new -sha256 -batch -text " + configOption + " -key " + q2(n3(params.privateKey)) + subjectOptions + " -out " + q2(n3(certificateSigningRequestFilename)),
709
- options
710
- );
711
- }
712
653
 
713
654
  // packages/node-opcua-pki/lib/ca/templates/ca_config_template.cnf.ts
714
655
  var config = `#.........DO NOT MODIFY BY HAND .........................
@@ -846,8 +787,8 @@ var config2 = {
846
787
  forceCA: false,
847
788
  pkiDir: "INVALID"
848
789
  };
849
- var n4 = makePath;
850
- var q3 = quote;
790
+ var n2 = makePath;
791
+ var q = quote;
851
792
  function octetStringToIpAddress(a) {
852
793
  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();
853
794
  }
@@ -898,7 +839,7 @@ async function construct_CertificateAuthority(certificateAuthority) {
898
839
  processAltNames({});
899
840
  const options = { cwd: caRootDir };
900
841
  const configFile = generateStaticConfig("conf/caconfig.cnf", options);
901
- const configOption = ` -config ${q3(n4(configFile))}`;
842
+ const configOption = ` -config ${q(n2(configFile))}`;
902
843
  const keySize = certificateAuthority.keySize;
903
844
  const privateKeyFilename = import_node_path5.default.join(caRootDir, "private/cakey.pem");
904
845
  const csrFilename = import_node_path5.default.join(caRootDir, "private/cakey.csr");
@@ -906,12 +847,12 @@ async function construct_CertificateAuthority(certificateAuthority) {
906
847
  await (0, import_node_opcua_crypto2.generatePrivateKeyFile)(privateKeyFilename, keySize);
907
848
  displayTitle("Generate a certificate request for the CA key");
908
849
  await execute_openssl(
909
- "req -new -sha256 -text -extensions v3_ca" + configOption + " -key " + q3(n4(privateKeyFilename)) + " -out " + q3(n4(csrFilename)) + " " + subjectOpt,
850
+ "req -new -sha256 -text -extensions v3_ca" + configOption + " -key " + q(n2(privateKeyFilename)) + " -out " + q(n2(csrFilename)) + " " + subjectOpt,
910
851
  options
911
852
  );
912
853
  displayTitle("Generate CA Certificate (self-signed)");
913
854
  await execute_openssl(
914
- " 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",
855
+ " 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",
915
856
  options
916
857
  );
917
858
  displaySubtitle("generate initial CRL (Certificate Revocation List)");
@@ -923,11 +864,14 @@ async function regenerateCrl(revocationList, configOption, options) {
923
864
  await execute_openssl(`ca -gencrl ${configOption} -out crl/revocation_list.crl`, options);
924
865
  await execute_openssl("crl -in crl/revocation_list.crl -out crl/revocation_list.der -outform der", options);
925
866
  displaySubtitle("Display (Certificate Revocation List)");
926
- await execute_openssl(`crl -in ${q3(n4(revocationList))} -text -noout`, options);
867
+ await execute_openssl(`crl -in ${q(n2(revocationList))} -text -noout`, options);
927
868
  }
928
869
  var CertificateAuthority = class {
870
+ /** RSA key size used when generating the CA private key. */
929
871
  keySize;
872
+ /** Root filesystem path of the CA directory structure. */
930
873
  location;
874
+ /** X.500 subject of the CA certificate. */
931
875
  subject;
932
876
  constructor(options) {
933
877
  (0, import_node_assert6.default)(Object.prototype.hasOwnProperty.call(options, "location"));
@@ -936,33 +880,54 @@ var CertificateAuthority = class {
936
880
  this.keySize = options.keySize || 2048;
937
881
  this.subject = new import_node_opcua_crypto2.Subject(options.subject || defaultSubject);
938
882
  }
883
+ /** Absolute path to the CA root directory (alias for {@link location}). */
939
884
  get rootDir() {
940
885
  return this.location;
941
886
  }
887
+ /** Path to the OpenSSL configuration file (`conf/caconfig.cnf`). */
942
888
  get configFile() {
943
889
  return import_node_path5.default.normalize(import_node_path5.default.join(this.rootDir, "./conf/caconfig.cnf"));
944
890
  }
891
+ /** Path to the CA certificate in PEM format (`public/cacert.pem`). */
945
892
  get caCertificate() {
946
893
  return makePath(this.rootDir, "./public/cacert.pem");
947
894
  }
948
895
  /**
949
- * the file name where the current Certificate Revocation List is stored (in DER format)
896
+ * Path to the current Certificate Revocation List in DER format.
897
+ * (`crl/revocation_list.der`)
950
898
  */
951
899
  get revocationListDER() {
952
900
  return makePath(this.rootDir, "./crl/revocation_list.der");
953
901
  }
954
902
  /**
955
- * the file name where the current Certificate Revocation List is stored (in PEM format)
903
+ * Path to the current Certificate Revocation List in PEM format.
904
+ * (`crl/revocation_list.crl`)
956
905
  */
957
906
  get revocationList() {
958
907
  return makePath(this.rootDir, "./crl/revocation_list.crl");
959
908
  }
909
+ /**
910
+ * Path to the concatenated CA certificate + CRL file.
911
+ * Used by OpenSSL for CRL-based verification.
912
+ */
960
913
  get caCertificateWithCrl() {
961
914
  return makePath(this.rootDir, "./public/cacertificate_with_crl.pem");
962
915
  }
916
+ /**
917
+ * Initialize the CA directory structure, generate the CA
918
+ * private key and self-signed certificate if they do not
919
+ * already exist.
920
+ */
963
921
  async initialize() {
964
922
  await construct_CertificateAuthority(this);
965
923
  }
924
+ /**
925
+ * Rebuild the combined CA certificate + CRL file.
926
+ *
927
+ * This concatenates the CA certificate with the current
928
+ * revocation list so that OpenSSL can verify certificates
929
+ * with CRL checking enabled.
930
+ */
966
931
  async constructCACertificateWithCRL() {
967
932
  const cacertWithCRL = this.caCertificateWithCrl;
968
933
  if (import_node_fs6.default.existsSync(this.revocationList)) {
@@ -974,6 +939,12 @@ var CertificateAuthority = class {
974
939
  await import_node_fs6.default.promises.writeFile(cacertWithCRL, import_node_fs6.default.readFileSync(this.caCertificate));
975
940
  }
976
941
  }
942
+ /**
943
+ * Append the CA certificate to a signed certificate file,
944
+ * creating a PEM certificate chain.
945
+ *
946
+ * @param certificate - path to the certificate file to extend
947
+ */
977
948
  async constructCertificateChain(certificate) {
978
949
  (0, import_node_assert6.default)(import_node_fs6.default.existsSync(certificate));
979
950
  (0, import_node_assert6.default)(import_node_fs6.default.existsSync(this.caCertificate));
@@ -984,6 +955,13 @@ var CertificateAuthority = class {
984
955
  // + fs.readFileSync(this.revocationList)
985
956
  );
986
957
  }
958
+ /**
959
+ * Create a self-signed certificate using OpenSSL.
960
+ *
961
+ * @param certificateFile - output path for the signed certificate
962
+ * @param privateKey - path to the private key file
963
+ * @param params - certificate parameters (subject, validity, SANs)
964
+ */
987
965
  async createSelfSignedCertificate(certificateFile, privateKey, params) {
988
966
  (0, import_node_assert6.default)(typeof privateKey === "string");
989
967
  (0, import_node_assert6.default)(import_node_fs6.default.existsSync(privateKey));
@@ -1005,28 +983,27 @@ var CertificateAuthority = class {
1005
983
  const subjectOptions = subject && subject.length > 1 ? ` -subj ${subject} ` : "";
1006
984
  displaySubtitle("- the certificate signing request");
1007
985
  await execute_openssl(
1008
- "req -new -sha256 -text " + configOption + subjectOptions + " -batch -key " + q3(n4(privateKey)) + " -out " + q3(n4(csrFile)),
986
+ "req -new -sha256 -text " + configOption + subjectOptions + " -batch -key " + q(n2(privateKey)) + " -out " + q(n2(csrFile)),
1009
987
  options
1010
988
  );
1011
989
  displaySubtitle("- creating the self-signed certificate");
1012
990
  await execute_openssl(
1013
- "ca -selfsign -keyfile " + q3(n4(privateKey)) + " -startdate " + x509Date(params.startDate) + " -enddate " + x509Date(params.endDate) + " -batch -out " + q3(n4(certificateFile)) + " -in " + q3(n4(csrFile)),
991
+ "ca -selfsign -keyfile " + q(n2(privateKey)) + " -startdate " + x509Date(params.startDate) + " -enddate " + x509Date(params.endDate) + " -batch -out " + q(n2(certificateFile)) + " -in " + q(n2(csrFile)),
1014
992
  options
1015
993
  );
1016
994
  displaySubtitle("- dump the certificate for a check");
1017
- await execute_openssl(`x509 -in ${q3(n4(certificateFile))} -dates -fingerprint -purpose -noout`, {});
995
+ await execute_openssl(`x509 -in ${q(n2(certificateFile))} -dates -fingerprint -purpose -noout`, {});
1018
996
  displaySubtitle("- verify self-signed certificate");
1019
- await execute_openssl_no_failure(`verify -verbose -CAfile ${q3(n4(certificateFile))} ${q3(n4(certificateFile))}`, options);
997
+ await execute_openssl_no_failure(`verify -verbose -CAfile ${q(n2(certificateFile))} ${q(n2(certificateFile))}`, options);
1020
998
  await import_node_fs6.default.promises.unlink(csrFile);
1021
999
  }
1022
1000
  /**
1023
- * revoke a certificate and update the CRL
1001
+ * Revoke a certificate and regenerate the CRL.
1024
1002
  *
1025
- * @method revokeCertificate
1026
- * @param certificate - the certificate to revoke
1027
- * @param params
1028
- * @param [params.reason = "keyCompromise" {String}]
1029
- * @async
1003
+ * @param certificate - path to the certificate file to revoke
1004
+ * @param params - revocation parameters
1005
+ * @param params.reason - CRL reason code
1006
+ * (default `"keyCompromise"`)
1030
1007
  */
1031
1008
  async revokeCertificate(certificate, params) {
1032
1009
  const crlReasons = [
@@ -1047,31 +1024,34 @@ var CertificateAuthority = class {
1047
1024
  setEnv("ALTNAME", "");
1048
1025
  const randomFile = import_node_path5.default.join(this.rootDir, "random.rnd");
1049
1026
  setEnv("RANDFILE", randomFile);
1050
- const configOption = ` -config ${q3(n4(configFile))}`;
1027
+ const configOption = ` -config ${q(n2(configFile))}`;
1051
1028
  const reason = params.reason || "keyCompromise";
1052
1029
  (0, import_node_assert6.default)(crlReasons.indexOf(reason) >= 0);
1053
1030
  displayTitle(`Revoking certificate ${certificate}`);
1054
1031
  displaySubtitle("Revoke certificate");
1055
- await execute_openssl_no_failure(`ca -verbose ${configOption} -revoke ${q3(certificate)} -crl_reason ${reason}`, options);
1032
+ await execute_openssl_no_failure(`ca -verbose ${configOption} -revoke ${q(certificate)} -crl_reason ${reason}`, options);
1056
1033
  await regenerateCrl(this.revocationList, configOption, options);
1057
1034
  displaySubtitle("Verify that certificate is revoked");
1058
1035
  await execute_openssl_no_failure(
1059
- "verify -verbose -CRLfile " + q3(n4(this.revocationList)) + " -CAfile " + q3(n4(this.caCertificate)) + " -crl_check " + q3(n4(certificate)),
1036
+ "verify -verbose -CRLfile " + q(n2(this.revocationList)) + " -CAfile " + q(n2(this.caCertificate)) + " -crl_check " + q(n2(certificate)),
1060
1037
  options
1061
1038
  );
1062
1039
  displaySubtitle("Produce CRL in DER form ");
1063
- await execute_openssl(`crl -in ${q3(n4(this.revocationList))} -out crl/revocation_list.der -outform der`, options);
1040
+ await execute_openssl(`crl -in ${q(n2(this.revocationList))} -out crl/revocation_list.der -outform der`, options);
1064
1041
  displaySubtitle("Produce CRL in PEM form ");
1065
- await execute_openssl(`crl -in ${q3(n4(this.revocationList))} -out crl/revocation_list.pem -outform pem -text `, options);
1042
+ await execute_openssl(`crl -in ${q(n2(this.revocationList))} -out crl/revocation_list.pem -outform pem -text `, options);
1066
1043
  }
1067
1044
  /**
1045
+ * Sign a Certificate Signing Request (CSR) with this CA.
1046
+ *
1047
+ * The signed certificate is written to `certificate`, and the
1048
+ * CA certificate chain plus CRL are appended to form a
1049
+ * complete certificate chain.
1068
1050
  *
1069
- * @param certificate - the certificate filename to generate
1070
- * @param certificateSigningRequestFilename - the certificate signing request
1071
- * @param params - parameters
1072
- * @param params.applicationUri - the applicationUri
1073
- * @param params.startDate - startDate of the certificate
1074
- * @param params.validity - number of day of validity of the certificate
1051
+ * @param certificate - output path for the signed certificate
1052
+ * @param certificateSigningRequestFilename - path to the CSR
1053
+ * @param params1 - signing parameters (validity, dates, SANs)
1054
+ * @returns the path to the signed certificate
1075
1055
  */
1076
1056
  async signCertificateRequest(certificate, certificateSigningRequestFilename, params1) {
1077
1057
  await ensure_openssl_installed();
@@ -1089,12 +1069,12 @@ var CertificateAuthority = class {
1089
1069
  if (typeof applicationUri !== "string") {
1090
1070
  throw new Error("Cannot find applicationUri in CSR");
1091
1071
  }
1092
- const dns2 = csrInfo.extensionRequest.subjectAltName.dNSName || [];
1072
+ const dns = csrInfo.extensionRequest.subjectAltName.dNSName || [];
1093
1073
  let ip = csrInfo.extensionRequest.subjectAltName.iPAddress || [];
1094
1074
  ip = ip.map(octetStringToIpAddress);
1095
1075
  const params = {
1096
1076
  applicationUri,
1097
- dns: dns2,
1077
+ dns,
1098
1078
  ip
1099
1079
  };
1100
1080
  processAltNames(params);
@@ -1102,11 +1082,11 @@ var CertificateAuthority = class {
1102
1082
  displaySubtitle("- then we ask the authority to sign the certificate signing request");
1103
1083
  const configOption = ` -config ${configFile}`;
1104
1084
  await execute_openssl(
1105
- "ca " + configOption + " -startdate " + x509Date(params1.startDate) + " -enddate " + x509Date(params1.endDate) + " -batch -out " + q3(n4(certificate)) + " -in " + q3(n4(certificateSigningRequestFilename)),
1085
+ "ca " + configOption + " -startdate " + x509Date(params1.startDate) + " -enddate " + x509Date(params1.endDate) + " -batch -out " + q(n2(certificate)) + " -in " + q(n2(certificateSigningRequestFilename)),
1106
1086
  options
1107
1087
  );
1108
1088
  displaySubtitle("- dump the certificate for a check");
1109
- await execute_openssl(`x509 -in ${q3(n4(certificate))} -dates -fingerprint -purpose -noout`, options);
1089
+ await execute_openssl(`x509 -in ${q(n2(certificate))} -dates -fingerprint -purpose -noout`, options);
1110
1090
  displaySubtitle("- construct CA certificate with CRL");
1111
1091
  await this.constructCACertificateWithCRL();
1112
1092
  displaySubtitle("- construct certificate chain");
@@ -1115,6 +1095,11 @@ var CertificateAuthority = class {
1115
1095
  await this.verifyCertificate(certificate);
1116
1096
  return certificate;
1117
1097
  }
1098
+ /**
1099
+ * Verify a certificate against this CA.
1100
+ *
1101
+ * @param certificate - path to the certificate file to verify
1102
+ */
1118
1103
  async verifyCertificate(certificate) {
1119
1104
  const isImplemented = false;
1120
1105
  if (isImplemented) {
@@ -1124,96 +1109,14 @@ var CertificateAuthority = class {
1124
1109
  const _configOption = ` -config ${configFile}`;
1125
1110
  _configOption;
1126
1111
  await execute_openssl_no_failure(
1127
- `verify -verbose -CAfile ${q3(n4(this.caCertificateWithCrl))} ${q3(n4(certificate))}`,
1112
+ `verify -verbose -CAfile ${q(n2(this.caCertificateWithCrl))} ${q(n2(certificate))}`,
1128
1113
  options
1129
1114
  );
1130
1115
  }
1131
1116
  }
1132
1117
  };
1133
1118
 
1134
- // packages/node-opcua-pki/lib/ca/crypto_create_CA.ts
1135
- var import_node_assert11 = __toESM(require("assert"));
1136
- var import_node_fs10 = __toESM(require("fs"));
1137
- var import_node_module = require("module");
1138
- var import_node_os4 = __toESM(require("os"));
1139
- var import_node_path7 = __toESM(require("path"));
1140
- var import_chalk7 = __toESM(require("chalk"));
1141
- var import_node_opcua_crypto6 = require("node-opcua-crypto");
1142
-
1143
- // packages/node-opcua-pki/lib/misc/applicationurn.ts
1144
- var import_node_assert7 = __toESM(require("assert"));
1145
- var import_node_crypto = require("crypto");
1146
- function makeApplicationUrn(hostname, suffix) {
1147
- let hostnameHash = hostname;
1148
- if (hostnameHash.length + 7 + suffix.length >= 64) {
1149
- hostnameHash = (0, import_node_crypto.createHash)("md5").update(hostname).digest("hex").substring(0, 16);
1150
- }
1151
- const applicationUrn = `urn:${hostnameHash}:${suffix}`;
1152
- (0, import_node_assert7.default)(applicationUrn.length <= 64);
1153
- return applicationUrn;
1154
- }
1155
-
1156
- // packages/node-opcua-pki/lib/misc/hostname.ts
1157
- var import_node_dns = __toESM(require("dns"));
1158
- var import_node_os3 = __toESM(require("os"));
1159
- var import_node_util = require("util");
1160
- function trim(str, length) {
1161
- if (!length) {
1162
- return str;
1163
- }
1164
- return str.substring(0, Math.min(str.length, length));
1165
- }
1166
- function fqdn(callback) {
1167
- const uqdn = import_node_os3.default.hostname();
1168
- import_node_dns.default.lookup(uqdn, { hints: import_node_dns.default.ADDRCONFIG }, (err1, ip) => {
1169
- if (err1) {
1170
- return callback(err1);
1171
- }
1172
- import_node_dns.default.lookupService(ip, 0, (err2, _fqdn) => {
1173
- if (err2) {
1174
- return callback(err2);
1175
- }
1176
- _fqdn = _fqdn.replace(".localdomain", "");
1177
- callback(null, _fqdn);
1178
- });
1179
- });
1180
- }
1181
- var _fullyQualifiedDomainNameInCache;
1182
- async function extractFullyQualifiedDomainName() {
1183
- if (_fullyQualifiedDomainNameInCache) {
1184
- return _fullyQualifiedDomainNameInCache;
1185
- }
1186
- if (process.platform === "win32") {
1187
- const env = process.env;
1188
- _fullyQualifiedDomainNameInCache = env.COMPUTERNAME + (env.USERDNSDOMAIN && env.USERDNSDOMAIN?.length > 0 ? `.${env.USERDNSDOMAIN}` : "");
1189
- } else {
1190
- try {
1191
- _fullyQualifiedDomainNameInCache = await (0, import_node_util.promisify)(fqdn)();
1192
- if (_fullyQualifiedDomainNameInCache === "localhost") {
1193
- throw new Error("localhost not expected");
1194
- }
1195
- if (/sethostname/.test(_fullyQualifiedDomainNameInCache)) {
1196
- throw new Error("Detecting fqdn on windows !!!");
1197
- }
1198
- } catch (_err) {
1199
- _fullyQualifiedDomainNameInCache = import_node_os3.default.hostname();
1200
- }
1201
- }
1202
- return _fullyQualifiedDomainNameInCache;
1203
- }
1204
- async function prepareFQDN() {
1205
- _fullyQualifiedDomainNameInCache = await extractFullyQualifiedDomainName();
1206
- }
1207
- function getFullyQualifiedDomainName(optional_max_length) {
1208
- if (!_fullyQualifiedDomainNameInCache) {
1209
- throw new Error("FullyQualifiedDomainName computation is not completed yet");
1210
- }
1211
- return _fullyQualifiedDomainNameInCache ? trim(_fullyQualifiedDomainNameInCache, optional_max_length) : "%FQDN%";
1212
- }
1213
- prepareFQDN();
1214
-
1215
1119
  // packages/node-opcua-pki/lib/pki/certificate_manager.ts
1216
- var import_node_assert10 = __toESM(require("assert"));
1217
1120
  var import_node_fs9 = __toESM(require("fs"));
1218
1121
  var import_node_path6 = __toESM(require("path"));
1219
1122
  var import_global_mutex = require("@ster5/global-mutex");
@@ -1222,18 +1125,18 @@ var import_chokidar = __toESM(require("chokidar"));
1222
1125
  var import_node_opcua_crypto5 = require("node-opcua-crypto");
1223
1126
 
1224
1127
  // packages/node-opcua-pki/lib/toolbox/without_openssl/create_certificate_signing_request.ts
1225
- var import_node_assert8 = __toESM(require("assert"));
1128
+ var import_node_assert7 = __toESM(require("assert"));
1226
1129
  var import_node_fs7 = __toESM(require("fs"));
1227
1130
  var import_node_opcua_crypto3 = require("node-opcua-crypto");
1228
1131
  async function createCertificateSigningRequestAsync(certificateSigningRequestFilename, params) {
1229
- (0, import_node_assert8.default)(params);
1230
- (0, import_node_assert8.default)(params.rootDir);
1231
- (0, import_node_assert8.default)(params.configFile);
1232
- (0, import_node_assert8.default)(params.privateKey);
1233
- (0, import_node_assert8.default)(typeof params.privateKey === "string");
1234
- (0, import_node_assert8.default)(import_node_fs7.default.existsSync(params.privateKey), `Private key must exist${params.privateKey}`);
1235
- (0, import_node_assert8.default)(import_node_fs7.default.existsSync(params.rootDir), "RootDir key must exist");
1236
- (0, import_node_assert8.default)(typeof certificateSigningRequestFilename === "string");
1132
+ (0, import_node_assert7.default)(params);
1133
+ (0, import_node_assert7.default)(params.rootDir);
1134
+ (0, import_node_assert7.default)(params.configFile);
1135
+ (0, import_node_assert7.default)(params.privateKey);
1136
+ (0, import_node_assert7.default)(typeof params.privateKey === "string");
1137
+ (0, import_node_assert7.default)(import_node_fs7.default.existsSync(params.privateKey), `Private key must exist${params.privateKey}`);
1138
+ (0, import_node_assert7.default)(import_node_fs7.default.existsSync(params.rootDir), "RootDir key must exist");
1139
+ (0, import_node_assert7.default)(typeof certificateSigningRequestFilename === "string");
1237
1140
  const subject = params.subject ? new import_node_opcua_crypto3.Subject(params.subject).toString() : void 0;
1238
1141
  displaySubtitle("- Creating a Certificate Signing Request with subtile");
1239
1142
  const privateKeyPem = await import_node_fs7.default.promises.readFile(params.privateKey, "utf-8");
@@ -1252,22 +1155,22 @@ async function createCertificateSigningRequestAsync(certificateSigningRequestFil
1252
1155
  }
1253
1156
 
1254
1157
  // packages/node-opcua-pki/lib/toolbox/without_openssl/create_self_signed_certificate.ts
1255
- var import_node_assert9 = __toESM(require("assert"));
1158
+ var import_node_assert8 = __toESM(require("assert"));
1256
1159
  var import_node_fs8 = __toESM(require("fs"));
1257
1160
  var import_node_opcua_crypto4 = require("node-opcua-crypto");
1258
1161
  async function createSelfSignedCertificateAsync(certificate, params) {
1259
1162
  params.purpose = params.purpose || import_node_opcua_crypto4.CertificatePurpose.ForApplication;
1260
- (0, import_node_assert9.default)(params.purpose, "Please provide a Certificate Purpose");
1261
- (0, import_node_assert9.default)(import_node_fs8.default.existsSync(params.configFile));
1262
- (0, import_node_assert9.default)(import_node_fs8.default.existsSync(params.rootDir));
1263
- (0, import_node_assert9.default)(import_node_fs8.default.existsSync(params.privateKey));
1163
+ (0, import_node_assert8.default)(params.purpose, "Please provide a Certificate Purpose");
1164
+ (0, import_node_assert8.default)(import_node_fs8.default.existsSync(params.configFile));
1165
+ (0, import_node_assert8.default)(import_node_fs8.default.existsSync(params.rootDir));
1166
+ (0, import_node_assert8.default)(import_node_fs8.default.existsSync(params.privateKey));
1264
1167
  if (!params.subject) {
1265
1168
  throw Error("Missing subject");
1266
1169
  }
1267
- (0, import_node_assert9.default)(typeof params.applicationUri === "string");
1268
- (0, import_node_assert9.default)(Array.isArray(params.dns));
1170
+ (0, import_node_assert8.default)(typeof params.applicationUri === "string");
1171
+ (0, import_node_assert8.default)(Array.isArray(params.dns));
1269
1172
  adjustDate(params);
1270
- (0, import_node_assert9.default)(Object.prototype.hasOwnProperty.call(params, "validity"));
1173
+ (0, import_node_assert8.default)(Object.prototype.hasOwnProperty.call(params, "validity"));
1271
1174
  let subject = new import_node_opcua_crypto4.Subject(params.subject);
1272
1175
  subject = subject.toString();
1273
1176
  const purpose = params.purpose;
@@ -1298,6 +1201,12 @@ var simple_config_template_cnf_default = config3;
1298
1201
  // packages/node-opcua-pki/lib/pki/certificate_manager.ts
1299
1202
  var configurationFileSimpleTemplate = simple_config_template_cnf_default;
1300
1203
  var fsWriteFile = import_node_fs9.default.promises.writeFile;
1204
+ function getOrComputeInfo(entry) {
1205
+ if (!entry.info) {
1206
+ entry.info = (0, import_node_opcua_crypto5.exploreCertificate)(entry.certificate);
1207
+ }
1208
+ return entry.info;
1209
+ }
1301
1210
  var VerificationStatus = /* @__PURE__ */ ((VerificationStatus2) => {
1302
1211
  VerificationStatus2["BadCertificateInvalid"] = "BadCertificateInvalid";
1303
1212
  VerificationStatus2["BadSecurityChecksFailed"] = "BadSecurityChecksFailed";
@@ -1318,28 +1227,28 @@ var VerificationStatus = /* @__PURE__ */ ((VerificationStatus2) => {
1318
1227
  return VerificationStatus2;
1319
1228
  })(VerificationStatus || {});
1320
1229
  function makeFingerprint(certificate) {
1321
- return (0, import_node_opcua_crypto5.makeSHA1Thumbprint)(certificate).toString("hex");
1230
+ const chain = (0, import_node_opcua_crypto5.split_der)(certificate);
1231
+ return (0, import_node_opcua_crypto5.makeSHA1Thumbprint)(chain[0]).toString("hex");
1322
1232
  }
1323
1233
  function short(stringToShorten) {
1324
1234
  return stringToShorten.substring(0, 10);
1325
1235
  }
1326
1236
  var forbiddenChars = /[\x00-\x1F<>:"/\\|?*]/g;
1327
1237
  function buildIdealCertificateName(certificate) {
1328
- const fingerprint2 = makeFingerprint(certificate);
1238
+ const fingerprint = makeFingerprint(certificate);
1329
1239
  try {
1330
1240
  const commonName = (0, import_node_opcua_crypto5.exploreCertificate)(certificate).tbsCertificate.subject.commonName || "";
1331
1241
  const sanitizedCommonName = commonName.replace(forbiddenChars, "_");
1332
- return `${sanitizedCommonName}[${fingerprint2}]`;
1242
+ return `${sanitizedCommonName}[${fingerprint}]`;
1333
1243
  } catch (_err) {
1334
- return `invalid_certificate_[${fingerprint2}]`;
1244
+ return `invalid_certificate_[${fingerprint}]`;
1335
1245
  }
1336
1246
  }
1337
1247
  function findMatchingIssuerKey(entries, wantedIssuerKey) {
1338
- const selected = entries.filter(({ certificate }) => {
1339
- const info = (0, import_node_opcua_crypto5.exploreCertificate)(certificate);
1248
+ return entries.filter((entry) => {
1249
+ const info = getOrComputeInfo(entry);
1340
1250
  return info.tbsCertificate.extensions && info.tbsCertificate.extensions.subjectKeyIdentifier === wantedIssuerKey;
1341
1251
  });
1342
- return selected;
1343
1252
  }
1344
1253
  function isSelfSigned2(info) {
1345
1254
  return info.tbsCertificate.extensions?.subjectKeyIdentifier === info.tbsCertificate.extensions?.authorityKeyIdentifier?.keyIdentifier;
@@ -1382,84 +1291,157 @@ var CertificateManagerState = /* @__PURE__ */ ((CertificateManagerState2) => {
1382
1291
  CertificateManagerState2[CertificateManagerState2["Disposed"] = 4] = "Disposed";
1383
1292
  return CertificateManagerState2;
1384
1293
  })(CertificateManagerState || {});
1385
- var CertificateManager = class {
1294
+ var CertificateManager = class _CertificateManager {
1295
+ // ── Global instance registry ─────────────────────────────────
1296
+ // Tracks all initialized CertificateManager instances so their
1297
+ // file watchers can be closed automatically on process exit,
1298
+ // even if the consumer forgets to call dispose().
1299
+ static #activeInstances = /* @__PURE__ */ new Set();
1300
+ static #cleanupInstalled = false;
1301
+ static #installProcessCleanup() {
1302
+ if (_CertificateManager.#cleanupInstalled) return;
1303
+ _CertificateManager.#cleanupInstalled = true;
1304
+ const closeDanglingWatchers = () => {
1305
+ for (const cm of _CertificateManager.#activeInstances) {
1306
+ for (const w of cm.#watchers) {
1307
+ try {
1308
+ w.close();
1309
+ } catch {
1310
+ }
1311
+ }
1312
+ cm.#watchers.splice(0);
1313
+ cm.state = 4 /* Disposed */;
1314
+ }
1315
+ _CertificateManager.#activeInstances.clear();
1316
+ };
1317
+ process.on("beforeExit", closeDanglingWatchers);
1318
+ for (const signal of ["SIGINT", "SIGTERM"]) {
1319
+ process.once(signal, () => {
1320
+ closeDanglingWatchers();
1321
+ process.exit();
1322
+ });
1323
+ }
1324
+ }
1325
+ /**
1326
+ * Dispose **all** active CertificateManager instances,
1327
+ * closing their file watchers and freeing resources.
1328
+ *
1329
+ * This is mainly useful in test tear-down to ensure the
1330
+ * Node.js process can exit cleanly.
1331
+ */
1332
+ static async disposeAll() {
1333
+ const instances = [..._CertificateManager.#activeInstances];
1334
+ await Promise.all(instances.map((cm) => cm.dispose()));
1335
+ }
1336
+ /**
1337
+ * Assert that all CertificateManager instances have been
1338
+ * properly disposed. Throws an Error listing the locations
1339
+ * of any leaked instances.
1340
+ *
1341
+ * Intended for use in test `afterAll()` / `afterEach()`
1342
+ * hooks to catch missing `dispose()` calls early.
1343
+ *
1344
+ * @example
1345
+ * ```ts
1346
+ * after(() => {
1347
+ * CertificateManager.checkAllDisposed();
1348
+ * });
1349
+ * ```
1350
+ */
1351
+ static checkAllDisposed() {
1352
+ if (_CertificateManager.#activeInstances.size === 0) return;
1353
+ const locations = [..._CertificateManager.#activeInstances].map((cm) => cm.rootDir);
1354
+ throw new Error(
1355
+ `${_CertificateManager.#activeInstances.size} CertificateManager instance(s) not disposed:
1356
+ - ${locations.join("\n - ")}`
1357
+ );
1358
+ }
1359
+ // ─────────────────────────────────────────────────────────────
1360
+ /**
1361
+ * When `true` (the default), any certificate that is not
1362
+ * already in the trusted or rejected store is automatically
1363
+ * written to the rejected folder the first time it is seen.
1364
+ */
1386
1365
  untrustUnknownCertificate = true;
1366
+ /** Current lifecycle state of this instance. */
1387
1367
  state = 0 /* Uninitialized */;
1368
+ /** @deprecated Use {@link folderPollingInterval} instead (typo fix). */
1388
1369
  folderPoolingInterval = 5e3;
1370
+ /** Interval in milliseconds for file-system polling (when enabled). */
1371
+ get folderPollingInterval() {
1372
+ return this.folderPoolingInterval;
1373
+ }
1374
+ set folderPollingInterval(value) {
1375
+ this.folderPoolingInterval = value;
1376
+ }
1377
+ /** RSA key size used when generating the private key. */
1389
1378
  keySize;
1390
- location;
1391
- _watchers = [];
1392
- _readCertificatesCalled = false;
1393
- _filenameToHash = {};
1394
- _thumbs = {
1395
- rejected: {},
1396
- trusted: {},
1379
+ #location;
1380
+ #watchers = [];
1381
+ #readCertificatesCalled = false;
1382
+ #filenameToHash = /* @__PURE__ */ new Map();
1383
+ #initializingPromise;
1384
+ #thumbs = {
1385
+ rejected: /* @__PURE__ */ new Map(),
1386
+ trusted: /* @__PURE__ */ new Map(),
1397
1387
  issuers: {
1398
- certs: {}
1388
+ certs: /* @__PURE__ */ new Map()
1399
1389
  },
1400
- crl: {},
1401
- issuersCrl: {}
1390
+ crl: /* @__PURE__ */ new Map(),
1391
+ issuersCrl: /* @__PURE__ */ new Map()
1402
1392
  };
1393
+ /**
1394
+ * Create a new CertificateManager.
1395
+ *
1396
+ * The constructor creates the root directory if it does not
1397
+ * exist but does **not** initialise the PKI store — call
1398
+ * {@link initialize} before using any other method.
1399
+ *
1400
+ * @param options - configuration options
1401
+ */
1403
1402
  constructor(options) {
1404
1403
  options.keySize = options.keySize || 2048;
1405
- (0, import_node_assert10.default)(Object.prototype.hasOwnProperty.call(options, "location"));
1406
- (0, import_node_assert10.default)(Object.prototype.hasOwnProperty.call(options, "keySize"));
1407
- (0, import_node_assert10.default)(this.state === 0 /* Uninitialized */);
1408
- this.location = makePath(options.location, "");
1404
+ if (!options.location) {
1405
+ throw new Error("CertificateManager: missing 'location' option");
1406
+ }
1407
+ this.#location = makePath(options.location, "");
1409
1408
  this.keySize = options.keySize;
1410
1409
  mkdirRecursiveSync(options.location);
1411
- if (!import_node_fs9.default.existsSync(this.location)) {
1412
- throw new Error(`CertificateManager cannot access location ${this.location}`);
1410
+ if (!import_node_fs9.default.existsSync(this.#location)) {
1411
+ throw new Error(`CertificateManager cannot access location ${this.#location}`);
1413
1412
  }
1414
1413
  }
1414
+ /** Path to the OpenSSL configuration file. */
1415
1415
  get configFile() {
1416
1416
  return import_node_path6.default.join(this.rootDir, "own/openssl.cnf");
1417
1417
  }
1418
+ /** Root directory of the PKI store. */
1418
1419
  get rootDir() {
1419
- return this.location;
1420
+ return this.#location;
1420
1421
  }
1422
+ /** Path to the private key file (`own/private/private_key.pem`). */
1421
1423
  get privateKey() {
1422
1424
  return import_node_path6.default.join(this.rootDir, "own/private/private_key.pem");
1423
1425
  }
1426
+ /** Path to the OpenSSL random seed file. */
1424
1427
  get randomFile() {
1425
1428
  return import_node_path6.default.join(this.rootDir, "./random.rnd");
1426
1429
  }
1427
- /**
1428
- * returns the certificate status trusted/rejected
1429
- * @param certificate
1430
- */
1431
- async getCertificateStatus(certificate) {
1432
- await this.initialize();
1433
- let status = await this._checkRejectedOrTrusted(certificate);
1434
- if (status === "unknown") {
1435
- (0, import_node_assert10.default)(certificate instanceof Buffer);
1436
- const pem = (0, import_node_opcua_crypto5.toPem)(certificate, "CERTIFICATE");
1437
- const fingerprint2 = makeFingerprint(certificate);
1438
- const filename = import_node_path6.default.join(this.rejectedFolder, `${buildIdealCertificateName(certificate)}.pem`);
1439
- await import_node_fs9.default.promises.writeFile(filename, pem);
1440
- this._thumbs.rejected[fingerprint2] = {
1441
- certificate,
1442
- filename
1443
- };
1444
- status = "rejected";
1445
- }
1446
- return status;
1447
- }
1448
1430
  /**
1449
1431
  * Move a certificate to the rejected store.
1450
- * If the certificate was previously trusted, it will be moved.
1432
+ * If the certificate was previously trusted, it will be removed from the trusted folder.
1451
1433
  * @param certificate - the DER-encoded certificate
1452
1434
  */
1453
1435
  async rejectCertificate(certificate) {
1454
- await this._moveCertificate(certificate, "rejected");
1436
+ await this.#moveCertificate(certificate, "rejected");
1455
1437
  }
1456
1438
  /**
1457
1439
  * Move a certificate to the trusted store.
1458
- * If the certificate was previously rejected, it will be moved.
1440
+ * If the certificate was previously rejected, it will be removed from the rejected folder.
1459
1441
  * @param certificate - the DER-encoded certificate
1460
1442
  */
1461
1443
  async trustCertificate(certificate) {
1462
- await this._moveCertificate(certificate, "trusted");
1444
+ await this.#moveCertificate(certificate, "trusted");
1463
1445
  }
1464
1446
  /** Path to the rejected certificates folder. */
1465
1447
  get rejectedFolder() {
@@ -1481,6 +1463,13 @@ var CertificateManager = class {
1481
1463
  get issuersCrlFolder() {
1482
1464
  return import_node_path6.default.join(this.rootDir, "issuers/crl");
1483
1465
  }
1466
+ /** Path to the own certificate folder. */
1467
+ get ownCertFolder() {
1468
+ return import_node_path6.default.join(this.rootDir, "own/certs");
1469
+ }
1470
+ get ownPrivateFolder() {
1471
+ return import_node_path6.default.join(this.rootDir, "own/private");
1472
+ }
1484
1473
  /**
1485
1474
  * Check if a certificate is in the trusted store.
1486
1475
  * If the certificate is unknown and `untrustUnknownCertificate` is set,
@@ -1490,33 +1479,27 @@ var CertificateManager = class {
1490
1479
  * or `"BadCertificateInvalid"` if the certificate cannot be parsed.
1491
1480
  */
1492
1481
  async isCertificateTrusted(certificate) {
1493
- const fingerprint2 = makeFingerprint(certificate);
1494
- const certificateInTrust = this._thumbs.trusted[fingerprint2]?.certificate;
1495
- if (certificateInTrust) {
1482
+ const fingerprint = makeFingerprint(certificate);
1483
+ if (this.#thumbs.trusted.has(fingerprint)) {
1496
1484
  return "Good";
1497
- } else {
1498
- const certificateInRejected = this._thumbs.rejected[fingerprint2];
1499
- if (!certificateInRejected) {
1500
- const certificateFilenameInRejected = import_node_path6.default.join(
1501
- this.rejectedFolder,
1502
- `${buildIdealCertificateName(certificate)}.pem`
1503
- );
1504
- if (!this.untrustUnknownCertificate) {
1505
- return "Good";
1506
- }
1507
- try {
1508
- const _certificateInfo = (0, import_node_opcua_crypto5.exploreCertificateInfo)(certificate);
1509
- _certificateInfo;
1510
- } catch (_err) {
1511
- return "BadCertificateInvalid";
1512
- }
1513
- debugLog("certificate has never been seen before and is now rejected (untrusted) ", certificateFilenameInRejected);
1514
- await fsWriteFile(certificateFilenameInRejected, (0, import_node_opcua_crypto5.toPem)(certificate, "CERTIFICATE"));
1485
+ }
1486
+ if (!this.#thumbs.rejected.has(fingerprint)) {
1487
+ if (!this.untrustUnknownCertificate) {
1488
+ return "Good";
1515
1489
  }
1516
- return "BadCertificateUntrusted";
1490
+ try {
1491
+ (0, import_node_opcua_crypto5.exploreCertificateInfo)(certificate);
1492
+ } catch (_err) {
1493
+ return "BadCertificateInvalid";
1494
+ }
1495
+ const filename = import_node_path6.default.join(this.rejectedFolder, `${buildIdealCertificateName(certificate)}.pem`);
1496
+ debugLog("certificate has never been seen before and is now rejected (untrusted) ", filename);
1497
+ await fsWriteFile(filename, (0, import_node_opcua_crypto5.toPem)(certificate, "CERTIFICATE"));
1498
+ this.#thumbs.rejected.set(fingerprint, { certificate, filename });
1517
1499
  }
1500
+ return "BadCertificateUntrusted";
1518
1501
  }
1519
- async _innerVerifyCertificateAsync(certificate, _isIssuer, level, options) {
1502
+ async #innerVerifyCertificateAsync(certificate, _isIssuer, level, options) {
1520
1503
  if (level >= 5) {
1521
1504
  return "BadSecurityChecksFailed" /* BadSecurityChecksFailed */;
1522
1505
  }
@@ -1551,7 +1534,7 @@ var CertificateManager = class {
1551
1534
  } else {
1552
1535
  debugLog(" the issuer certificate has been found in the issuer.cert folder !");
1553
1536
  }
1554
- const issuerStatus = await this._innerVerifyCertificateAsync(issuerCertificate, true, level + 1, options);
1537
+ const issuerStatus = await this.#innerVerifyCertificateAsync(issuerCertificate, true, level + 1, options);
1555
1538
  if (issuerStatus === "BadCertificateRevocationUnknown" /* BadCertificateRevocationUnknown */) {
1556
1539
  return "BadCertificateIssuerRevocationUnknown" /* BadCertificateIssuerRevocationUnknown */;
1557
1540
  }
@@ -1585,7 +1568,7 @@ var CertificateManager = class {
1585
1568
  debugLog("revokedStatus", revokedStatus);
1586
1569
  return revokedStatus;
1587
1570
  }
1588
- const issuerTrustedStatus = await this._checkRejectedOrTrusted(issuerCertificate);
1571
+ const issuerTrustedStatus = await this.#checkRejectedOrTrusted(issuerCertificate);
1589
1572
  debugLog("issuerTrustedStatus", issuerTrustedStatus);
1590
1573
  if (issuerTrustedStatus === "unknown") {
1591
1574
  hasTrustedIssuer = false;
@@ -1604,12 +1587,14 @@ var CertificateManager = class {
1604
1587
  debugLog("revokedStatus of self signed certificate:", revokedStatus);
1605
1588
  }
1606
1589
  }
1607
- const status = await this._checkRejectedOrTrusted(certificate);
1590
+ const status = await this.#checkRejectedOrTrusted(certificate);
1608
1591
  if (status === "rejected") {
1609
- return "BadCertificateUntrusted" /* BadCertificateUntrusted */;
1592
+ if (!(options.acceptCertificateWithValidIssuerChain && hasValidIssuer && hasTrustedIssuer)) {
1593
+ return "BadCertificateUntrusted" /* BadCertificateUntrusted */;
1594
+ }
1610
1595
  }
1611
1596
  const _c2 = chain[1] ? (0, import_node_opcua_crypto5.exploreCertificateInfo)(chain[1]) : "non";
1612
- _c2;
1597
+ debugLog("chain[1] info=", _c2);
1613
1598
  const certificateInfo = (0, import_node_opcua_crypto5.exploreCertificateInfo)(certificate);
1614
1599
  const now = /* @__PURE__ */ new Date();
1615
1600
  let isTimeInvalid = false;
@@ -1632,7 +1617,6 @@ var CertificateManager = class {
1632
1617
  if (status === "trusted") {
1633
1618
  return isTimeInvalid ? "BadCertificateTimeInvalid" /* BadCertificateTimeInvalid */ : "Good" /* Good */;
1634
1619
  }
1635
- (0, import_node_assert10.default)(status === "unknown");
1636
1620
  if (hasIssuerKey) {
1637
1621
  if (!hasTrustedIssuer) {
1638
1622
  return "BadCertificateUntrusted" /* BadCertificateUntrusted */;
@@ -1640,47 +1624,74 @@ var CertificateManager = class {
1640
1624
  if (!hasValidIssuer) {
1641
1625
  return "BadCertificateUntrusted" /* BadCertificateUntrusted */;
1642
1626
  }
1627
+ if (!options.acceptCertificateWithValidIssuerChain) {
1628
+ return "BadCertificateUntrusted" /* BadCertificateUntrusted */;
1629
+ }
1643
1630
  return isTimeInvalid ? "BadCertificateTimeInvalid" /* BadCertificateTimeInvalid */ : "Good" /* Good */;
1644
1631
  } else {
1645
1632
  return "BadCertificateUntrusted" /* BadCertificateUntrusted */;
1646
1633
  }
1647
1634
  }
1635
+ /**
1636
+ * Internal verification hook called by {@link verifyCertificate}.
1637
+ *
1638
+ * Subclasses can override this to inject additional validation
1639
+ * logic (e.g. application-level policy checks) while still
1640
+ * delegating to the default chain/CRL/trust verification.
1641
+ *
1642
+ * @param certificate - the DER-encoded certificate to verify
1643
+ * @param options - verification options forwarded from the
1644
+ * public API
1645
+ * @returns the verification status code
1646
+ */
1648
1647
  async verifyCertificateAsync(certificate, options) {
1649
- const status1 = await this._innerVerifyCertificateAsync(certificate, false, 0, options);
1648
+ const status1 = await this.#innerVerifyCertificateAsync(certificate, false, 0, options);
1650
1649
  return status1;
1651
1650
  }
1652
1651
  /**
1653
- * Verify certificate validity
1654
- * @method verifyCertificate
1655
- * @param certificate
1652
+ * Verify a certificate against the PKI trust store.
1653
+ *
1654
+ * This performs a full validation including trust status,
1655
+ * issuer chain, CRL revocation checks, and time validity.
1656
+ *
1657
+ * @param certificate - the DER-encoded certificate to verify
1658
+ * @param options - optional flags to relax validation rules
1659
+ * @returns the verification status code
1656
1660
  */
1657
1661
  async verifyCertificate(certificate, options) {
1658
1662
  if (!certificate) {
1659
1663
  return "BadSecurityChecksFailed" /* BadSecurityChecksFailed */;
1660
1664
  }
1661
- return await this.verifyCertificateAsync(certificate, options || {});
1665
+ try {
1666
+ const status = await this.verifyCertificateAsync(certificate, options || {});
1667
+ return status;
1668
+ } catch (error) {
1669
+ warningLog(`verifyCertificate error: ${error.message}`);
1670
+ return "BadCertificateInvalid" /* BadCertificateInvalid */;
1671
+ }
1662
1672
  }
1663
- /*
1664
- *
1665
- * PKI
1666
- * +---> trusted
1667
- * +---> rejected
1668
- * +---> own
1669
- * +---> cert
1670
- * +---> own
1673
+ /**
1674
+ * Initialize the PKI directory structure, generate the
1675
+ * private key (if missing), and start file-system watchers.
1671
1676
  *
1677
+ * This method is idempotent — subsequent calls are no-ops.
1678
+ * It must be called before any certificate operations.
1672
1679
  */
1673
1680
  async initialize() {
1674
1681
  if (this.state !== 0 /* Uninitialized */) {
1675
1682
  return;
1676
1683
  }
1677
1684
  this.state = 1 /* Initializing */;
1678
- await this._initialize();
1685
+ this.#initializingPromise = this.#initialize();
1686
+ await this.#initializingPromise;
1687
+ this.#initializingPromise = void 0;
1679
1688
  this.state = 2 /* Initialized */;
1689
+ _CertificateManager.#activeInstances.add(this);
1690
+ _CertificateManager.#installProcessCleanup();
1680
1691
  }
1681
- async _initialize() {
1692
+ async #initialize() {
1682
1693
  this.state = 1 /* Initializing */;
1683
- const pkiDir = this.location;
1694
+ const pkiDir = this.#location;
1684
1695
  mkdirRecursiveSync(pkiDir);
1685
1696
  mkdirRecursiveSync(import_node_path6.default.join(pkiDir, "own"));
1686
1697
  mkdirRecursiveSync(import_node_path6.default.join(pkiDir, "own/certs"));
@@ -1694,24 +1705,22 @@ var CertificateManager = class {
1694
1705
  mkdirRecursiveSync(import_node_path6.default.join(pkiDir, "issuers/crl"));
1695
1706
  if (!import_node_fs9.default.existsSync(this.configFile) || !import_node_fs9.default.existsSync(this.privateKey)) {
1696
1707
  return await this.withLock2(async () => {
1697
- (0, import_node_assert10.default)(this.state !== 3 /* Disposing */);
1698
- if (this.state === 4 /* Disposed */) {
1708
+ if (this.state === 3 /* Disposing */ || this.state === 4 /* Disposed */) {
1699
1709
  return;
1700
1710
  }
1701
- (0, import_node_assert10.default)(this.state === 1 /* Initializing */);
1702
1711
  if (!import_node_fs9.default.existsSync(this.configFile)) {
1703
1712
  import_node_fs9.default.writeFileSync(this.configFile, configurationFileSimpleTemplate);
1704
1713
  }
1705
1714
  if (!import_node_fs9.default.existsSync(this.privateKey)) {
1706
1715
  debugLog("generating private key ...");
1707
1716
  await (0, import_node_opcua_crypto5.generatePrivateKeyFile)(this.privateKey, this.keySize);
1708
- await this._readCertificates();
1717
+ await this.#readCertificates();
1709
1718
  } else {
1710
- await this._readCertificates();
1719
+ await this.#readCertificates();
1711
1720
  }
1712
1721
  });
1713
1722
  } else {
1714
- await this._readCertificates();
1723
+ await this.#readCertificates();
1715
1724
  }
1716
1725
  }
1717
1726
  /**
@@ -1728,20 +1737,46 @@ var CertificateManager = class {
1728
1737
  return;
1729
1738
  }
1730
1739
  if (this.state === 1 /* Initializing */) {
1731
- await new Promise((resolve) => setTimeout(resolve, 100));
1732
- return await this.dispose();
1740
+ if (this.#initializingPromise) {
1741
+ await this.#initializingPromise;
1742
+ }
1733
1743
  }
1734
1744
  try {
1735
1745
  this.state = 3 /* Disposing */;
1736
- await Promise.all(this._watchers.map((w) => w.close()));
1737
- this._watchers.forEach((w) => {
1746
+ await Promise.all(this.#watchers.map((w) => w.close()));
1747
+ this.#watchers.forEach((w) => {
1738
1748
  w.removeAllListeners();
1739
1749
  });
1740
- this._watchers.splice(0);
1750
+ this.#watchers.splice(0);
1741
1751
  } finally {
1742
1752
  this.state = 4 /* Disposed */;
1753
+ _CertificateManager.#activeInstances.delete(this);
1743
1754
  }
1744
1755
  }
1756
+ /**
1757
+ * Force a full re-scan of all PKI folders, rebuilding
1758
+ * the in-memory `_thumbs` index from scratch.
1759
+ *
1760
+ * Call this after external processes have modified the
1761
+ * PKI folders (e.g. via `writeTrustList` or CLI tools)
1762
+ * to ensure the CertificateManager sees the latest
1763
+ * state without waiting for file-system events.
1764
+ */
1765
+ async reloadCertificates() {
1766
+ await Promise.all(this.#watchers.map((w) => w.close()));
1767
+ for (const w of this.#watchers) {
1768
+ w.removeAllListeners();
1769
+ }
1770
+ this.#watchers.splice(0);
1771
+ this.#thumbs.rejected.clear();
1772
+ this.#thumbs.trusted.clear();
1773
+ this.#thumbs.issuers.certs.clear();
1774
+ this.#thumbs.crl.clear();
1775
+ this.#thumbs.issuersCrl.clear();
1776
+ this.#filenameToHash.clear();
1777
+ this.#readCertificatesCalled = false;
1778
+ await this.#readCertificates();
1779
+ }
1745
1780
  async withLock2(action) {
1746
1781
  const lockFileName = import_node_path6.default.join(this.rootDir, "mutex.lock");
1747
1782
  return (0, import_global_mutex.withLock)({ fileToLock: lockFileName }, async () => {
@@ -1749,12 +1784,18 @@ var CertificateManager = class {
1749
1784
  });
1750
1785
  }
1751
1786
  /**
1787
+ * Create a self-signed certificate for this PKI's private key.
1752
1788
  *
1753
- * create a self-signed certificate for the CertificateManager private key
1789
+ * The certificate is written to `params.outputFile` or
1790
+ * `own/certs/self_signed_certificate.pem` by default.
1754
1791
  *
1792
+ * @param params - certificate parameters (subject, SANs,
1793
+ * validity, etc.)
1755
1794
  */
1756
1795
  async createSelfSignedCertificate(params) {
1757
- (0, import_node_assert10.default)(typeof params.applicationUri === "string", "expecting applicationUri");
1796
+ if (typeof params.applicationUri !== "string") {
1797
+ throw new Error("createSelfSignedCertificate: expecting applicationUri to be a string");
1798
+ }
1758
1799
  if (!import_node_fs9.default.existsSync(this.privateKey)) {
1759
1800
  throw new Error(`Cannot find private key ${this.privateKey}`);
1760
1801
  }
@@ -1769,22 +1810,31 @@ var CertificateManager = class {
1769
1810
  await createSelfSignedCertificate(certificateFilename, _params);
1770
1811
  });
1771
1812
  }
1813
+ /**
1814
+ * Create a Certificate Signing Request (CSR) using this
1815
+ * PKI's private key and configuration.
1816
+ *
1817
+ * The CSR file is written to `own/certs/` with a timestamped
1818
+ * filename.
1819
+ *
1820
+ * @param params - CSR parameters (subject, SANs)
1821
+ * @returns the filesystem path to the generated CSR file
1822
+ */
1772
1823
  async createCertificateRequest(params) {
1773
- (0, import_node_assert10.default)(params);
1824
+ if (!params) {
1825
+ throw new Error("params is required");
1826
+ }
1774
1827
  const _params = params;
1775
1828
  if (Object.prototype.hasOwnProperty.call(_params, "rootDir")) {
1776
1829
  throw new Error("rootDir should not be specified ");
1777
1830
  }
1778
- (0, import_node_assert10.default)(!_params.rootDir);
1779
- (0, import_node_assert10.default)(!_params.configFile);
1780
- (0, import_node_assert10.default)(!_params.privateKey);
1781
1831
  _params.rootDir = import_node_path6.default.resolve(this.rootDir);
1782
1832
  _params.configFile = import_node_path6.default.resolve(this.configFile);
1783
1833
  _params.privateKey = import_node_path6.default.resolve(this.privateKey);
1784
1834
  return await this.withLock2(async () => {
1785
1835
  const now = /* @__PURE__ */ new Date();
1786
- const today2 = `${now.toISOString().slice(0, 10)}_${now.getTime()}`;
1787
- const certificateSigningRequestFilename = import_node_path6.default.join(this.rootDir, "own/certs", `certificate_${today2}.csr`);
1836
+ const today = `${now.toISOString().slice(0, 10)}_${now.getTime()}`;
1837
+ const certificateSigningRequestFilename = import_node_path6.default.join(this.rootDir, "own/certs", `certificate_${today}.csr`);
1788
1838
  await createCertificateSigningRequestAsync(certificateSigningRequestFilename, _params);
1789
1839
  return certificateSigningRequestFilename;
1790
1840
  });
@@ -1805,13 +1855,13 @@ var CertificateManager = class {
1805
1855
  }
1806
1856
  }
1807
1857
  const pemCertificate = (0, import_node_opcua_crypto5.toPem)(certificate, "CERTIFICATE");
1808
- const fingerprint2 = makeFingerprint(certificate);
1809
- if (this._thumbs.issuers.certs[fingerprint2]) {
1858
+ const fingerprint = makeFingerprint(certificate);
1859
+ if (this.#thumbs.issuers.certs.has(fingerprint)) {
1810
1860
  return "Good" /* Good */;
1811
1861
  }
1812
1862
  const filename = import_node_path6.default.join(this.issuersCertFolder, `issuer_${buildIdealCertificateName(certificate)}.pem`);
1813
1863
  await import_node_fs9.default.promises.writeFile(filename, pemCertificate, "ascii");
1814
- this._thumbs.issuers.certs[fingerprint2] = { certificate, filename };
1864
+ this.#thumbs.issuers.certs.set(fingerprint, { certificate, filename });
1815
1865
  if (addInTrustList) {
1816
1866
  await this.trustCertificate(certificate);
1817
1867
  }
@@ -1825,18 +1875,18 @@ var CertificateManager = class {
1825
1875
  async addRevocationList(crl, target = "issuers") {
1826
1876
  return await this.withLock2(async () => {
1827
1877
  try {
1828
- const index = target === "trusted" ? this._thumbs.crl : this._thumbs.issuersCrl;
1878
+ const index = target === "trusted" ? this.#thumbs.crl : this.#thumbs.issuersCrl;
1829
1879
  const folder = target === "trusted" ? this.crlFolder : this.issuersCrlFolder;
1830
1880
  const crlInfo = (0, import_node_opcua_crypto5.exploreCertificateRevocationList)(crl);
1831
1881
  const key = crlInfo.tbsCertList.issuerFingerprint;
1832
- if (!index[key]) {
1833
- index[key] = { crls: [], serialNumbers: {} };
1882
+ if (!index.has(key)) {
1883
+ index.set(key, { crls: [], serialNumbers: {} });
1834
1884
  }
1835
1885
  const pemCertificate = (0, import_node_opcua_crypto5.toPem)(crl, "X509 CRL");
1836
1886
  const filename = import_node_path6.default.join(folder, `crl_${buildIdealCertificateName(crl)}.pem`);
1837
1887
  await import_node_fs9.default.promises.writeFile(filename, pemCertificate, "ascii");
1838
- await this._on_crl_file_added(index, filename);
1839
- await this.waitAndCheckCRLProcessingStatus();
1888
+ await this.#onCrlFileAdded(index, filename);
1889
+ await this.#waitAndCheckCRLProcessingStatus();
1840
1890
  return "Good" /* Good */;
1841
1891
  } catch (err) {
1842
1892
  debugLog(err);
@@ -1865,15 +1915,13 @@ var CertificateManager = class {
1865
1915
  throw err;
1866
1916
  }
1867
1917
  }
1868
- for (const key of Object.keys(index)) {
1869
- delete index[key];
1870
- }
1918
+ index.clear();
1871
1919
  };
1872
1920
  if (target === "issuers" || target === "all") {
1873
- await clearFolder(this.issuersCrlFolder, this._thumbs.issuersCrl);
1921
+ await clearFolder(this.issuersCrlFolder, this.#thumbs.issuersCrl);
1874
1922
  }
1875
1923
  if (target === "trusted" || target === "all") {
1876
- await clearFolder(this.crlFolder, this._thumbs.crl);
1924
+ await clearFolder(this.crlFolder, this.#thumbs.crl);
1877
1925
  }
1878
1926
  }
1879
1927
  /**
@@ -1882,9 +1930,9 @@ var CertificateManager = class {
1882
1930
  * @param thumbprint - hex-encoded SHA-1 thumbprint (lowercase)
1883
1931
  */
1884
1932
  async hasIssuer(thumbprint) {
1885
- await this._readCertificates();
1933
+ await this.#readCertificates();
1886
1934
  const normalized = thumbprint.toLowerCase();
1887
- return Object.prototype.hasOwnProperty.call(this._thumbs.issuers.certs, normalized);
1935
+ return this.#thumbs.issuers.certs.has(normalized);
1888
1936
  }
1889
1937
  /**
1890
1938
  * Remove a trusted certificate identified by its SHA-1 thumbprint.
@@ -1894,9 +1942,9 @@ var CertificateManager = class {
1894
1942
  * @returns the removed certificate buffer, or `null` if not found
1895
1943
  */
1896
1944
  async removeTrustedCertificate(thumbprint) {
1897
- await this._readCertificates();
1945
+ await this.#readCertificates();
1898
1946
  const normalized = thumbprint.toLowerCase();
1899
- const entry = this._thumbs.trusted[normalized];
1947
+ const entry = this.#thumbs.trusted.get(normalized);
1900
1948
  if (!entry) {
1901
1949
  return null;
1902
1950
  }
@@ -1907,7 +1955,7 @@ var CertificateManager = class {
1907
1955
  throw err;
1908
1956
  }
1909
1957
  }
1910
- delete this._thumbs.trusted[normalized];
1958
+ this.#thumbs.trusted.delete(normalized);
1911
1959
  return entry.certificate;
1912
1960
  }
1913
1961
  /**
@@ -1918,9 +1966,9 @@ var CertificateManager = class {
1918
1966
  * @returns the removed certificate buffer, or `null` if not found
1919
1967
  */
1920
1968
  async removeIssuer(thumbprint) {
1921
- await this._readCertificates();
1969
+ await this.#readCertificates();
1922
1970
  const normalized = thumbprint.toLowerCase();
1923
- const entry = this._thumbs.issuers.certs[normalized];
1971
+ const entry = this.#thumbs.issuers.certs.get(normalized);
1924
1972
  if (!entry) {
1925
1973
  return null;
1926
1974
  }
@@ -1931,7 +1979,7 @@ var CertificateManager = class {
1931
1979
  throw err;
1932
1980
  }
1933
1981
  }
1934
- delete this._thumbs.issuers.certs[normalized];
1982
+ this.#thumbs.issuers.certs.delete(normalized);
1935
1983
  return entry.certificate;
1936
1984
  }
1937
1985
  /**
@@ -1944,7 +1992,7 @@ var CertificateManager = class {
1944
1992
  const issuerInfo = (0, import_node_opcua_crypto5.exploreCertificate)(issuerCertificate);
1945
1993
  const issuerFingerprint = issuerInfo.tbsCertificate.subjectFingerPrint;
1946
1994
  const processIndex = async (index) => {
1947
- const crlData = index[issuerFingerprint];
1995
+ const crlData = index.get(issuerFingerprint);
1948
1996
  if (!crlData) return;
1949
1997
  for (const crlEntry of crlData.crls) {
1950
1998
  try {
@@ -1955,13 +2003,13 @@ var CertificateManager = class {
1955
2003
  }
1956
2004
  }
1957
2005
  }
1958
- delete index[issuerFingerprint];
2006
+ index.delete(issuerFingerprint);
1959
2007
  };
1960
2008
  if (target === "issuers" || target === "all") {
1961
- await processIndex(this._thumbs.issuersCrl);
2009
+ await processIndex(this.#thumbs.issuersCrl);
1962
2010
  }
1963
2011
  if (target === "trusted" || target === "all") {
1964
- await processIndex(this._thumbs.crl);
2012
+ await processIndex(this.#thumbs.crl);
1965
2013
  }
1966
2014
  }
1967
2015
  /**
@@ -1993,9 +2041,23 @@ var CertificateManager = class {
1993
2041
  return "BadCertificateInvalid" /* BadCertificateInvalid */;
1994
2042
  }
1995
2043
  if (certificates.length > 1) {
2044
+ const issuerFolder = this.issuersCertFolder;
2045
+ const issuerThumbprints = /* @__PURE__ */ new Set();
2046
+ const files = await import_node_fs9.default.promises.readdir(issuerFolder);
2047
+ for (const file of files) {
2048
+ const ext = import_node_path6.default.extname(file).toLowerCase();
2049
+ if (ext === ".pem" || ext === ".der") {
2050
+ try {
2051
+ const issuerCert = (0, import_node_opcua_crypto5.readCertificate)(import_node_path6.default.join(issuerFolder, file));
2052
+ const fp = makeFingerprint(issuerCert);
2053
+ issuerThumbprints.add(fp);
2054
+ } catch (_err) {
2055
+ }
2056
+ }
2057
+ }
1996
2058
  for (const issuerCert of certificates.slice(1)) {
1997
2059
  const thumbprint = makeFingerprint(issuerCert);
1998
- if (!Object.prototype.hasOwnProperty.call(this._thumbs.issuers.certs, thumbprint)) {
2060
+ if (!issuerThumbprints.has(thumbprint)) {
1999
2061
  return "BadCertificateChainIncomplete" /* BadCertificateChainIncomplete */;
2000
2062
  }
2001
2063
  }
@@ -2016,8 +2078,8 @@ var CertificateManager = class {
2016
2078
  * signed by this issuer.
2017
2079
  */
2018
2080
  async isIssuerInUseByTrustedCertificate(issuerCertificate) {
2019
- await this._readCertificates();
2020
- for (const entry of Object.values(this._thumbs.trusted)) {
2081
+ await this.#readCertificates();
2082
+ for (const entry of this.#thumbs.trusted.values()) {
2021
2083
  if (!entry.certificate) continue;
2022
2084
  try {
2023
2085
  if ((0, import_node_opcua_crypto5.verifyCertificateSignature)(entry.certificate, issuerCertificate)) {
@@ -2052,7 +2114,7 @@ var CertificateManager = class {
2052
2114
  debugLog("Certificate has no extension 3");
2053
2115
  return null;
2054
2116
  }
2055
- const issuerCertificates = Object.values(this._thumbs.issuers.certs);
2117
+ const issuerCertificates = [...this.#thumbs.issuers.certs.values()];
2056
2118
  const selectedIssuerCertificates = findMatchingIssuerKey(issuerCertificates, wantedIssuerKey);
2057
2119
  if (selectedIssuerCertificates.length > 0) {
2058
2120
  if (selectedIssuerCertificates.length > 1) {
@@ -2060,7 +2122,7 @@ var CertificateManager = class {
2060
2122
  }
2061
2123
  return selectedIssuerCertificates[0].certificate || null;
2062
2124
  }
2063
- const trustedCertificates = Object.values(this._thumbs.trusted);
2125
+ const trustedCertificates = [...this.#thumbs.trusted.values()];
2064
2126
  const selectedTrustedCertificates = findMatchingIssuerKey(trustedCertificates, wantedIssuerKey);
2065
2127
  if (selectedTrustedCertificates.length > 1) {
2066
2128
  warningLog(
@@ -2072,53 +2134,78 @@ var CertificateManager = class {
2072
2134
  return selectedTrustedCertificates.length > 0 ? selectedTrustedCertificates[0].certificate : null;
2073
2135
  }
2074
2136
  /**
2137
+ *
2138
+ * check if the certificate explicitly appear in the trust list, the reject list or none.
2139
+ * In case of being in the reject and trusted list at the same time is consider: rejected.
2075
2140
  * @internal
2076
2141
  * @private
2077
2142
  */
2078
- async _checkRejectedOrTrusted(certificate) {
2079
- (0, import_node_assert10.default)(certificate instanceof Buffer);
2080
- const fingerprint2 = makeFingerprint(certificate);
2081
- debugLog("_checkRejectedOrTrusted fingerprint ", short(fingerprint2));
2082
- await this._readCertificates();
2083
- if (Object.prototype.hasOwnProperty.call(this._thumbs.rejected, fingerprint2)) {
2143
+ async #checkRejectedOrTrusted(certificate) {
2144
+ const fingerprint = makeFingerprint(certificate);
2145
+ debugLog("#checkRejectedOrTrusted fingerprint ", short(fingerprint));
2146
+ await this.#readCertificates();
2147
+ if (this.#thumbs.rejected.has(fingerprint)) {
2084
2148
  return "rejected";
2085
2149
  }
2086
- if (Object.prototype.hasOwnProperty.call(this._thumbs.trusted, fingerprint2)) {
2150
+ if (this.#thumbs.trusted.has(fingerprint)) {
2087
2151
  return "trusted";
2088
2152
  }
2089
2153
  return "unknown";
2090
2154
  }
2091
- async _moveCertificate(certificate, newStatus) {
2092
- (0, import_node_assert10.default)(certificate instanceof Buffer);
2093
- const fingerprint2 = makeFingerprint(certificate);
2094
- const status = await this.getCertificateStatus(certificate);
2095
- debugLog("_moveCertificate", fingerprint2.substring(0, 10), "from", status, "to", newStatus);
2096
- (0, import_node_assert10.default)(status === "rejected" || status === "trusted");
2097
- if (status !== newStatus) {
2098
- const indexSrc = status === "rejected" ? this._thumbs.rejected : this._thumbs.trusted;
2099
- const certificateSrc = indexSrc[fingerprint2]?.filename;
2100
- if (!certificateSrc) {
2101
- debugLog(" cannot find certificate ", fingerprint2.substring(0, 10), " in", this._thumbs, [status]);
2102
- throw new Error("internal");
2155
+ async #moveCertificate(certificate, newStatus) {
2156
+ await this.withLock2(async () => {
2157
+ const fingerprint = makeFingerprint(certificate);
2158
+ let status = await this.#checkRejectedOrTrusted(certificate);
2159
+ if (status === "unknown") {
2160
+ const pem = (0, import_node_opcua_crypto5.toPem)(certificate, "CERTIFICATE");
2161
+ const filename = import_node_path6.default.join(this.rejectedFolder, `${buildIdealCertificateName(certificate)}.pem`);
2162
+ await import_node_fs9.default.promises.writeFile(filename, pem);
2163
+ this.#thumbs.rejected.set(fingerprint, { certificate, filename });
2164
+ status = "rejected";
2103
2165
  }
2104
- const destFolder = newStatus === "rejected" ? this.rejectedFolder : newStatus === "trusted" ? this.trustedFolder : this.rejectedFolder;
2105
- const certificateDest = import_node_path6.default.join(destFolder, import_node_path6.default.basename(certificateSrc));
2106
- debugLog("_moveCertificate1", fingerprint2.substring(0, 10), "old name", certificateSrc);
2107
- debugLog("_moveCertificate1", fingerprint2.substring(0, 10), "new name", certificateDest);
2108
- await import_node_fs9.default.promises.rename(certificateSrc, certificateDest);
2109
- delete indexSrc[fingerprint2];
2110
- const indexDest = newStatus === "rejected" ? this._thumbs.rejected : newStatus === "trusted" ? this._thumbs.trusted : this._thumbs.rejected;
2111
- indexDest[fingerprint2] = {
2112
- certificate,
2113
- filename: certificateDest
2114
- };
2115
- }
2166
+ debugLog("#moveCertificate", fingerprint.substring(0, 10), "from", status, "to", newStatus);
2167
+ if (status !== "rejected" && status !== "trusted") {
2168
+ throw new Error(`#moveCertificate: unexpected status '${status}' for certificate ${fingerprint.substring(0, 10)}`);
2169
+ }
2170
+ if (status !== newStatus) {
2171
+ const indexSrc = status === "rejected" ? this.#thumbs.rejected : this.#thumbs.trusted;
2172
+ const srcEntry = indexSrc.get(fingerprint);
2173
+ if (!srcEntry) {
2174
+ debugLog(" cannot find certificate ", fingerprint.substring(0, 10), " in", status);
2175
+ throw new Error(`#moveCertificate: certificate ${fingerprint.substring(0, 10)} not found in ${status} index`);
2176
+ }
2177
+ const destFolder = newStatus === "trusted" ? this.trustedFolder : this.rejectedFolder;
2178
+ const certificateDest = import_node_path6.default.join(destFolder, import_node_path6.default.basename(srcEntry.filename));
2179
+ debugLog("#moveCertificate", fingerprint.substring(0, 10), "old name", srcEntry.filename);
2180
+ debugLog("#moveCertificate", fingerprint.substring(0, 10), "new name", certificateDest);
2181
+ await import_node_fs9.default.promises.rename(srcEntry.filename, certificateDest);
2182
+ indexSrc.delete(fingerprint);
2183
+ const indexDest = newStatus === "trusted" ? this.#thumbs.trusted : this.#thumbs.rejected;
2184
+ indexDest.set(fingerprint, { certificate, filename: certificateDest });
2185
+ }
2186
+ });
2116
2187
  }
2117
- _findAssociatedCRLs(issuerCertificate) {
2188
+ #findAssociatedCRLs(issuerCertificate) {
2118
2189
  const issuerCertificateInfo = (0, import_node_opcua_crypto5.exploreCertificate)(issuerCertificate);
2119
2190
  const key = issuerCertificateInfo.tbsCertificate.subjectFingerPrint;
2120
- return this._thumbs.issuersCrl[key] ? this._thumbs.issuersCrl[key] : this._thumbs.crl[key] ? this._thumbs.crl[key] : null;
2191
+ return this.#thumbs.issuersCrl.get(key) ?? this.#thumbs.crl.get(key) ?? null;
2121
2192
  }
2193
+ /**
2194
+ * Check whether a certificate has been revoked by its issuer's CRL.
2195
+ *
2196
+ * - Self-signed certificates are never considered revoked.
2197
+ * - If no `issuerCertificate` is provided, the method attempts
2198
+ * to find it via {@link findIssuerCertificate}.
2199
+ *
2200
+ * @param certificate - the DER-encoded certificate to check
2201
+ * @param issuerCertificate - optional issuer certificate; looked
2202
+ * up automatically when omitted
2203
+ * @returns `Good` if not revoked, `BadCertificateRevoked` if the
2204
+ * serial number appears in a CRL,
2205
+ * `BadCertificateRevocationUnknown` if no CRL is available,
2206
+ * or `BadCertificateChainIncomplete` if the issuer cannot be
2207
+ * found.
2208
+ */
2122
2209
  async isCertificateRevoked(certificate, issuerCertificate) {
2123
2210
  if (isSelfSigned3(certificate)) {
2124
2211
  return "Good" /* Good */;
@@ -2129,913 +2216,251 @@ var CertificateManager = class {
2129
2216
  if (!issuerCertificate) {
2130
2217
  return "BadCertificateChainIncomplete" /* BadCertificateChainIncomplete */;
2131
2218
  }
2132
- const crls = this._findAssociatedCRLs(issuerCertificate);
2219
+ const crls = this.#findAssociatedCRLs(issuerCertificate);
2133
2220
  if (!crls) {
2134
2221
  return "BadCertificateRevocationUnknown" /* BadCertificateRevocationUnknown */;
2135
2222
  }
2136
2223
  const certInfo = (0, import_node_opcua_crypto5.exploreCertificate)(certificate);
2137
2224
  const serialNumber = certInfo.tbsCertificate.serialNumber || certInfo.tbsCertificate.extensions?.authorityKeyIdentifier?.serial || "";
2138
2225
  const key = certInfo.tbsCertificate.extensions?.authorityKeyIdentifier?.authorityCertIssuerFingerPrint || "<unknown>";
2139
- const crl2 = this._thumbs.crl[key] || null;
2226
+ const crl2 = this.#thumbs.crl.get(key) ?? null;
2140
2227
  if (crls.serialNumbers[serialNumber] || crl2?.serialNumbers[serialNumber]) {
2141
2228
  return "BadCertificateRevoked" /* BadCertificateRevoked */;
2142
2229
  }
2143
2230
  return "Good" /* Good */;
2144
2231
  }
2145
- _pending_crl_to_process = 0;
2146
- _on_crl_process;
2147
- queue = [];
2148
- _on_crl_file_added(index, filename) {
2149
- this.queue.push({ index, filename });
2150
- this._pending_crl_to_process += 1;
2151
- if (this._pending_crl_to_process === 1) {
2152
- this._process_next_crl();
2232
+ #pendingCrlToProcess = 0;
2233
+ #onCrlProcess;
2234
+ #queue = [];
2235
+ #onCrlFileAdded(index, filename) {
2236
+ this.#queue.push({ index, filename });
2237
+ this.#pendingCrlToProcess += 1;
2238
+ if (this.#pendingCrlToProcess === 1) {
2239
+ this.#processNextCrl();
2153
2240
  }
2154
2241
  }
2155
- async _process_next_crl() {
2242
+ async #processNextCrl() {
2156
2243
  try {
2157
- const nextCRL = this.queue.shift();
2244
+ const nextCRL = this.#queue.shift();
2158
2245
  if (!nextCRL) return;
2159
2246
  const { index, filename } = nextCRL;
2160
2247
  const crl = await (0, import_node_opcua_crypto5.readCertificateRevocationList)(filename);
2161
2248
  const crlInfo = (0, import_node_opcua_crypto5.exploreCertificateRevocationList)(crl);
2162
2249
  debugLog(import_chalk6.default.cyan("add CRL in folder "), filename);
2163
- const fingerprint2 = crlInfo.tbsCertList.issuerFingerprint;
2164
- index[fingerprint2] = index[fingerprint2] || {
2165
- crls: [],
2166
- serialNumbers: {}
2167
- };
2168
- index[fingerprint2].crls.push({ crlInfo, filename });
2169
- const serialNumbers = index[fingerprint2].serialNumbers;
2250
+ const fingerprint = crlInfo.tbsCertList.issuerFingerprint;
2251
+ if (!index.has(fingerprint)) {
2252
+ index.set(fingerprint, { crls: [], serialNumbers: {} });
2253
+ }
2254
+ const data = index.get(fingerprint) || { crls: [], serialNumbers: {} };
2255
+ data.crls.push({ crlInfo, filename });
2170
2256
  for (const revokedCertificate of crlInfo.tbsCertList.revokedCertificates) {
2171
2257
  const serialNumber = revokedCertificate.userCertificate;
2172
- if (!serialNumbers[serialNumber]) {
2173
- serialNumbers[serialNumber] = revokedCertificate.revocationDate;
2258
+ if (!data.serialNumbers[serialNumber]) {
2259
+ data.serialNumbers[serialNumber] = revokedCertificate.revocationDate;
2174
2260
  }
2175
2261
  }
2176
- debugLog(import_chalk6.default.cyan("CRL"), fingerprint2, "serial numbers = ", Object.keys(serialNumbers));
2262
+ debugLog(import_chalk6.default.cyan("CRL"), fingerprint, "serial numbers = ", Object.keys(data.serialNumbers));
2177
2263
  } catch (err) {
2178
2264
  debugLog("CRL filename error =");
2179
2265
  debugLog(err);
2180
2266
  }
2181
- this._pending_crl_to_process -= 1;
2182
- if (this._pending_crl_to_process === 0) {
2183
- if (this._on_crl_process) {
2184
- this._on_crl_process();
2185
- this._on_crl_process = void 0;
2267
+ this.#pendingCrlToProcess -= 1;
2268
+ if (this.#pendingCrlToProcess === 0) {
2269
+ if (this.#onCrlProcess) {
2270
+ this.#onCrlProcess();
2271
+ this.#onCrlProcess = void 0;
2186
2272
  }
2187
2273
  } else {
2188
- this._process_next_crl();
2274
+ this.#processNextCrl();
2189
2275
  }
2190
2276
  }
2191
- async _readCertificates() {
2192
- if (this._readCertificatesCalled) {
2277
+ async #readCertificates() {
2278
+ if (this.#readCertificatesCalled) {
2193
2279
  return;
2194
2280
  }
2195
- this._readCertificatesCalled = true;
2196
- const options = {
2197
- usePolling: true,
2198
- interval: Math.min(10 * 60 * 1e3, Math.max(100, this.folderPoolingInterval)),
2199
- persistent: false,
2200
- awaitWriteFinish: {
2201
- stabilityThreshold: 2e3,
2202
- pollInterval: 600
2203
- }
2281
+ this.#readCertificatesCalled = true;
2282
+ const usePolling = process.env.OPCUA_PKI_USE_POLLING === "true";
2283
+ const chokidarOptions = {
2284
+ usePolling,
2285
+ ...usePolling ? { interval: Math.min(10 * 60 * 1e3, Math.max(100, this.folderPoolingInterval)) } : {},
2286
+ persistent: false
2204
2287
  };
2205
- async function _walkCRLFiles(folder, index) {
2206
- await new Promise((resolve, _reject) => {
2207
- const w = import_chokidar.default.watch(folder, options);
2208
- w.on("unlink", (filename, stat) => {
2209
- filename;
2210
- stat;
2211
- });
2212
- w.on("add", (filename, stat) => {
2213
- stat;
2214
- this._on_crl_file_added(index, filename);
2215
- });
2216
- w.on("change", (path8, stat) => {
2217
- debugLog("change in folder ", folder, path8, stat);
2218
- });
2219
- this._watchers.push(w);
2220
- w.on("ready", () => {
2221
- resolve();
2222
- });
2288
+ const createUnreffedWatcher = (folder) => {
2289
+ const capturedHandles = [];
2290
+ const origWatch = import_node_fs9.default.watch;
2291
+ import_node_fs9.default.watch = ((...args) => {
2292
+ const handle = origWatch.apply(import_node_fs9.default, args);
2293
+ capturedHandles.push(handle);
2294
+ return handle;
2223
2295
  });
2224
- }
2225
- async function _walkAllFiles(folder, index) {
2226
- const w = import_chokidar.default.watch(folder, options);
2227
- w.on("unlink", (filename, stat) => {
2228
- stat;
2229
- debugLog(import_chalk6.default.cyan(`unlink in folder ${folder}`), filename);
2230
- const h = this._filenameToHash[filename];
2231
- if (h && index[h]) {
2232
- delete index[h];
2296
+ const w = import_chokidar.default.watch(folder, chokidarOptions);
2297
+ const unreffAll = () => {
2298
+ import_node_fs9.default.watch = origWatch;
2299
+ for (const h of capturedHandles) {
2300
+ h.unref();
2233
2301
  }
2234
- });
2235
- w.on("add", (filename, stat) => {
2236
- stat;
2237
- debugLog(import_chalk6.default.cyan(`add in folder ${folder}`), filename);
2238
- try {
2239
- const certificate = (0, import_node_opcua_crypto5.readCertificate)(filename);
2240
- const info = (0, import_node_opcua_crypto5.exploreCertificate)(certificate);
2241
- const fingerprint2 = makeFingerprint(certificate);
2242
- index[fingerprint2] = {
2243
- certificate,
2244
- filename
2245
- };
2246
- this._filenameToHash[filename] = fingerprint2;
2247
- debugLog(
2248
- import_chalk6.default.magenta("CERT"),
2249
- info.tbsCertificate.subjectFingerPrint,
2250
- info.tbsCertificate.serialNumber,
2251
- info.tbsCertificate.extensions?.authorityKeyIdentifier?.authorityCertIssuerFingerPrint
2252
- );
2253
- } catch (err) {
2254
- debugLog(`Walk files in folder ${folder} with file ${filename}`);
2255
- debugLog(err);
2302
+ };
2303
+ return { w, capturedHandles, unreffAll };
2304
+ };
2305
+ const promises = [
2306
+ this.#walkAllFiles(this.trustedFolder, this.#thumbs.trusted, createUnreffedWatcher),
2307
+ this.#walkAllFiles(this.issuersCertFolder, this.#thumbs.issuers.certs, createUnreffedWatcher),
2308
+ this.#walkAllFiles(this.rejectedFolder, this.#thumbs.rejected, createUnreffedWatcher),
2309
+ this.#walkCRLFiles(this.crlFolder, this.#thumbs.crl, createUnreffedWatcher),
2310
+ this.#walkCRLFiles(this.issuersCrlFolder, this.#thumbs.issuersCrl, createUnreffedWatcher)
2311
+ ];
2312
+ await Promise.all(promises);
2313
+ await this.#waitAndCheckCRLProcessingStatus();
2314
+ }
2315
+ async #walkCRLFiles(folder, index, createUnreffedWatcher) {
2316
+ await new Promise((resolve, _reject) => {
2317
+ const { w, unreffAll } = createUnreffedWatcher(folder);
2318
+ w.on("unlink", (filename) => {
2319
+ for (const [key, data] of index.entries()) {
2320
+ data.crls = data.crls.filter((c) => c.filename !== filename);
2321
+ if (data.crls.length === 0) {
2322
+ index.delete(key);
2323
+ }
2256
2324
  }
2257
2325
  });
2258
- w.on("change", (path8, stat) => {
2259
- stat;
2260
- debugLog("change in folder ", folder, path8);
2326
+ w.on("add", (filename) => {
2327
+ this.#onCrlFileAdded(index, filename);
2261
2328
  });
2262
- this._watchers.push(w);
2263
- await new Promise((resolve, _reject) => {
2264
- w.on("ready", () => {
2265
- debugLog("ready");
2266
- debugLog(Object.entries(index).map((kv) => kv[0].substring(0, 10)));
2267
- resolve();
2268
- });
2329
+ w.on("change", (changedPath) => {
2330
+ debugLog("change in folder ", folder, changedPath);
2331
+ });
2332
+ this.#watchers.push(w);
2333
+ w.on("ready", () => {
2334
+ unreffAll();
2335
+ resolve();
2269
2336
  });
2270
- }
2271
- const promises = [
2272
- _walkAllFiles.bind(this, this.trustedFolder, this._thumbs.trusted)(),
2273
- _walkAllFiles.bind(this, this.issuersCertFolder, this._thumbs.issuers.certs)(),
2274
- _walkAllFiles.bind(this, this.rejectedFolder, this._thumbs.rejected)(),
2275
- _walkCRLFiles.bind(this, this.crlFolder, this._thumbs.crl)(),
2276
- _walkCRLFiles.bind(this, this.issuersCrlFolder, this._thumbs.issuersCrl)()
2277
- ];
2278
- await Promise.all(promises);
2279
- await this.waitAndCheckCRLProcessingStatus();
2280
- }
2281
- // make sure that all crls have been processed.
2282
- async waitAndCheckCRLProcessingStatus() {
2283
- return new Promise((resolve, reject) => {
2284
- if (this._pending_crl_to_process === 0) {
2285
- setImmediate(resolve);
2286
- return;
2287
- }
2288
- if (this._on_crl_process) {
2289
- return reject(new Error("Internal Error"));
2290
- }
2291
- this._on_crl_process = resolve;
2292
- });
2293
- }
2294
- };
2295
-
2296
- // packages/node-opcua-pki/lib/ca/crypto_create_CA.ts
2297
- var import_command_line_args = __toESM(require("command-line-args"));
2298
- var import_command_line_usage = __toESM(require("command-line-usage"));
2299
- var epilog = "Copyright (c) sterfive - node-opcua - 2017-2026";
2300
- function get_offset_date(date, nbDays) {
2301
- const d = new Date(date.getTime());
2302
- d.setDate(d.getDate() + nbDays);
2303
- return d;
2304
- }
2305
- var today = /* @__PURE__ */ new Date();
2306
- var yesterday = get_offset_date(today, -1);
2307
- var two_years_ago = get_offset_date(today, -2 * 365);
2308
- var next_year = get_offset_date(today, 365);
2309
- var gLocalConfig = {};
2310
- var g_certificateAuthority;
2311
- async function construct_CertificateAuthority2(subject) {
2312
- (0, import_node_assert11.default)(typeof gLocalConfig.CAFolder === "string", "expecting a CAFolder in config");
2313
- (0, import_node_assert11.default)(typeof gLocalConfig.keySize === "number", "expecting a keySize in config");
2314
- if (!g_certificateAuthority) {
2315
- g_certificateAuthority = new CertificateAuthority({
2316
- keySize: gLocalConfig.keySize,
2317
- location: gLocalConfig.CAFolder,
2318
- subject
2319
- });
2320
- await g_certificateAuthority.initialize();
2321
- }
2322
- }
2323
- var certificateManager;
2324
- async function construct_CertificateManager() {
2325
- (0, import_node_assert11.default)(typeof gLocalConfig.PKIFolder === "string", "expecting a PKIFolder in config");
2326
- if (!certificateManager) {
2327
- certificateManager = new CertificateManager({
2328
- keySize: gLocalConfig.keySize,
2329
- location: gLocalConfig.PKIFolder
2330
2337
  });
2331
- await certificateManager.initialize();
2332
2338
  }
2333
- }
2334
- function default_template_content() {
2335
- if (process.pkg?.entrypoint) {
2336
- const a = import_node_fs10.default.readFileSync(import_node_path7.default.join(__dirname, "../../bin/pki_config.example.js"), "utf8");
2337
- return a;
2338
- }
2339
- function find_default_config_template() {
2340
- const rootFolder = find_module_root_folder();
2341
- const configName = "pki_config.example.js";
2342
- let default_config_template2 = import_node_path7.default.join(rootFolder, "bin", configName);
2343
- if (!import_node_fs10.default.existsSync(default_config_template2)) {
2344
- default_config_template2 = import_node_path7.default.join(__dirname, "..", configName);
2345
- if (!import_node_fs10.default.existsSync(default_config_template2)) {
2346
- default_config_template2 = import_node_path7.default.join(__dirname, `../bin/${configName}`);
2339
+ async #walkAllFiles(folder, index, createUnreffedWatcher) {
2340
+ const { w, unreffAll } = createUnreffedWatcher(folder);
2341
+ w.on("unlink", (filename) => {
2342
+ debugLog(import_chalk6.default.cyan(`unlink in folder ${folder}`), filename);
2343
+ const h = this.#filenameToHash.get(filename);
2344
+ if (h && index.has(h)) {
2345
+ index.delete(h);
2347
2346
  }
2348
- }
2349
- return default_config_template2;
2350
- }
2351
- const default_config_template = find_default_config_template();
2352
- (0, import_node_assert11.default)(import_node_fs10.default.existsSync(default_config_template));
2353
- const default_config_template_content = import_node_fs10.default.readFileSync(default_config_template, "utf8");
2354
- return default_config_template_content;
2355
- }
2356
- function find_module_root_folder() {
2357
- let rootFolder = import_node_path7.default.join(__dirname);
2358
- for (let i = 0; i < 4; i++) {
2359
- if (import_node_fs10.default.existsSync(import_node_path7.default.join(rootFolder, "package.json"))) {
2360
- return rootFolder;
2361
- }
2362
- rootFolder = import_node_path7.default.join(rootFolder, "..");
2363
- }
2364
- (0, import_node_assert11.default)(import_node_fs10.default.existsSync(import_node_path7.default.join(rootFolder, "package.json")), "root folder must have a package.json file");
2365
- return rootFolder;
2366
- }
2367
- async function readConfiguration(argv) {
2368
- if (argv.silent) {
2369
- g_config.silent = true;
2370
- } else {
2371
- g_config.silent = false;
2372
- }
2373
- const fqdn2 = await extractFullyQualifiedDomainName();
2374
- const hostname = import_node_os4.default.hostname();
2375
- let certificateDir;
2376
- function performSubstitution(str) {
2377
- str = str.replace("{CWD}", process.cwd());
2378
- if (certificateDir) {
2379
- str = str.replace("{root}", certificateDir);
2380
- }
2381
- if (gLocalConfig?.PKIFolder) {
2382
- str = str.replace("{PKIFolder}", gLocalConfig.PKIFolder);
2383
- }
2384
- str = str.replace("{hostname}", hostname);
2385
- str = str.replace("%FQDN%", fqdn2);
2386
- return str;
2387
- }
2388
- function prepare(file) {
2389
- const tmp = import_node_path7.default.resolve(performSubstitution(file));
2390
- return makePath(tmp);
2391
- }
2392
- certificateDir = argv.root;
2393
- (0, import_node_assert11.default)(typeof certificateDir === "string");
2394
- certificateDir = prepare(certificateDir);
2395
- mkdirRecursiveSync(certificateDir);
2396
- (0, import_node_assert11.default)(import_node_fs10.default.existsSync(certificateDir));
2397
- const default_config = import_node_path7.default.join(certificateDir, "config.js");
2398
- if (!import_node_fs10.default.existsSync(default_config)) {
2399
- debugLog(import_chalk7.default.yellow(" Creating default g_config file "), import_chalk7.default.cyan(default_config));
2400
- const default_config_template_content = default_template_content();
2401
- import_node_fs10.default.writeFileSync(default_config, default_config_template_content);
2402
- } else {
2403
- debugLog(import_chalk7.default.yellow(" using g_config file "), import_chalk7.default.cyan(default_config));
2404
- }
2405
- if (!import_node_fs10.default.existsSync(default_config)) {
2406
- debugLog(import_chalk7.default.redBright(" cannot find config file ", default_config));
2407
- }
2408
- const defaultRandomFile = import_node_path7.default.join(import_node_path7.default.dirname(default_config), "random.rnd");
2409
- setEnv("RANDFILE", defaultRandomFile);
2410
- const _require = (0, import_node_module.createRequire)(__filename);
2411
- gLocalConfig = _require(default_config);
2412
- gLocalConfig.subject = new import_node_opcua_crypto6.Subject(gLocalConfig.subject || "");
2413
- if (argv.subject) {
2414
- gLocalConfig.subject = new import_node_opcua_crypto6.Subject(argv.subject);
2415
- }
2416
- if (!gLocalConfig.subject.commonName) {
2417
- throw new Error("subject must have a Common Name");
2418
- }
2419
- gLocalConfig.certificateDir = certificateDir;
2420
- let CAFolder = argv.CAFolder || import_node_path7.default.join(certificateDir, "CA");
2421
- CAFolder = prepare(CAFolder);
2422
- gLocalConfig.CAFolder = CAFolder;
2423
- gLocalConfig.PKIFolder = import_node_path7.default.join(gLocalConfig.certificateDir, "PKI");
2424
- if (argv.PKIFolder) {
2425
- gLocalConfig.PKIFolder = prepare(argv.PKIFolder);
2426
- }
2427
- gLocalConfig.PKIFolder = prepare(gLocalConfig.PKIFolder);
2428
- if (argv.privateKey) {
2429
- gLocalConfig.privateKey = prepare(argv.privateKey);
2430
- }
2431
- if (argv.applicationUri) {
2432
- gLocalConfig.applicationUri = performSubstitution(argv.applicationUri);
2433
- }
2434
- if (argv.output) {
2435
- gLocalConfig.outputFile = argv.output;
2436
- }
2437
- gLocalConfig.altNames = [];
2438
- if (argv.altNames) {
2439
- gLocalConfig.altNames = argv.altNames.split(";");
2440
- }
2441
- gLocalConfig.dns = [getFullyQualifiedDomainName()];
2442
- if (argv.dns) {
2443
- gLocalConfig.dns = argv.dns.split(",").map(performSubstitution);
2444
- }
2445
- gLocalConfig.ip = [];
2446
- if (argv.ip) {
2447
- gLocalConfig.ip = argv.ip.split(",");
2448
- }
2449
- if (argv.keySize) {
2450
- const v = argv.keySize;
2451
- if (v !== 1024 && v !== 2048 && v !== 3072 && v !== 4096) {
2452
- throw new Error(`invalid keysize specified ${v} should be 1024,2048,3072 or 4096`);
2453
- }
2454
- gLocalConfig.keySize = argv.keySize;
2455
- }
2456
- if (argv.validity) {
2457
- gLocalConfig.validity = argv.validity;
2458
- }
2459
- }
2460
- async function createDefaultCertificate(base_name, prefix, key_length, applicationUri, dev) {
2461
- (0, import_node_assert11.default)(key_length === 1024 || key_length === 2048 || key_length === 3072 || key_length === 4096);
2462
- const private_key_file = makePath(base_name, `${prefix}key_${key_length}.pem`);
2463
- const public_key_file = makePath(base_name, `${prefix}public_key_${key_length}.pub`);
2464
- const certificate_file = makePath(base_name, `${prefix}cert_${key_length}.pem`);
2465
- const certificate_file_outofdate = makePath(base_name, `${prefix}cert_${key_length}_outofdate.pem`);
2466
- const certificate_file_not_active_yet = makePath(base_name, `${prefix}cert_${key_length}_not_active_yet.pem`);
2467
- const certificate_revoked = makePath(base_name, `${prefix}cert_${key_length}_revoked.pem`);
2468
- const self_signed_certificate_file = makePath(base_name, `${prefix}selfsigned_cert_${key_length}.pem`);
2469
- const fqdn2 = getFullyQualifiedDomainName();
2470
- const hostname = import_node_os4.default.hostname();
2471
- const dns2 = [
2472
- // for conformance reason, localhost shall not be present in the DNS field of COP
2473
- // ***FORBIDEN** "localhost",
2474
- getFullyQualifiedDomainName()
2475
- ];
2476
- if (hostname !== fqdn2) {
2477
- dns2.push(hostname);
2478
- }
2479
- const ip = [];
2480
- async function createCertificateIfNotExist(certificate, private_key, applicationUri2, startDate, validity) {
2481
- if (import_node_fs10.default.existsSync(certificate)) {
2482
- warningLog(import_chalk7.default.yellow(" certificate"), import_chalk7.default.cyan(certificate), import_chalk7.default.yellow(" already exists => skipping"));
2483
- return "";
2484
- } else {
2485
- return await createCertificate(certificate, private_key, applicationUri2, startDate, validity);
2486
- }
2487
- }
2488
- async function createCertificate(certificate, privateKey, applicationUri2, startDate, validity) {
2489
- const certificateSigningRequestFile = `${certificate}.csr`;
2490
- const configFile = makePath(base_name, "../certificates/PKI/own/openssl.cnf");
2491
- const dns3 = [import_node_os4.default.hostname()];
2492
- const ip2 = ["127.0.0.1"];
2493
- const params = {
2494
- applicationUri: applicationUri2,
2495
- privateKey,
2496
- rootDir: ".",
2497
- configFile,
2498
- dns: dns3,
2499
- ip: ip2,
2500
- purpose: import_node_opcua_crypto6.CertificatePurpose.ForApplication
2501
- };
2502
- await createCertificateSigningRequestWithOpenSSL(certificateSigningRequestFile, params);
2503
- return await g_certificateAuthority.signCertificateRequest(certificate, certificateSigningRequestFile, {
2504
- applicationUri: applicationUri2,
2505
- dns: dns3,
2506
- ip: ip2,
2507
- startDate,
2508
- validity
2509
2347
  });
2510
- }
2511
- async function createSelfSignedCertificate2(certificate, private_key, applicationUri2, startDate, validity) {
2512
- await g_certificateAuthority.createSelfSignedCertificate(certificate, private_key, {
2513
- applicationUri: applicationUri2,
2514
- dns: dns2,
2515
- ip,
2516
- startDate,
2517
- validity
2348
+ w.on("add", (filename) => {
2349
+ debugLog(import_chalk6.default.cyan(`add in folder ${folder}`), filename);
2350
+ try {
2351
+ const certificate = (0, import_node_opcua_crypto5.readCertificate)(filename);
2352
+ const info = (0, import_node_opcua_crypto5.exploreCertificate)(certificate);
2353
+ const fingerprint = makeFingerprint(certificate);
2354
+ index.set(fingerprint, { certificate, filename, info });
2355
+ this.#filenameToHash.set(filename, fingerprint);
2356
+ debugLog(
2357
+ import_chalk6.default.magenta("CERT"),
2358
+ info.tbsCertificate.subjectFingerPrint,
2359
+ info.tbsCertificate.serialNumber,
2360
+ info.tbsCertificate.extensions?.authorityKeyIdentifier?.authorityCertIssuerFingerPrint
2361
+ );
2362
+ } catch (err) {
2363
+ debugLog(`Walk files in folder ${folder} with file ${filename}`);
2364
+ debugLog(err);
2365
+ }
2518
2366
  });
2519
- }
2520
- async function revoke_certificate(certificate) {
2521
- await g_certificateAuthority.revokeCertificate(certificate, {});
2522
- }
2523
- async function createPrivateKeyIfNotExist(privateKey, keyLength) {
2524
- if (import_node_fs10.default.existsSync(privateKey)) {
2525
- warningLog(import_chalk7.default.yellow(" privateKey"), import_chalk7.default.cyan(privateKey), import_chalk7.default.yellow(" already exists => skipping"));
2526
- return;
2527
- } else {
2528
- await (0, import_node_opcua_crypto6.generatePrivateKeyFile)(privateKey, keyLength);
2529
- }
2530
- }
2531
- displaySubtitle(` create private key :${private_key_file}`);
2532
- await createPrivateKeyIfNotExist(private_key_file, key_length);
2533
- displaySubtitle(` extract public key ${public_key_file} from private key `);
2534
- await getPublicKeyFromPrivateKey(private_key_file, public_key_file);
2535
- displaySubtitle(` create Certificate ${certificate_file}`);
2536
- await createCertificateIfNotExist(certificate_file, private_key_file, applicationUri, yesterday, 365);
2537
- displaySubtitle(` create self signed Certificate ${self_signed_certificate_file}`);
2538
- if (import_node_fs10.default.existsSync(self_signed_certificate_file)) {
2539
- return;
2540
- }
2541
- await createSelfSignedCertificate2(self_signed_certificate_file, private_key_file, applicationUri, yesterday, 365);
2542
- if (dev) {
2543
- await createCertificateIfNotExist(certificate_file_outofdate, private_key_file, applicationUri, two_years_ago, 365);
2544
- await createCertificateIfNotExist(certificate_file_not_active_yet, private_key_file, applicationUri, next_year, 365);
2545
- if (!import_node_fs10.default.existsSync(certificate_revoked)) {
2546
- const certificate = await createCertificateIfNotExist(
2547
- certificate_revoked,
2548
- private_key_file,
2549
- `${applicationUri}Revoked`,
2550
- // make sure we used a uniq URI here
2551
- yesterday,
2552
- 365
2553
- );
2554
- warningLog(" certificate to revoke => ", certificate);
2555
- revoke_certificate(certificate_revoked);
2556
- }
2557
- }
2558
- }
2559
- async function wrap(func) {
2560
- try {
2561
- await func();
2562
- } catch (err) {
2563
- console.log(err.message);
2564
- }
2565
- }
2566
- async function create_default_certificates(dev) {
2567
- (0, import_node_assert11.default)(gLocalConfig);
2568
- const base_name = gLocalConfig.certificateDir || "";
2569
- (0, import_node_assert11.default)(import_node_fs10.default.existsSync(base_name));
2570
- let clientURN;
2571
- let serverURN;
2572
- let discoveryServerURN;
2573
- wrap(async () => {
2574
- await extractFullyQualifiedDomainName();
2575
- const hostname = import_node_os4.default.hostname();
2576
- const fqdn2 = getFullyQualifiedDomainName();
2577
- warningLog(import_chalk7.default.yellow(" hostname = "), import_chalk7.default.cyan(hostname));
2578
- warningLog(import_chalk7.default.yellow(" fqdn = "), import_chalk7.default.cyan(fqdn2));
2579
- clientURN = makeApplicationUrn(hostname, "NodeOPCUA-Client");
2580
- serverURN = makeApplicationUrn(hostname, "NodeOPCUA-Server");
2581
- discoveryServerURN = makeApplicationUrn(hostname, "NodeOPCUA-DiscoveryServer");
2582
- displayTitle("Create Application Certificate for Server & its private key");
2583
- await createDefaultCertificate(base_name, "client_", 1024, clientURN, dev);
2584
- await createDefaultCertificate(base_name, "client_", 2048, clientURN, dev);
2585
- await createDefaultCertificate(base_name, "client_", 3072, clientURN, dev);
2586
- await createDefaultCertificate(base_name, "client_", 4096, clientURN, dev);
2587
- displayTitle("Create Application Certificate for Client & its private key");
2588
- await createDefaultCertificate(base_name, "server_", 1024, serverURN, dev);
2589
- await createDefaultCertificate(base_name, "server_", 2048, serverURN, dev);
2590
- await createDefaultCertificate(base_name, "server_", 3072, serverURN, dev);
2591
- await createDefaultCertificate(base_name, "server_", 4096, serverURN, dev);
2592
- displayTitle("Create Application Certificate for DiscoveryServer & its private key");
2593
- await createDefaultCertificate(base_name, "discoveryServer_", 1024, discoveryServerURN, dev);
2594
- await createDefaultCertificate(base_name, "discoveryServer_", 2048, discoveryServerURN, dev);
2595
- await createDefaultCertificate(base_name, "discoveryServer_", 3072, discoveryServerURN, dev);
2596
- await createDefaultCertificate(base_name, "discoveryServer_", 4096, discoveryServerURN, dev);
2597
- });
2598
- }
2599
- async function createDefaultCertificates(dev) {
2600
- await construct_CertificateAuthority2("");
2601
- await construct_CertificateManager();
2602
- await create_default_certificates(dev);
2603
- }
2604
- var commonOptions = [
2605
- {
2606
- name: "root",
2607
- alias: "r",
2608
- type: String,
2609
- defaultValue: "{CWD}/certificates",
2610
- description: "the location of the Certificate folder"
2611
- },
2612
- {
2613
- name: "CAFolder",
2614
- alias: "c",
2615
- type: String,
2616
- defaultValue: "{root}/CA",
2617
- description: "the location of the Certificate Authority folder"
2618
- },
2619
- { name: "PKIFolder", type: String, defaultValue: "{root}/PKI", description: "the location of the Public Key Infrastructure" },
2620
- { name: "silent", type: Boolean, defaultValue: false, description: "minimize output" },
2621
- {
2622
- name: "privateKey",
2623
- alias: "p",
2624
- type: String,
2625
- defaultValue: "{PKIFolder}/own/private_key.pem",
2626
- description: "the private key to use to generate certificate"
2627
- },
2628
- {
2629
- name: "keySize",
2630
- alias: "k",
2631
- type: Number,
2632
- defaultValue: 2048,
2633
- description: "the private key size in bits (1024|2048|3072|4096)"
2634
- },
2635
- { name: "help", alias: "h", type: Boolean, description: "display this help" }
2636
- ];
2637
- function getOptions(names) {
2638
- return commonOptions.filter((o) => names.includes(o.name) || o.name === "help" || o.name === "silent");
2639
- }
2640
- function showHelp(command, description, options, usage) {
2641
- const sections = [
2642
- {
2643
- header: `Command: ${command}`,
2644
- content: description
2645
- },
2646
- {
2647
- header: "Usage",
2648
- content: usage || `$0 ${command} [options]`
2649
- },
2650
- {
2651
- header: "Options",
2652
- optionList: options
2653
- }
2654
- ];
2655
- console.log((0, import_command_line_usage.default)(sections));
2656
- }
2657
- async function main(argumentsList) {
2658
- const mainDefinitions = [{ name: "command", defaultOption: true }];
2659
- let mainOptions;
2660
- try {
2661
- mainOptions = (0, import_command_line_args.default)(mainDefinitions, { argv: argumentsList, stopAtFirstUnknown: true });
2662
- } catch (err) {
2663
- console.log(err.message);
2664
- return;
2665
- }
2666
- const argv = mainOptions._unknown || [];
2667
- const command = mainOptions.command;
2668
- if (!command || command === "help") {
2669
- console.log(
2670
- (0, import_command_line_usage.default)([
2671
- {
2672
- header: "node-opcua-pki",
2673
- content: `PKI management for node-opcua
2674
-
2675
- ${epilog}`
2676
- },
2677
- {
2678
- header: "Commands",
2679
- content: [
2680
- { name: "demo", summary: "create default certificate for node-opcua demos" },
2681
- { name: "createCA", summary: "create a Certificate Authority" },
2682
- { name: "createPKI", summary: "create a Public Key Infrastructure" },
2683
- { name: "certificate", summary: "create a new certificate" },
2684
- { name: "revoke <certificateFile>", summary: "revoke a existing certificate" },
2685
- { name: "csr", summary: "create a certificate signing request" },
2686
- { name: "sign", summary: "validate a certificate signing request and generate a certificate" },
2687
- { name: "dump <certificateFile>", summary: "display a certificate" },
2688
- { name: "toder <pemCertificate>", summary: "convert a certificate to a DER format with finger print" },
2689
- { name: "fingerprint <certificateFile>", summary: "print the certificate fingerprint" },
2690
- { name: "version", summary: "display the version number" }
2691
- ]
2692
- }
2693
- ])
2694
- );
2695
- return;
2696
- }
2697
- if (command === "version") {
2698
- const rootFolder = find_module_root_folder();
2699
- const pkg = JSON.parse(import_node_fs10.default.readFileSync(import_node_path7.default.join(rootFolder, "package.json"), "utf-8"));
2700
- console.log(pkg.version);
2701
- return;
2702
- }
2703
- if (command === "demo") {
2704
- const optionsDef = [
2705
- ...getOptions(["root", "silent"]),
2706
- { name: "dev", type: Boolean, description: "create all sort of fancy certificates for dev testing purposes" },
2707
- { name: "clean", type: Boolean, description: "Purge existing directory [use with care!]" }
2708
- ];
2709
- const local_argv = (0, import_command_line_args.default)(optionsDef, { argv });
2710
- if (local_argv.help)
2711
- return showHelp(
2712
- "demo",
2713
- "create default certificate for node-opcua demos",
2714
- optionsDef,
2715
- "$0 demo [--dev] [--silent] [--clean]"
2716
- );
2717
- await wrap(async () => {
2718
- await ensure_openssl_installed();
2719
- displayChapter("Create Demo certificates");
2720
- displayTitle("reading configuration");
2721
- await readConfiguration(local_argv);
2722
- if (local_argv.clean) {
2723
- displayTitle("Cleaning old certificates");
2724
- (0, import_node_assert11.default)(gLocalConfig);
2725
- const certificateDir = gLocalConfig.certificateDir || "";
2726
- const files = await import_node_fs10.default.promises.readdir(certificateDir);
2727
- for (const file of files) {
2728
- if (file.includes(".pem") || file.includes(".pub")) {
2729
- await import_node_fs10.default.promises.unlink(import_node_path7.default.join(certificateDir, file));
2730
- }
2367
+ w.on("change", (changedPath) => {
2368
+ debugLog(import_chalk6.default.cyan(`change in folder ${folder}`), changedPath);
2369
+ try {
2370
+ const certificate = (0, import_node_opcua_crypto5.readCertificate)(changedPath);
2371
+ const newFingerprint = makeFingerprint(certificate);
2372
+ const oldHash = this.#filenameToHash.get(changedPath);
2373
+ if (oldHash && oldHash !== newFingerprint) {
2374
+ index.delete(oldHash);
2731
2375
  }
2732
- mkdirRecursiveSync(certificateDir);
2376
+ index.set(newFingerprint, { certificate, filename: changedPath, info: (0, import_node_opcua_crypto5.exploreCertificate)(certificate) });
2377
+ this.#filenameToHash.set(changedPath, newFingerprint);
2378
+ } catch (err) {
2379
+ debugLog(`change event: failed to re-read ${changedPath}`, err);
2733
2380
  }
2734
- displayTitle("create certificates");
2735
- await createDefaultCertificates(local_argv.dev);
2736
- displayChapter("Demo certificates CREATED");
2737
- });
2738
- return;
2739
- }
2740
- if (command === "createCA") {
2741
- const optionsDef = [
2742
- ...getOptions(["root", "CAFolder", "keySize", "silent"]),
2743
- { name: "subject", type: String, defaultValue: defaultSubject, description: "the CA certificate subject" }
2744
- ];
2745
- const local_argv = (0, import_command_line_args.default)(optionsDef, { argv });
2746
- if (local_argv.help) return showHelp("createCA", "create a Certificate Authority", optionsDef);
2747
- await wrap(async () => {
2748
- await ensure_openssl_installed();
2749
- await readConfiguration(local_argv);
2750
- await construct_CertificateAuthority2(local_argv.subject);
2751
- });
2752
- return;
2753
- }
2754
- if (command === "createPKI") {
2755
- const optionsDef = getOptions(["root", "PKIFolder", "keySize", "silent"]);
2756
- const local_argv = (0, import_command_line_args.default)(optionsDef, { argv });
2757
- if (local_argv.help) return showHelp("createPKI", "create a Public Key Infrastructure", optionsDef);
2758
- await wrap(async () => {
2759
- await readConfiguration(local_argv);
2760
- await construct_CertificateManager();
2761
2381
  });
2762
- return;
2763
- }
2764
- if (command === "certificate") {
2765
- const optionsDef = [
2766
- ...getOptions(["root", "CAFolder", "PKIFolder", "privateKey", "silent"]),
2767
- {
2768
- name: "applicationUri",
2769
- alias: "a",
2770
- type: String,
2771
- defaultValue: "urn:{hostname}:Node-OPCUA-Server",
2772
- description: "the application URI"
2773
- },
2774
- {
2775
- name: "output",
2776
- alias: "o",
2777
- type: String,
2778
- defaultValue: "my_certificate.pem",
2779
- description: "the name of the generated certificate =>"
2780
- },
2781
- {
2782
- name: "selfSigned",
2783
- alias: "s",
2784
- type: Boolean,
2785
- defaultValue: false,
2786
- description: "if true, certificate will be self-signed"
2787
- },
2788
- { name: "validity", alias: "v", type: Number, description: "the certificate validity in days" },
2789
- {
2790
- name: "dns",
2791
- type: String,
2792
- defaultValue: "{hostname}",
2793
- description: "the list of valid domain name (comma separated)"
2794
- },
2795
- { name: "ip", type: String, defaultValue: "", description: "the list of valid IPs (comma separated)" },
2796
- {
2797
- name: "subject",
2798
- type: String,
2799
- defaultValue: "",
2800
- description: "the certificate subject ( for instance C=FR/ST=Centre/L=Orleans/O=SomeOrganization/CN=Hello )"
2801
- }
2802
- ];
2803
- const local_argv = (0, import_command_line_args.default)(optionsDef, { argv });
2804
- if (local_argv.help || !local_argv.applicationUri || !local_argv.output)
2805
- return showHelp("certificate", "create a new certificate", optionsDef);
2806
- async function command_certificate(local_argv2) {
2807
- const selfSigned = !!local_argv2.selfSigned;
2808
- if (!selfSigned) {
2809
- await command_full_certificate(local_argv2);
2810
- } else {
2811
- await command_selfsigned_certificate(local_argv2);
2812
- }
2813
- }
2814
- async function command_selfsigned_certificate(local_argv2) {
2815
- const _fqdn = await extractFullyQualifiedDomainName();
2816
- await readConfiguration(local_argv2);
2817
- await construct_CertificateManager();
2818
- displaySubtitle(` create self signed Certificate ${gLocalConfig.outputFile}`);
2819
- let subject = local_argv2.subject && local_argv2.subject.length > 1 ? new import_node_opcua_crypto6.Subject(local_argv2.subject) : gLocalConfig.subject || "";
2820
- subject = JSON.parse(JSON.stringify(subject));
2821
- const params = {
2822
- applicationUri: gLocalConfig.applicationUri || "",
2823
- dns: gLocalConfig.dns || [],
2824
- ip: gLocalConfig.ip || [],
2825
- outputFile: gLocalConfig.outputFile || "self_signed_certificate.pem",
2826
- startDate: gLocalConfig.startDate || /* @__PURE__ */ new Date(),
2827
- subject,
2828
- validity: gLocalConfig.validity || 365
2829
- };
2830
- await certificateManager.createSelfSignedCertificate(params);
2831
- }
2832
- async function command_full_certificate(local_argv2) {
2833
- await readConfiguration(local_argv2);
2834
- await construct_CertificateManager();
2835
- await construct_CertificateAuthority2("");
2836
- (0, import_node_assert11.default)(import_node_fs10.default.existsSync(gLocalConfig.CAFolder || ""), " CA folder must exist");
2837
- gLocalConfig.privateKey = void 0;
2838
- gLocalConfig.subject = local_argv2.subject && local_argv2.subject.length > 1 ? local_argv2.subject : gLocalConfig.subject;
2839
- const csr_file = await certificateManager.createCertificateRequest(
2840
- gLocalConfig
2841
- );
2842
- if (!csr_file) {
2843
- return;
2844
- }
2845
- warningLog(" csr_file = ", csr_file);
2846
- const certificate = csr_file.replace(".csr", ".pem");
2847
- if (import_node_fs10.default.existsSync(certificate)) {
2848
- throw new Error(` File ${certificate} already exist`);
2849
- }
2850
- await g_certificateAuthority.signCertificateRequest(
2851
- certificate,
2852
- csr_file,
2853
- gLocalConfig
2854
- );
2855
- (0, import_node_assert11.default)(typeof gLocalConfig.outputFile === "string");
2856
- import_node_fs10.default.writeFileSync(gLocalConfig.outputFile || "", import_node_fs10.default.readFileSync(certificate, "ascii"));
2857
- }
2858
- await wrap(async () => await command_certificate(local_argv));
2859
- return;
2860
- }
2861
- if (command === "revoke") {
2862
- const optionsDef = [{ name: "certificateFile", type: String, defaultOption: true }, ...getOptions(["root", "CAFolder"])];
2863
- const local_argv = (0, import_command_line_args.default)(optionsDef, { argv });
2864
- if (local_argv.help || !local_argv.certificateFile)
2865
- return showHelp(
2866
- "revoke <certificateFile>",
2867
- "revoke a existing certificate",
2868
- optionsDef,
2869
- "$0 revoke my_certificate.pem"
2870
- );
2871
- async function revoke_certificate(certificate) {
2872
- await g_certificateAuthority.revokeCertificate(certificate, {});
2873
- }
2874
- await wrap(async () => {
2875
- const certificate = import_node_path7.default.resolve(local_argv.certificateFile);
2876
- warningLog(import_chalk7.default.yellow(" Certificate to revoke : "), import_chalk7.default.cyan(certificate));
2877
- if (!import_node_fs10.default.existsSync(certificate)) {
2878
- throw new Error(`cannot find certificate to revoke ${certificate}`);
2879
- }
2880
- await readConfiguration(local_argv);
2881
- await construct_CertificateAuthority2("");
2882
- await revoke_certificate(certificate);
2883
- warningLog("done ... ");
2884
- warningLog(" crl = ", g_certificateAuthority.revocationList);
2885
- warningLog("\nyou should now publish the new Certificate Revocation List");
2382
+ this.#watchers.push(w);
2383
+ await new Promise((resolve, _reject) => {
2384
+ w.on("ready", () => {
2385
+ unreffAll();
2386
+ debugLog("ready");
2387
+ debugLog([...index.keys()].map((k) => k.substring(0, 10)));
2388
+ resolve();
2389
+ });
2886
2390
  });
2887
- return;
2888
2391
  }
2889
- if (command === "csr") {
2890
- const optionsDef = [
2891
- ...getOptions(["root", "PKIFolder", "privateKey", "silent"]),
2892
- {
2893
- name: "applicationUri",
2894
- alias: "a",
2895
- type: String,
2896
- defaultValue: "urn:{hostname}:Node-OPCUA-Server",
2897
- description: "the application URI"
2898
- },
2899
- {
2900
- name: "output",
2901
- alias: "o",
2902
- type: String,
2903
- defaultValue: "my_certificate_signing_request.csr",
2904
- description: "the name of the generated signing_request"
2905
- },
2906
- {
2907
- name: "dns",
2908
- type: String,
2909
- defaultValue: "{hostname}",
2910
- description: "the list of valid domain name (comma separated)"
2911
- },
2912
- { name: "ip", type: String, defaultValue: "", description: "the list of valid IPs (comma separated)" },
2913
- {
2914
- name: "subject",
2915
- type: String,
2916
- defaultValue: "/CN=Certificate",
2917
- description: "the certificate subject ( for instance /C=FR/ST=Centre/L=Orleans/O=SomeOrganization/CN=Hello )"
2918
- }
2919
- ];
2920
- const local_argv = (0, import_command_line_args.default)(optionsDef, { argv });
2921
- if (local_argv.help) return showHelp("csr", "create a certificate signing request", optionsDef);
2922
- await wrap(async () => {
2923
- await readConfiguration(local_argv);
2924
- if (!import_node_fs10.default.existsSync(gLocalConfig.PKIFolder || "")) {
2925
- warningLog("PKI folder must exist");
2926
- }
2927
- await construct_CertificateManager();
2928
- if (!gLocalConfig.outputFile || import_node_fs10.default.existsSync(gLocalConfig.outputFile)) {
2929
- throw new Error(` File ${gLocalConfig.outputFile} already exist`);
2930
- }
2931
- gLocalConfig.privateKey = void 0;
2932
- gLocalConfig.subject = local_argv.subject && local_argv.subject.length > 1 ? local_argv.subject : gLocalConfig.subject;
2933
- const internal_csr_file = await certificateManager.createCertificateRequest(
2934
- gLocalConfig
2935
- );
2936
- if (!internal_csr_file) {
2937
- return;
2938
- }
2939
- if (!gLocalConfig.outputFile) {
2940
- warningLog("please specify a output file");
2392
+ // make sure that all crls have been processed.
2393
+ async #waitAndCheckCRLProcessingStatus() {
2394
+ return new Promise((resolve, reject) => {
2395
+ if (this.#pendingCrlToProcess === 0) {
2396
+ setImmediate(resolve);
2941
2397
  return;
2942
2398
  }
2943
- const csr = await import_node_fs10.default.promises.readFile(internal_csr_file, "utf-8");
2944
- import_node_fs10.default.writeFileSync(gLocalConfig.outputFile || "", csr, "utf-8");
2945
- warningLog("Subject = ", gLocalConfig.subject);
2946
- warningLog("applicationUri = ", gLocalConfig.applicationUri);
2947
- warningLog("altNames = ", gLocalConfig.altNames);
2948
- warningLog("dns = ", gLocalConfig.dns);
2949
- warningLog("ip = ", gLocalConfig.ip);
2950
- warningLog("CSR file = ", gLocalConfig.outputFile);
2951
- });
2952
- return;
2953
- }
2954
- if (command === "sign") {
2955
- const optionsDef = [
2956
- ...getOptions(["root", "CAFolder", "silent"]),
2957
- { name: "csr", alias: "i", type: String, defaultValue: "my_certificate_signing_request.csr", description: "the csr" },
2958
- {
2959
- name: "output",
2960
- alias: "o",
2961
- type: String,
2962
- defaultValue: "my_certificate.pem",
2963
- description: "the name of the generated certificate"
2964
- },
2965
- { name: "validity", alias: "v", type: Number, defaultValue: 365, description: "the certificate validity in days" }
2966
- ];
2967
- const local_argv = (0, import_command_line_args.default)(optionsDef, { argv });
2968
- if (local_argv.help || !local_argv.csr || !local_argv.output)
2969
- return showHelp("sign", "validate a certificate signing request and generate a certificate", optionsDef);
2970
- await wrap(async () => {
2971
- await readConfiguration(local_argv);
2972
- if (!import_node_fs10.default.existsSync(gLocalConfig.CAFolder || "")) {
2973
- throw new Error(`CA folder must exist:${gLocalConfig.CAFolder}`);
2974
- }
2975
- await construct_CertificateAuthority2("");
2976
- const csr_file = import_node_path7.default.resolve(local_argv.csr || "");
2977
- if (!import_node_fs10.default.existsSync(csr_file)) {
2978
- throw new Error(`Certificate signing request doesn't exist: ${csr_file}`);
2979
- }
2980
- const certificate = import_node_path7.default.resolve(local_argv.output || csr_file.replace(".csr", ".pem"));
2981
- if (import_node_fs10.default.existsSync(certificate)) {
2982
- throw new Error(` File ${certificate} already exist`);
2399
+ if (this.#onCrlProcess) {
2400
+ return reject(new Error("Internal Error"));
2983
2401
  }
2984
- await g_certificateAuthority.signCertificateRequest(
2985
- certificate,
2986
- csr_file,
2987
- gLocalConfig
2988
- );
2989
- (0, import_node_assert11.default)(typeof gLocalConfig.outputFile === "string");
2990
- import_node_fs10.default.writeFileSync(gLocalConfig.outputFile || "", import_node_fs10.default.readFileSync(certificate, "ascii"));
2991
- });
2992
- return;
2993
- }
2994
- if (command === "dump") {
2995
- const optionsDef = [
2996
- { name: "certificateFile", type: String, defaultOption: true },
2997
- { name: "help", alias: "h", type: Boolean }
2998
- ];
2999
- const local_argv = (0, import_command_line_args.default)(optionsDef, { argv });
3000
- if (local_argv.help || !local_argv.certificateFile)
3001
- return showHelp("dump <certificateFile>", "display a certificate", optionsDef);
3002
- await wrap(async () => {
3003
- const data = await dumpCertificate(local_argv.certificateFile);
3004
- warningLog(data);
3005
- });
3006
- return;
3007
- }
3008
- if (command === "toder") {
3009
- const optionsDef = [
3010
- { name: "pemCertificate", type: String, defaultOption: true },
3011
- { name: "help", alias: "h", type: Boolean }
3012
- ];
3013
- const local_argv = (0, import_command_line_args.default)(optionsDef, { argv });
3014
- if (local_argv.help || !local_argv.pemCertificate)
3015
- return showHelp("toder <pemCertificate>", "convert a certificate to a DER format with finger print", optionsDef);
3016
- await wrap(async () => {
3017
- await toDer(local_argv.pemCertificate);
2402
+ this.#onCrlProcess = resolve;
3018
2403
  });
3019
- return;
3020
- }
3021
- if (command === "fingerprint") {
3022
- const optionsDef = [
3023
- { name: "certificateFile", type: String, defaultOption: true },
3024
- { name: "help", alias: "h", type: Boolean }
3025
- ];
3026
- const local_argv = (0, import_command_line_args.default)(optionsDef, { argv });
3027
- if (local_argv.help || !local_argv.certificateFile)
3028
- return showHelp("fingerprint <certificateFile>", "print the certificate fingerprint", optionsDef);
3029
- await wrap(async () => {
3030
- const certificate = local_argv.certificateFile;
3031
- const data = await fingerprint(certificate);
3032
- if (!data) return;
3033
- const s = data.split("=")[1].split(":").join("").trim();
3034
- warningLog(s);
3035
- });
3036
- return;
3037
2404
  }
3038
- console.log(`Unknown command: ${command}`);
2405
+ };
2406
+
2407
+ // packages/node-opcua-pki/lib/pki/toolbox_pfx.ts
2408
+ var import_node_assert9 = __toESM(require("assert"));
2409
+ var import_node_fs10 = __toESM(require("fs"));
2410
+ var q2 = quote;
2411
+ var n3 = makePath;
2412
+ async function createPFX(options) {
2413
+ const { certificateFile, privateKeyFile, outputFile, passphrase = "", caCertificateFiles } = options;
2414
+ (0, import_node_assert9.default)(import_node_fs10.default.existsSync(certificateFile), `Certificate file does not exist: ${certificateFile}`);
2415
+ (0, import_node_assert9.default)(import_node_fs10.default.existsSync(privateKeyFile), `Private key file does not exist: ${privateKeyFile}`);
2416
+ let cmd = `pkcs12 -export`;
2417
+ cmd += ` -in ${q2(n3(certificateFile))}`;
2418
+ cmd += ` -inkey ${q2(n3(privateKeyFile))}`;
2419
+ if (caCertificateFiles) {
2420
+ for (const caFile of caCertificateFiles) {
2421
+ (0, import_node_assert9.default)(import_node_fs10.default.existsSync(caFile), `CA certificate file does not exist: ${caFile}`);
2422
+ cmd += ` -certfile ${q2(n3(caFile))}`;
2423
+ }
2424
+ }
2425
+ cmd += ` -out ${q2(n3(outputFile))}`;
2426
+ cmd += ` -passout pass:${passphrase}`;
2427
+ await execute_openssl(cmd, {});
2428
+ }
2429
+ async function extractCertificateFromPFX(options) {
2430
+ const { pfxFile, passphrase = "" } = options;
2431
+ (0, import_node_assert9.default)(import_node_fs10.default.existsSync(pfxFile), `PFX file does not exist: ${pfxFile}`);
2432
+ const cmd = `pkcs12 -in ${q2(n3(pfxFile))} -clcerts -nokeys -nodes -passin pass:${passphrase}`;
2433
+ return await execute_openssl(cmd, {});
2434
+ }
2435
+ async function extractPrivateKeyFromPFX(options) {
2436
+ const { pfxFile, passphrase = "" } = options;
2437
+ (0, import_node_assert9.default)(import_node_fs10.default.existsSync(pfxFile), `PFX file does not exist: ${pfxFile}`);
2438
+ const cmd = `pkcs12 -in ${q2(n3(pfxFile))} -nocerts -nodes -passin pass:${passphrase}`;
2439
+ return await execute_openssl(cmd, {});
2440
+ }
2441
+ async function extractCACertificatesFromPFX(options) {
2442
+ const { pfxFile, passphrase = "" } = options;
2443
+ (0, import_node_assert9.default)(import_node_fs10.default.existsSync(pfxFile), `PFX file does not exist: ${pfxFile}`);
2444
+ const cmd = `pkcs12 -in ${q2(n3(pfxFile))} -cacerts -nokeys -nodes -passin pass:${passphrase}`;
2445
+ return await execute_openssl(cmd, {});
2446
+ }
2447
+ async function extractAllFromPFX(options) {
2448
+ const [certificate, privateKey, caCertificates] = await Promise.all([
2449
+ extractCertificateFromPFX(options),
2450
+ extractPrivateKeyFromPFX(options),
2451
+ extractCACertificatesFromPFX(options)
2452
+ ]);
2453
+ return { certificate, privateKey, caCertificates };
2454
+ }
2455
+ async function convertPFXtoPEM(pfxFile, pemFile, passphrase = "") {
2456
+ (0, import_node_assert9.default)(import_node_fs10.default.existsSync(pfxFile), `PFX file does not exist: ${pfxFile}`);
2457
+ const cmd = `pkcs12 -in ${q2(n3(pfxFile))} -out ${q2(n3(pemFile))} -nodes -passin pass:${passphrase}`;
2458
+ await execute_openssl(cmd, {});
2459
+ }
2460
+ async function dumpPFX(pfxFile, passphrase = "") {
2461
+ (0, import_node_assert9.default)(import_node_fs10.default.existsSync(pfxFile), `PFX file does not exist: ${pfxFile}`);
2462
+ const cmd = `pkcs12 -in ${q2(n3(pfxFile))} -info -nodes -passin pass:${passphrase}`;
2463
+ return await execute_openssl(cmd, {});
3039
2464
  }
3040
2465
  // Annotate the CommonJS export names for ESM import in node:
3041
2466
  0 && (module.exports = {
@@ -3046,22 +2471,15 @@ ${epilog}`
3046
2471
  VerificationStatus,
3047
2472
  adjustApplicationUri,
3048
2473
  adjustDate,
3049
- certificateFileExist,
3050
- debugLog,
3051
- display,
3052
- displayChapter,
3053
- displayDebug,
3054
- displayError,
3055
- displaySubtitle,
3056
- displayTitle,
3057
- doDebug,
2474
+ convertPFXtoPEM,
2475
+ createPFX,
2476
+ dumpPFX,
2477
+ extractAllFromPFX,
2478
+ extractCACertificatesFromPFX,
2479
+ extractCertificateFromPFX,
2480
+ extractPrivateKeyFromPFX,
3058
2481
  findIssuerCertificateInChain,
3059
- g_config,
3060
2482
  install_prerequisite,
3061
- makePath,
3062
- mkdirRecursiveSync,
3063
- pki_main,
3064
- quote,
3065
- warningLog
2483
+ quote
3066
2484
  });
3067
2485
  //# sourceMappingURL=index.js.map