node-opcua-pki 6.2.0 → 6.4.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
@@ -1132,7 +1132,7 @@ var CertificateAuthority = class {
1132
1132
  };
1133
1133
 
1134
1134
  // packages/node-opcua-pki/lib/ca/crypto_create_CA.ts
1135
- var import_node_assert11 = __toESM(require("assert"));
1135
+ var import_node_assert10 = __toESM(require("assert"));
1136
1136
  var import_node_fs10 = __toESM(require("fs"));
1137
1137
  var import_node_module = require("module");
1138
1138
  var import_node_os4 = __toESM(require("os"));
@@ -1213,7 +1213,6 @@ function getFullyQualifiedDomainName(optional_max_length) {
1213
1213
  prepareFQDN();
1214
1214
 
1215
1215
  // packages/node-opcua-pki/lib/pki/certificate_manager.ts
1216
- var import_node_assert10 = __toESM(require("assert"));
1217
1216
  var import_node_fs9 = __toESM(require("fs"));
1218
1217
  var import_node_path6 = __toESM(require("path"));
1219
1218
  var import_global_mutex = require("@ster5/global-mutex");
@@ -1298,6 +1297,12 @@ var simple_config_template_cnf_default = config3;
1298
1297
  // packages/node-opcua-pki/lib/pki/certificate_manager.ts
1299
1298
  var configurationFileSimpleTemplate = simple_config_template_cnf_default;
1300
1299
  var fsWriteFile = import_node_fs9.default.promises.writeFile;
1300
+ function getOrComputeInfo(entry) {
1301
+ if (!entry.info) {
1302
+ entry.info = (0, import_node_opcua_crypto5.exploreCertificate)(entry.certificate);
1303
+ }
1304
+ return entry.info;
1305
+ }
1301
1306
  var VerificationStatus = /* @__PURE__ */ ((VerificationStatus2) => {
1302
1307
  VerificationStatus2["BadCertificateInvalid"] = "BadCertificateInvalid";
1303
1308
  VerificationStatus2["BadSecurityChecksFailed"] = "BadSecurityChecksFailed";
@@ -1335,11 +1340,10 @@ function buildIdealCertificateName(certificate) {
1335
1340
  }
1336
1341
  }
1337
1342
  function findMatchingIssuerKey(entries, wantedIssuerKey) {
1338
- const selected = entries.filter(({ certificate }) => {
1339
- const info = (0, import_node_opcua_crypto5.exploreCertificate)(certificate);
1343
+ return entries.filter((entry) => {
1344
+ const info = getOrComputeInfo(entry);
1340
1345
  return info.tbsCertificate.extensions && info.tbsCertificate.extensions.subjectKeyIdentifier === wantedIssuerKey;
1341
1346
  });
1342
- return selected;
1343
1347
  }
1344
1348
  function isSelfSigned2(info) {
1345
1349
  return info.tbsCertificate.extensions?.subjectKeyIdentifier === info.tbsCertificate.extensions?.authorityKeyIdentifier?.keyIdentifier;
@@ -1385,26 +1389,35 @@ var CertificateManagerState = /* @__PURE__ */ ((CertificateManagerState2) => {
1385
1389
  var CertificateManager = class {
1386
1390
  untrustUnknownCertificate = true;
1387
1391
  state = 0 /* Uninitialized */;
1392
+ /** @deprecated Use {@link folderPollingInterval} instead (typo fix). */
1388
1393
  folderPoolingInterval = 5e3;
1394
+ /** Interval in milliseconds for file-system polling (when enabled). */
1395
+ get folderPollingInterval() {
1396
+ return this.folderPoolingInterval;
1397
+ }
1398
+ set folderPollingInterval(value) {
1399
+ this.folderPoolingInterval = value;
1400
+ }
1389
1401
  keySize;
1390
1402
  location;
1391
1403
  _watchers = [];
1392
1404
  _readCertificatesCalled = false;
1393
- _filenameToHash = {};
1405
+ _filenameToHash = /* @__PURE__ */ new Map();
1406
+ _initializingPromise;
1394
1407
  _thumbs = {
1395
- rejected: {},
1396
- trusted: {},
1408
+ rejected: /* @__PURE__ */ new Map(),
1409
+ trusted: /* @__PURE__ */ new Map(),
1397
1410
  issuers: {
1398
- certs: {}
1411
+ certs: /* @__PURE__ */ new Map()
1399
1412
  },
1400
- crl: {},
1401
- issuersCrl: {}
1413
+ crl: /* @__PURE__ */ new Map(),
1414
+ issuersCrl: /* @__PURE__ */ new Map()
1402
1415
  };
1403
1416
  constructor(options) {
1404
1417
  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 */);
1418
+ if (!options.location) {
1419
+ throw new Error("CertificateManager: missing 'location' option");
1420
+ }
1408
1421
  this.location = makePath(options.location, "");
1409
1422
  this.keySize = options.keySize;
1410
1423
  mkdirRecursiveSync(options.location);
@@ -1432,15 +1445,11 @@ var CertificateManager = class {
1432
1445
  await this.initialize();
1433
1446
  let status = await this._checkRejectedOrTrusted(certificate);
1434
1447
  if (status === "unknown") {
1435
- (0, import_node_assert10.default)(certificate instanceof Buffer);
1436
1448
  const pem = (0, import_node_opcua_crypto5.toPem)(certificate, "CERTIFICATE");
1437
1449
  const fingerprint2 = makeFingerprint(certificate);
1438
1450
  const filename = import_node_path6.default.join(this.rejectedFolder, `${buildIdealCertificateName(certificate)}.pem`);
1439
1451
  await import_node_fs9.default.promises.writeFile(filename, pem);
1440
- this._thumbs.rejected[fingerprint2] = {
1441
- certificate,
1442
- filename
1443
- };
1452
+ this._thumbs.rejected.set(fingerprint2, { certificate, filename });
1444
1453
  status = "rejected";
1445
1454
  }
1446
1455
  return status;
@@ -1491,30 +1500,27 @@ var CertificateManager = class {
1491
1500
  */
1492
1501
  async isCertificateTrusted(certificate) {
1493
1502
  const fingerprint2 = makeFingerprint(certificate);
1494
- const certificateInTrust = this._thumbs.trusted[fingerprint2]?.certificate;
1495
- if (certificateInTrust) {
1503
+ if (this._thumbs.trusted.has(fingerprint2)) {
1496
1504
  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"));
1505
+ }
1506
+ if (!this._thumbs.rejected.has(fingerprint2)) {
1507
+ if (!this.untrustUnknownCertificate) {
1508
+ return "Good";
1509
+ }
1510
+ try {
1511
+ (0, import_node_opcua_crypto5.exploreCertificateInfo)(certificate);
1512
+ } catch (_err) {
1513
+ return "BadCertificateInvalid";
1515
1514
  }
1516
- return "BadCertificateUntrusted";
1515
+ const filename = import_node_path6.default.join(
1516
+ this.rejectedFolder,
1517
+ `${buildIdealCertificateName(certificate)}.pem`
1518
+ );
1519
+ debugLog("certificate has never been seen before and is now rejected (untrusted) ", filename);
1520
+ await fsWriteFile(filename, (0, import_node_opcua_crypto5.toPem)(certificate, "CERTIFICATE"));
1521
+ this._thumbs.rejected.set(fingerprint2, { certificate, filename });
1517
1522
  }
1523
+ return "BadCertificateUntrusted";
1518
1524
  }
1519
1525
  async _innerVerifyCertificateAsync(certificate, _isIssuer, level, options) {
1520
1526
  if (level >= 5) {
@@ -1609,7 +1615,7 @@ var CertificateManager = class {
1609
1615
  return "BadCertificateUntrusted" /* BadCertificateUntrusted */;
1610
1616
  }
1611
1617
  const _c2 = chain[1] ? (0, import_node_opcua_crypto5.exploreCertificateInfo)(chain[1]) : "non";
1612
- _c2;
1618
+ debugLog("chain[1] info=", _c2);
1613
1619
  const certificateInfo = (0, import_node_opcua_crypto5.exploreCertificateInfo)(certificate);
1614
1620
  const now = /* @__PURE__ */ new Date();
1615
1621
  let isTimeInvalid = false;
@@ -1632,7 +1638,6 @@ var CertificateManager = class {
1632
1638
  if (status === "trusted") {
1633
1639
  return isTimeInvalid ? "BadCertificateTimeInvalid" /* BadCertificateTimeInvalid */ : "Good" /* Good */;
1634
1640
  }
1635
- (0, import_node_assert10.default)(status === "unknown");
1636
1641
  if (hasIssuerKey) {
1637
1642
  if (!hasTrustedIssuer) {
1638
1643
  return "BadCertificateUntrusted" /* BadCertificateUntrusted */;
@@ -1675,7 +1680,9 @@ var CertificateManager = class {
1675
1680
  return;
1676
1681
  }
1677
1682
  this.state = 1 /* Initializing */;
1678
- await this._initialize();
1683
+ this._initializingPromise = this._initialize();
1684
+ await this._initializingPromise;
1685
+ this._initializingPromise = void 0;
1679
1686
  this.state = 2 /* Initialized */;
1680
1687
  }
1681
1688
  async _initialize() {
@@ -1694,11 +1701,9 @@ var CertificateManager = class {
1694
1701
  mkdirRecursiveSync(import_node_path6.default.join(pkiDir, "issuers/crl"));
1695
1702
  if (!import_node_fs9.default.existsSync(this.configFile) || !import_node_fs9.default.existsSync(this.privateKey)) {
1696
1703
  return await this.withLock2(async () => {
1697
- (0, import_node_assert10.default)(this.state !== 3 /* Disposing */);
1698
- if (this.state === 4 /* Disposed */) {
1704
+ if (this.state === 3 /* Disposing */ || this.state === 4 /* Disposed */) {
1699
1705
  return;
1700
1706
  }
1701
- (0, import_node_assert10.default)(this.state === 1 /* Initializing */);
1702
1707
  if (!import_node_fs9.default.existsSync(this.configFile)) {
1703
1708
  import_node_fs9.default.writeFileSync(this.configFile, configurationFileSimpleTemplate);
1704
1709
  }
@@ -1728,8 +1733,9 @@ var CertificateManager = class {
1728
1733
  return;
1729
1734
  }
1730
1735
  if (this.state === 1 /* Initializing */) {
1731
- await new Promise((resolve) => setTimeout(resolve, 100));
1732
- return await this.dispose();
1736
+ if (this._initializingPromise) {
1737
+ await this._initializingPromise;
1738
+ }
1733
1739
  }
1734
1740
  try {
1735
1741
  this.state = 3 /* Disposing */;
@@ -1742,6 +1748,30 @@ var CertificateManager = class {
1742
1748
  this.state = 4 /* Disposed */;
1743
1749
  }
1744
1750
  }
1751
+ /**
1752
+ * Force a full re-scan of all PKI folders, rebuilding
1753
+ * the in-memory `_thumbs` index from scratch.
1754
+ *
1755
+ * Call this after external processes have modified the
1756
+ * PKI folders (e.g. via `writeTrustList` or CLI tools)
1757
+ * to ensure the CertificateManager sees the latest
1758
+ * state without waiting for file-system events.
1759
+ */
1760
+ async reloadCertificates() {
1761
+ await Promise.all(this._watchers.map((w) => w.close()));
1762
+ for (const w of this._watchers) {
1763
+ w.removeAllListeners();
1764
+ }
1765
+ this._watchers.splice(0);
1766
+ this._thumbs.rejected.clear();
1767
+ this._thumbs.trusted.clear();
1768
+ this._thumbs.issuers.certs.clear();
1769
+ this._thumbs.crl.clear();
1770
+ this._thumbs.issuersCrl.clear();
1771
+ this._filenameToHash.clear();
1772
+ this._readCertificatesCalled = false;
1773
+ await this._readCertificates();
1774
+ }
1745
1775
  async withLock2(action) {
1746
1776
  const lockFileName = import_node_path6.default.join(this.rootDir, "mutex.lock");
1747
1777
  return (0, import_global_mutex.withLock)({ fileToLock: lockFileName }, async () => {
@@ -1754,7 +1784,9 @@ var CertificateManager = class {
1754
1784
  *
1755
1785
  */
1756
1786
  async createSelfSignedCertificate(params) {
1757
- (0, import_node_assert10.default)(typeof params.applicationUri === "string", "expecting applicationUri");
1787
+ if (typeof params.applicationUri !== "string") {
1788
+ throw new Error("createSelfSignedCertificate: expecting applicationUri to be a string");
1789
+ }
1758
1790
  if (!import_node_fs9.default.existsSync(this.privateKey)) {
1759
1791
  throw new Error(`Cannot find private key ${this.privateKey}`);
1760
1792
  }
@@ -1770,14 +1802,13 @@ var CertificateManager = class {
1770
1802
  });
1771
1803
  }
1772
1804
  async createCertificateRequest(params) {
1773
- (0, import_node_assert10.default)(params);
1805
+ if (!params) {
1806
+ throw new Error("params is required");
1807
+ }
1774
1808
  const _params = params;
1775
1809
  if (Object.prototype.hasOwnProperty.call(_params, "rootDir")) {
1776
1810
  throw new Error("rootDir should not be specified ");
1777
1811
  }
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
1812
  _params.rootDir = import_node_path6.default.resolve(this.rootDir);
1782
1813
  _params.configFile = import_node_path6.default.resolve(this.configFile);
1783
1814
  _params.privateKey = import_node_path6.default.resolve(this.privateKey);
@@ -1806,12 +1837,12 @@ var CertificateManager = class {
1806
1837
  }
1807
1838
  const pemCertificate = (0, import_node_opcua_crypto5.toPem)(certificate, "CERTIFICATE");
1808
1839
  const fingerprint2 = makeFingerprint(certificate);
1809
- if (this._thumbs.issuers.certs[fingerprint2]) {
1840
+ if (this._thumbs.issuers.certs.has(fingerprint2)) {
1810
1841
  return "Good" /* Good */;
1811
1842
  }
1812
1843
  const filename = import_node_path6.default.join(this.issuersCertFolder, `issuer_${buildIdealCertificateName(certificate)}.pem`);
1813
1844
  await import_node_fs9.default.promises.writeFile(filename, pemCertificate, "ascii");
1814
- this._thumbs.issuers.certs[fingerprint2] = { certificate, filename };
1845
+ this._thumbs.issuers.certs.set(fingerprint2, { certificate, filename });
1815
1846
  if (addInTrustList) {
1816
1847
  await this.trustCertificate(certificate);
1817
1848
  }
@@ -1829,8 +1860,8 @@ var CertificateManager = class {
1829
1860
  const folder = target === "trusted" ? this.crlFolder : this.issuersCrlFolder;
1830
1861
  const crlInfo = (0, import_node_opcua_crypto5.exploreCertificateRevocationList)(crl);
1831
1862
  const key = crlInfo.tbsCertList.issuerFingerprint;
1832
- if (!index[key]) {
1833
- index[key] = { crls: [], serialNumbers: {} };
1863
+ if (!index.has(key)) {
1864
+ index.set(key, { crls: [], serialNumbers: {} });
1834
1865
  }
1835
1866
  const pemCertificate = (0, import_node_opcua_crypto5.toPem)(crl, "X509 CRL");
1836
1867
  const filename = import_node_path6.default.join(folder, `crl_${buildIdealCertificateName(crl)}.pem`);
@@ -1865,9 +1896,7 @@ var CertificateManager = class {
1865
1896
  throw err;
1866
1897
  }
1867
1898
  }
1868
- for (const key of Object.keys(index)) {
1869
- delete index[key];
1870
- }
1899
+ index.clear();
1871
1900
  };
1872
1901
  if (target === "issuers" || target === "all") {
1873
1902
  await clearFolder(this.issuersCrlFolder, this._thumbs.issuersCrl);
@@ -1884,7 +1913,7 @@ var CertificateManager = class {
1884
1913
  async hasIssuer(thumbprint) {
1885
1914
  await this._readCertificates();
1886
1915
  const normalized = thumbprint.toLowerCase();
1887
- return Object.prototype.hasOwnProperty.call(this._thumbs.issuers.certs, normalized);
1916
+ return this._thumbs.issuers.certs.has(normalized);
1888
1917
  }
1889
1918
  /**
1890
1919
  * Remove a trusted certificate identified by its SHA-1 thumbprint.
@@ -1896,7 +1925,7 @@ var CertificateManager = class {
1896
1925
  async removeTrustedCertificate(thumbprint) {
1897
1926
  await this._readCertificates();
1898
1927
  const normalized = thumbprint.toLowerCase();
1899
- const entry = this._thumbs.trusted[normalized];
1928
+ const entry = this._thumbs.trusted.get(normalized);
1900
1929
  if (!entry) {
1901
1930
  return null;
1902
1931
  }
@@ -1907,7 +1936,7 @@ var CertificateManager = class {
1907
1936
  throw err;
1908
1937
  }
1909
1938
  }
1910
- delete this._thumbs.trusted[normalized];
1939
+ this._thumbs.trusted.delete(normalized);
1911
1940
  return entry.certificate;
1912
1941
  }
1913
1942
  /**
@@ -1920,7 +1949,7 @@ var CertificateManager = class {
1920
1949
  async removeIssuer(thumbprint) {
1921
1950
  await this._readCertificates();
1922
1951
  const normalized = thumbprint.toLowerCase();
1923
- const entry = this._thumbs.issuers.certs[normalized];
1952
+ const entry = this._thumbs.issuers.certs.get(normalized);
1924
1953
  if (!entry) {
1925
1954
  return null;
1926
1955
  }
@@ -1931,7 +1960,7 @@ var CertificateManager = class {
1931
1960
  throw err;
1932
1961
  }
1933
1962
  }
1934
- delete this._thumbs.issuers.certs[normalized];
1963
+ this._thumbs.issuers.certs.delete(normalized);
1935
1964
  return entry.certificate;
1936
1965
  }
1937
1966
  /**
@@ -1944,7 +1973,7 @@ var CertificateManager = class {
1944
1973
  const issuerInfo = (0, import_node_opcua_crypto5.exploreCertificate)(issuerCertificate);
1945
1974
  const issuerFingerprint = issuerInfo.tbsCertificate.subjectFingerPrint;
1946
1975
  const processIndex = async (index) => {
1947
- const crlData = index[issuerFingerprint];
1976
+ const crlData = index.get(issuerFingerprint);
1948
1977
  if (!crlData) return;
1949
1978
  for (const crlEntry of crlData.crls) {
1950
1979
  try {
@@ -1955,7 +1984,7 @@ var CertificateManager = class {
1955
1984
  }
1956
1985
  }
1957
1986
  }
1958
- delete index[issuerFingerprint];
1987
+ index.delete(issuerFingerprint);
1959
1988
  };
1960
1989
  if (target === "issuers" || target === "all") {
1961
1990
  await processIndex(this._thumbs.issuersCrl);
@@ -1988,16 +2017,28 @@ var CertificateManager = class {
1988
2017
  } catch (_err) {
1989
2018
  return "BadCertificateInvalid" /* BadCertificateInvalid */;
1990
2019
  }
1991
- const status = await this.verifyCertificate(leafCertificate, {
1992
- ignoreMissingRevocationList: true
1993
- });
1994
- if (status !== "Good" /* Good */ && status !== "BadCertificateUntrusted" /* BadCertificateUntrusted */) {
1995
- return status;
2020
+ const result = await (0, import_node_opcua_crypto5.verifyCertificateChain)([leafCertificate]);
2021
+ if (result.status !== "Good") {
2022
+ return "BadCertificateInvalid" /* BadCertificateInvalid */;
1996
2023
  }
1997
2024
  if (certificates.length > 1) {
2025
+ const issuerFolder = this.issuersCertFolder;
2026
+ const issuerThumbprints = /* @__PURE__ */ new Set();
2027
+ const files = await import_node_fs9.default.promises.readdir(issuerFolder);
2028
+ for (const file of files) {
2029
+ const ext = import_node_path6.default.extname(file).toLowerCase();
2030
+ if (ext === ".pem" || ext === ".der") {
2031
+ try {
2032
+ const issuerCert = (0, import_node_opcua_crypto5.readCertificate)(import_node_path6.default.join(issuerFolder, file));
2033
+ const fp = makeFingerprint(issuerCert);
2034
+ issuerThumbprints.add(fp);
2035
+ } catch (_err) {
2036
+ }
2037
+ }
2038
+ }
1998
2039
  for (const issuerCert of certificates.slice(1)) {
1999
2040
  const thumbprint = makeFingerprint(issuerCert);
2000
- if (!Object.prototype.hasOwnProperty.call(this._thumbs.issuers.certs, thumbprint)) {
2041
+ if (!issuerThumbprints.has(thumbprint)) {
2001
2042
  return "BadCertificateChainIncomplete" /* BadCertificateChainIncomplete */;
2002
2043
  }
2003
2044
  }
@@ -2019,7 +2060,7 @@ var CertificateManager = class {
2019
2060
  */
2020
2061
  async isIssuerInUseByTrustedCertificate(issuerCertificate) {
2021
2062
  await this._readCertificates();
2022
- for (const entry of Object.values(this._thumbs.trusted)) {
2063
+ for (const entry of this._thumbs.trusted.values()) {
2023
2064
  if (!entry.certificate) continue;
2024
2065
  try {
2025
2066
  if ((0, import_node_opcua_crypto5.verifyCertificateSignature)(entry.certificate, issuerCertificate)) {
@@ -2054,7 +2095,7 @@ var CertificateManager = class {
2054
2095
  debugLog("Certificate has no extension 3");
2055
2096
  return null;
2056
2097
  }
2057
- const issuerCertificates = Object.values(this._thumbs.issuers.certs);
2098
+ const issuerCertificates = [...this._thumbs.issuers.certs.values()];
2058
2099
  const selectedIssuerCertificates = findMatchingIssuerKey(issuerCertificates, wantedIssuerKey);
2059
2100
  if (selectedIssuerCertificates.length > 0) {
2060
2101
  if (selectedIssuerCertificates.length > 1) {
@@ -2062,7 +2103,7 @@ var CertificateManager = class {
2062
2103
  }
2063
2104
  return selectedIssuerCertificates[0].certificate || null;
2064
2105
  }
2065
- const trustedCertificates = Object.values(this._thumbs.trusted);
2106
+ const trustedCertificates = [...this._thumbs.trusted.values()];
2066
2107
  const selectedTrustedCertificates = findMatchingIssuerKey(trustedCertificates, wantedIssuerKey);
2067
2108
  if (selectedTrustedCertificates.length > 1) {
2068
2109
  warningLog(
@@ -2078,48 +2119,47 @@ var CertificateManager = class {
2078
2119
  * @private
2079
2120
  */
2080
2121
  async _checkRejectedOrTrusted(certificate) {
2081
- (0, import_node_assert10.default)(certificate instanceof Buffer);
2082
2122
  const fingerprint2 = makeFingerprint(certificate);
2083
2123
  debugLog("_checkRejectedOrTrusted fingerprint ", short(fingerprint2));
2084
2124
  await this._readCertificates();
2085
- if (Object.prototype.hasOwnProperty.call(this._thumbs.rejected, fingerprint2)) {
2125
+ if (this._thumbs.rejected.has(fingerprint2)) {
2086
2126
  return "rejected";
2087
2127
  }
2088
- if (Object.prototype.hasOwnProperty.call(this._thumbs.trusted, fingerprint2)) {
2128
+ if (this._thumbs.trusted.has(fingerprint2)) {
2089
2129
  return "trusted";
2090
2130
  }
2091
2131
  return "unknown";
2092
2132
  }
2093
2133
  async _moveCertificate(certificate, newStatus) {
2094
- (0, import_node_assert10.default)(certificate instanceof Buffer);
2095
- const fingerprint2 = makeFingerprint(certificate);
2096
- const status = await this.getCertificateStatus(certificate);
2097
- debugLog("_moveCertificate", fingerprint2.substring(0, 10), "from", status, "to", newStatus);
2098
- (0, import_node_assert10.default)(status === "rejected" || status === "trusted");
2099
- if (status !== newStatus) {
2100
- const indexSrc = status === "rejected" ? this._thumbs.rejected : this._thumbs.trusted;
2101
- const certificateSrc = indexSrc[fingerprint2]?.filename;
2102
- if (!certificateSrc) {
2103
- debugLog(" cannot find certificate ", fingerprint2.substring(0, 10), " in", this._thumbs, [status]);
2104
- throw new Error("internal");
2134
+ await this.withLock2(async () => {
2135
+ const fingerprint2 = makeFingerprint(certificate);
2136
+ const status = await this.getCertificateStatus(certificate);
2137
+ debugLog("_moveCertificate", fingerprint2.substring(0, 10), "from", status, "to", newStatus);
2138
+ if (status !== "rejected" && status !== "trusted") {
2139
+ throw new Error(`_moveCertificate: unexpected status '${status}' for certificate ${fingerprint2.substring(0, 10)}`);
2105
2140
  }
2106
- const destFolder = newStatus === "rejected" ? this.rejectedFolder : newStatus === "trusted" ? this.trustedFolder : this.rejectedFolder;
2107
- const certificateDest = import_node_path6.default.join(destFolder, import_node_path6.default.basename(certificateSrc));
2108
- debugLog("_moveCertificate1", fingerprint2.substring(0, 10), "old name", certificateSrc);
2109
- debugLog("_moveCertificate1", fingerprint2.substring(0, 10), "new name", certificateDest);
2110
- await import_node_fs9.default.promises.rename(certificateSrc, certificateDest);
2111
- delete indexSrc[fingerprint2];
2112
- const indexDest = newStatus === "rejected" ? this._thumbs.rejected : newStatus === "trusted" ? this._thumbs.trusted : this._thumbs.rejected;
2113
- indexDest[fingerprint2] = {
2114
- certificate,
2115
- filename: certificateDest
2116
- };
2117
- }
2141
+ if (status !== newStatus) {
2142
+ const indexSrc = status === "rejected" ? this._thumbs.rejected : this._thumbs.trusted;
2143
+ const srcEntry = indexSrc.get(fingerprint2);
2144
+ if (!srcEntry) {
2145
+ debugLog(" cannot find certificate ", fingerprint2.substring(0, 10), " in", status);
2146
+ throw new Error(`_moveCertificate: certificate ${fingerprint2.substring(0, 10)} not found in ${status} index`);
2147
+ }
2148
+ const destFolder = newStatus === "trusted" ? this.trustedFolder : this.rejectedFolder;
2149
+ const certificateDest = import_node_path6.default.join(destFolder, import_node_path6.default.basename(srcEntry.filename));
2150
+ debugLog("_moveCertificate", fingerprint2.substring(0, 10), "old name", srcEntry.filename);
2151
+ debugLog("_moveCertificate", fingerprint2.substring(0, 10), "new name", certificateDest);
2152
+ await import_node_fs9.default.promises.rename(srcEntry.filename, certificateDest);
2153
+ indexSrc.delete(fingerprint2);
2154
+ const indexDest = newStatus === "trusted" ? this._thumbs.trusted : this._thumbs.rejected;
2155
+ indexDest.set(fingerprint2, { certificate, filename: certificateDest });
2156
+ }
2157
+ });
2118
2158
  }
2119
2159
  _findAssociatedCRLs(issuerCertificate) {
2120
2160
  const issuerCertificateInfo = (0, import_node_opcua_crypto5.exploreCertificate)(issuerCertificate);
2121
2161
  const key = issuerCertificateInfo.tbsCertificate.subjectFingerPrint;
2122
- return this._thumbs.issuersCrl[key] ? this._thumbs.issuersCrl[key] : this._thumbs.crl[key] ? this._thumbs.crl[key] : null;
2162
+ return this._thumbs.issuersCrl.get(key) ?? this._thumbs.crl.get(key) ?? null;
2123
2163
  }
2124
2164
  async isCertificateRevoked(certificate, issuerCertificate) {
2125
2165
  if (isSelfSigned3(certificate)) {
@@ -2138,7 +2178,7 @@ var CertificateManager = class {
2138
2178
  const certInfo = (0, import_node_opcua_crypto5.exploreCertificate)(certificate);
2139
2179
  const serialNumber = certInfo.tbsCertificate.serialNumber || certInfo.tbsCertificate.extensions?.authorityKeyIdentifier?.serial || "";
2140
2180
  const key = certInfo.tbsCertificate.extensions?.authorityKeyIdentifier?.authorityCertIssuerFingerPrint || "<unknown>";
2141
- const crl2 = this._thumbs.crl[key] || null;
2181
+ const crl2 = this._thumbs.crl.get(key) ?? null;
2142
2182
  if (crls.serialNumbers[serialNumber] || crl2?.serialNumbers[serialNumber]) {
2143
2183
  return "BadCertificateRevoked" /* BadCertificateRevoked */;
2144
2184
  }
@@ -2163,19 +2203,18 @@ var CertificateManager = class {
2163
2203
  const crlInfo = (0, import_node_opcua_crypto5.exploreCertificateRevocationList)(crl);
2164
2204
  debugLog(import_chalk6.default.cyan("add CRL in folder "), filename);
2165
2205
  const fingerprint2 = crlInfo.tbsCertList.issuerFingerprint;
2166
- index[fingerprint2] = index[fingerprint2] || {
2167
- crls: [],
2168
- serialNumbers: {}
2169
- };
2170
- index[fingerprint2].crls.push({ crlInfo, filename });
2171
- const serialNumbers = index[fingerprint2].serialNumbers;
2206
+ if (!index.has(fingerprint2)) {
2207
+ index.set(fingerprint2, { crls: [], serialNumbers: {} });
2208
+ }
2209
+ const data = index.get(fingerprint2) || { crls: [], serialNumbers: {} };
2210
+ data.crls.push({ crlInfo, filename });
2172
2211
  for (const revokedCertificate of crlInfo.tbsCertList.revokedCertificates) {
2173
2212
  const serialNumber = revokedCertificate.userCertificate;
2174
- if (!serialNumbers[serialNumber]) {
2175
- serialNumbers[serialNumber] = revokedCertificate.revocationDate;
2213
+ if (!data.serialNumbers[serialNumber]) {
2214
+ data.serialNumbers[serialNumber] = revokedCertificate.revocationDate;
2176
2215
  }
2177
2216
  }
2178
- debugLog(import_chalk6.default.cyan("CRL"), fingerprint2, "serial numbers = ", Object.keys(serialNumbers));
2217
+ debugLog(import_chalk6.default.cyan("CRL"), fingerprint2, "serial numbers = ", Object.keys(data.serialNumbers));
2179
2218
  } catch (err) {
2180
2219
  debugLog("CRL filename error =");
2181
2220
  debugLog(err);
@@ -2195,28 +2234,28 @@ var CertificateManager = class {
2195
2234
  return;
2196
2235
  }
2197
2236
  this._readCertificatesCalled = true;
2237
+ const usePolling = process.env.OPCUA_PKI_USE_POLLING === "true";
2198
2238
  const options = {
2199
- usePolling: true,
2200
- interval: Math.min(10 * 60 * 1e3, Math.max(100, this.folderPoolingInterval)),
2201
- persistent: false,
2202
- awaitWriteFinish: {
2203
- stabilityThreshold: 2e3,
2204
- pollInterval: 600
2205
- }
2239
+ usePolling,
2240
+ ...usePolling ? { interval: Math.min(10 * 60 * 1e3, Math.max(100, this.folderPoolingInterval)) } : {},
2241
+ persistent: false
2206
2242
  };
2207
2243
  async function _walkCRLFiles(folder, index) {
2208
2244
  await new Promise((resolve, _reject) => {
2209
2245
  const w = import_chokidar.default.watch(folder, options);
2210
- w.on("unlink", (filename, stat) => {
2211
- filename;
2212
- stat;
2246
+ w.on("unlink", (filename) => {
2247
+ for (const [key, data] of index.entries()) {
2248
+ data.crls = data.crls.filter((c) => c.filename !== filename);
2249
+ if (data.crls.length === 0) {
2250
+ index.delete(key);
2251
+ }
2252
+ }
2213
2253
  });
2214
- w.on("add", (filename, stat) => {
2215
- stat;
2254
+ w.on("add", (filename) => {
2216
2255
  this._on_crl_file_added(index, filename);
2217
2256
  });
2218
- w.on("change", (path8, stat) => {
2219
- debugLog("change in folder ", folder, path8, stat);
2257
+ w.on("change", (changedPath) => {
2258
+ debugLog("change in folder ", folder, changedPath);
2220
2259
  });
2221
2260
  this._watchers.push(w);
2222
2261
  w.on("ready", () => {
@@ -2226,26 +2265,21 @@ var CertificateManager = class {
2226
2265
  }
2227
2266
  async function _walkAllFiles(folder, index) {
2228
2267
  const w = import_chokidar.default.watch(folder, options);
2229
- w.on("unlink", (filename, stat) => {
2230
- stat;
2268
+ w.on("unlink", (filename) => {
2231
2269
  debugLog(import_chalk6.default.cyan(`unlink in folder ${folder}`), filename);
2232
- const h = this._filenameToHash[filename];
2233
- if (h && index[h]) {
2234
- delete index[h];
2270
+ const h = this._filenameToHash.get(filename);
2271
+ if (h && index.has(h)) {
2272
+ index.delete(h);
2235
2273
  }
2236
2274
  });
2237
- w.on("add", (filename, stat) => {
2238
- stat;
2275
+ w.on("add", (filename) => {
2239
2276
  debugLog(import_chalk6.default.cyan(`add in folder ${folder}`), filename);
2240
2277
  try {
2241
2278
  const certificate = (0, import_node_opcua_crypto5.readCertificate)(filename);
2242
2279
  const info = (0, import_node_opcua_crypto5.exploreCertificate)(certificate);
2243
2280
  const fingerprint2 = makeFingerprint(certificate);
2244
- index[fingerprint2] = {
2245
- certificate,
2246
- filename
2247
- };
2248
- this._filenameToHash[filename] = fingerprint2;
2281
+ index.set(fingerprint2, { certificate, filename, info });
2282
+ this._filenameToHash.set(filename, fingerprint2);
2249
2283
  debugLog(
2250
2284
  import_chalk6.default.magenta("CERT"),
2251
2285
  info.tbsCertificate.subjectFingerPrint,
@@ -2257,15 +2291,26 @@ var CertificateManager = class {
2257
2291
  debugLog(err);
2258
2292
  }
2259
2293
  });
2260
- w.on("change", (path8, stat) => {
2261
- stat;
2262
- debugLog("change in folder ", folder, path8);
2294
+ w.on("change", (changedPath) => {
2295
+ debugLog(import_chalk6.default.cyan(`change in folder ${folder}`), changedPath);
2296
+ try {
2297
+ const certificate = (0, import_node_opcua_crypto5.readCertificate)(changedPath);
2298
+ const newFingerprint = makeFingerprint(certificate);
2299
+ const oldHash = this._filenameToHash.get(changedPath);
2300
+ if (oldHash && oldHash !== newFingerprint) {
2301
+ index.delete(oldHash);
2302
+ }
2303
+ index.set(newFingerprint, { certificate, filename: changedPath, info: (0, import_node_opcua_crypto5.exploreCertificate)(certificate) });
2304
+ this._filenameToHash.set(changedPath, newFingerprint);
2305
+ } catch (err) {
2306
+ debugLog(`change event: failed to re-read ${changedPath}`, err);
2307
+ }
2263
2308
  });
2264
2309
  this._watchers.push(w);
2265
2310
  await new Promise((resolve, _reject) => {
2266
2311
  w.on("ready", () => {
2267
2312
  debugLog("ready");
2268
- debugLog(Object.entries(index).map((kv) => kv[0].substring(0, 10)));
2313
+ debugLog([...index.keys()].map((k) => k.substring(0, 10)));
2269
2314
  resolve();
2270
2315
  });
2271
2316
  });
@@ -2311,8 +2356,8 @@ var next_year = get_offset_date(today, 365);
2311
2356
  var gLocalConfig = {};
2312
2357
  var g_certificateAuthority;
2313
2358
  async function construct_CertificateAuthority2(subject) {
2314
- (0, import_node_assert11.default)(typeof gLocalConfig.CAFolder === "string", "expecting a CAFolder in config");
2315
- (0, import_node_assert11.default)(typeof gLocalConfig.keySize === "number", "expecting a keySize in config");
2359
+ (0, import_node_assert10.default)(typeof gLocalConfig.CAFolder === "string", "expecting a CAFolder in config");
2360
+ (0, import_node_assert10.default)(typeof gLocalConfig.keySize === "number", "expecting a keySize in config");
2316
2361
  if (!g_certificateAuthority) {
2317
2362
  g_certificateAuthority = new CertificateAuthority({
2318
2363
  keySize: gLocalConfig.keySize,
@@ -2324,7 +2369,7 @@ async function construct_CertificateAuthority2(subject) {
2324
2369
  }
2325
2370
  var certificateManager;
2326
2371
  async function construct_CertificateManager() {
2327
- (0, import_node_assert11.default)(typeof gLocalConfig.PKIFolder === "string", "expecting a PKIFolder in config");
2372
+ (0, import_node_assert10.default)(typeof gLocalConfig.PKIFolder === "string", "expecting a PKIFolder in config");
2328
2373
  if (!certificateManager) {
2329
2374
  certificateManager = new CertificateManager({
2330
2375
  keySize: gLocalConfig.keySize,
@@ -2351,7 +2396,7 @@ function default_template_content() {
2351
2396
  return default_config_template2;
2352
2397
  }
2353
2398
  const default_config_template = find_default_config_template();
2354
- (0, import_node_assert11.default)(import_node_fs10.default.existsSync(default_config_template));
2399
+ (0, import_node_assert10.default)(import_node_fs10.default.existsSync(default_config_template));
2355
2400
  const default_config_template_content = import_node_fs10.default.readFileSync(default_config_template, "utf8");
2356
2401
  return default_config_template_content;
2357
2402
  }
@@ -2363,7 +2408,7 @@ function find_module_root_folder() {
2363
2408
  }
2364
2409
  rootFolder = import_node_path7.default.join(rootFolder, "..");
2365
2410
  }
2366
- (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");
2411
+ (0, import_node_assert10.default)(import_node_fs10.default.existsSync(import_node_path7.default.join(rootFolder, "package.json")), "root folder must have a package.json file");
2367
2412
  return rootFolder;
2368
2413
  }
2369
2414
  async function readConfiguration(argv) {
@@ -2392,10 +2437,10 @@ async function readConfiguration(argv) {
2392
2437
  return makePath(tmp);
2393
2438
  }
2394
2439
  certificateDir = argv.root;
2395
- (0, import_node_assert11.default)(typeof certificateDir === "string");
2440
+ (0, import_node_assert10.default)(typeof certificateDir === "string");
2396
2441
  certificateDir = prepare(certificateDir);
2397
2442
  mkdirRecursiveSync(certificateDir);
2398
- (0, import_node_assert11.default)(import_node_fs10.default.existsSync(certificateDir));
2443
+ (0, import_node_assert10.default)(import_node_fs10.default.existsSync(certificateDir));
2399
2444
  const default_config = import_node_path7.default.join(certificateDir, "config.js");
2400
2445
  if (!import_node_fs10.default.existsSync(default_config)) {
2401
2446
  debugLog(import_chalk7.default.yellow(" Creating default g_config file "), import_chalk7.default.cyan(default_config));
@@ -2460,7 +2505,7 @@ async function readConfiguration(argv) {
2460
2505
  }
2461
2506
  }
2462
2507
  async function createDefaultCertificate(base_name, prefix, key_length, applicationUri, dev) {
2463
- (0, import_node_assert11.default)(key_length === 1024 || key_length === 2048 || key_length === 3072 || key_length === 4096);
2508
+ (0, import_node_assert10.default)(key_length === 1024 || key_length === 2048 || key_length === 3072 || key_length === 4096);
2464
2509
  const private_key_file = makePath(base_name, `${prefix}key_${key_length}.pem`);
2465
2510
  const public_key_file = makePath(base_name, `${prefix}public_key_${key_length}.pub`);
2466
2511
  const certificate_file = makePath(base_name, `${prefix}cert_${key_length}.pem`);
@@ -2566,9 +2611,9 @@ async function wrap(func) {
2566
2611
  }
2567
2612
  }
2568
2613
  async function create_default_certificates(dev) {
2569
- (0, import_node_assert11.default)(gLocalConfig);
2614
+ (0, import_node_assert10.default)(gLocalConfig);
2570
2615
  const base_name = gLocalConfig.certificateDir || "";
2571
- (0, import_node_assert11.default)(import_node_fs10.default.existsSync(base_name));
2616
+ (0, import_node_assert10.default)(import_node_fs10.default.existsSync(base_name));
2572
2617
  let clientURN;
2573
2618
  let serverURN;
2574
2619
  let discoveryServerURN;
@@ -2723,7 +2768,7 @@ ${epilog}`
2723
2768
  await readConfiguration(local_argv);
2724
2769
  if (local_argv.clean) {
2725
2770
  displayTitle("Cleaning old certificates");
2726
- (0, import_node_assert11.default)(gLocalConfig);
2771
+ (0, import_node_assert10.default)(gLocalConfig);
2727
2772
  const certificateDir = gLocalConfig.certificateDir || "";
2728
2773
  const files = await import_node_fs10.default.promises.readdir(certificateDir);
2729
2774
  for (const file of files) {
@@ -2835,7 +2880,7 @@ ${epilog}`
2835
2880
  await readConfiguration(local_argv2);
2836
2881
  await construct_CertificateManager();
2837
2882
  await construct_CertificateAuthority2("");
2838
- (0, import_node_assert11.default)(import_node_fs10.default.existsSync(gLocalConfig.CAFolder || ""), " CA folder must exist");
2883
+ (0, import_node_assert10.default)(import_node_fs10.default.existsSync(gLocalConfig.CAFolder || ""), " CA folder must exist");
2839
2884
  gLocalConfig.privateKey = void 0;
2840
2885
  gLocalConfig.subject = local_argv2.subject && local_argv2.subject.length > 1 ? local_argv2.subject : gLocalConfig.subject;
2841
2886
  const csr_file = await certificateManager.createCertificateRequest(
@@ -2854,7 +2899,7 @@ ${epilog}`
2854
2899
  csr_file,
2855
2900
  gLocalConfig
2856
2901
  );
2857
- (0, import_node_assert11.default)(typeof gLocalConfig.outputFile === "string");
2902
+ (0, import_node_assert10.default)(typeof gLocalConfig.outputFile === "string");
2858
2903
  import_node_fs10.default.writeFileSync(gLocalConfig.outputFile || "", import_node_fs10.default.readFileSync(certificate, "ascii"));
2859
2904
  }
2860
2905
  await wrap(async () => await command_certificate(local_argv));
@@ -2988,7 +3033,7 @@ ${epilog}`
2988
3033
  csr_file,
2989
3034
  gLocalConfig
2990
3035
  );
2991
- (0, import_node_assert11.default)(typeof gLocalConfig.outputFile === "string");
3036
+ (0, import_node_assert10.default)(typeof gLocalConfig.outputFile === "string");
2992
3037
  import_node_fs10.default.writeFileSync(gLocalConfig.outputFile || "", import_node_fs10.default.readFileSync(certificate, "ascii"));
2993
3038
  });
2994
3039
  return;