node-opcua-pki 6.3.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.mjs CHANGED
@@ -1086,7 +1086,7 @@ var CertificateAuthority = class {
1086
1086
  };
1087
1087
 
1088
1088
  // packages/node-opcua-pki/lib/ca/crypto_create_CA.ts
1089
- import assert11 from "assert";
1089
+ import assert10 from "assert";
1090
1090
  import fs10 from "fs";
1091
1091
  import { createRequire } from "module";
1092
1092
  import os4 from "os";
@@ -1167,7 +1167,6 @@ function getFullyQualifiedDomainName(optional_max_length) {
1167
1167
  prepareFQDN();
1168
1168
 
1169
1169
  // packages/node-opcua-pki/lib/pki/certificate_manager.ts
1170
- import assert10 from "assert";
1171
1170
  import fs9 from "fs";
1172
1171
  import path7 from "path";
1173
1172
  import { withLock } from "@ster5/global-mutex";
@@ -1269,6 +1268,12 @@ var simple_config_template_cnf_default = config3;
1269
1268
  // packages/node-opcua-pki/lib/pki/certificate_manager.ts
1270
1269
  var configurationFileSimpleTemplate = simple_config_template_cnf_default;
1271
1270
  var fsWriteFile = fs9.promises.writeFile;
1271
+ function getOrComputeInfo(entry) {
1272
+ if (!entry.info) {
1273
+ entry.info = exploreCertificate(entry.certificate);
1274
+ }
1275
+ return entry.info;
1276
+ }
1272
1277
  var VerificationStatus = /* @__PURE__ */ ((VerificationStatus2) => {
1273
1278
  VerificationStatus2["BadCertificateInvalid"] = "BadCertificateInvalid";
1274
1279
  VerificationStatus2["BadSecurityChecksFailed"] = "BadSecurityChecksFailed";
@@ -1306,11 +1311,10 @@ function buildIdealCertificateName(certificate) {
1306
1311
  }
1307
1312
  }
1308
1313
  function findMatchingIssuerKey(entries, wantedIssuerKey) {
1309
- const selected = entries.filter(({ certificate }) => {
1310
- const info = exploreCertificate(certificate);
1314
+ return entries.filter((entry) => {
1315
+ const info = getOrComputeInfo(entry);
1311
1316
  return info.tbsCertificate.extensions && info.tbsCertificate.extensions.subjectKeyIdentifier === wantedIssuerKey;
1312
1317
  });
1313
- return selected;
1314
1318
  }
1315
1319
  function isSelfSigned2(info) {
1316
1320
  return info.tbsCertificate.extensions?.subjectKeyIdentifier === info.tbsCertificate.extensions?.authorityKeyIdentifier?.keyIdentifier;
@@ -1356,26 +1360,35 @@ var CertificateManagerState = /* @__PURE__ */ ((CertificateManagerState2) => {
1356
1360
  var CertificateManager = class {
1357
1361
  untrustUnknownCertificate = true;
1358
1362
  state = 0 /* Uninitialized */;
1363
+ /** @deprecated Use {@link folderPollingInterval} instead (typo fix). */
1359
1364
  folderPoolingInterval = 5e3;
1365
+ /** Interval in milliseconds for file-system polling (when enabled). */
1366
+ get folderPollingInterval() {
1367
+ return this.folderPoolingInterval;
1368
+ }
1369
+ set folderPollingInterval(value) {
1370
+ this.folderPoolingInterval = value;
1371
+ }
1360
1372
  keySize;
1361
1373
  location;
1362
1374
  _watchers = [];
1363
1375
  _readCertificatesCalled = false;
1364
- _filenameToHash = {};
1376
+ _filenameToHash = /* @__PURE__ */ new Map();
1377
+ _initializingPromise;
1365
1378
  _thumbs = {
1366
- rejected: {},
1367
- trusted: {},
1379
+ rejected: /* @__PURE__ */ new Map(),
1380
+ trusted: /* @__PURE__ */ new Map(),
1368
1381
  issuers: {
1369
- certs: {}
1382
+ certs: /* @__PURE__ */ new Map()
1370
1383
  },
1371
- crl: {},
1372
- issuersCrl: {}
1384
+ crl: /* @__PURE__ */ new Map(),
1385
+ issuersCrl: /* @__PURE__ */ new Map()
1373
1386
  };
1374
1387
  constructor(options) {
1375
1388
  options.keySize = options.keySize || 2048;
1376
- assert10(Object.prototype.hasOwnProperty.call(options, "location"));
1377
- assert10(Object.prototype.hasOwnProperty.call(options, "keySize"));
1378
- assert10(this.state === 0 /* Uninitialized */);
1389
+ if (!options.location) {
1390
+ throw new Error("CertificateManager: missing 'location' option");
1391
+ }
1379
1392
  this.location = makePath(options.location, "");
1380
1393
  this.keySize = options.keySize;
1381
1394
  mkdirRecursiveSync(options.location);
@@ -1403,15 +1416,11 @@ var CertificateManager = class {
1403
1416
  await this.initialize();
1404
1417
  let status = await this._checkRejectedOrTrusted(certificate);
1405
1418
  if (status === "unknown") {
1406
- assert10(certificate instanceof Buffer);
1407
1419
  const pem = toPem(certificate, "CERTIFICATE");
1408
1420
  const fingerprint2 = makeFingerprint(certificate);
1409
1421
  const filename = path7.join(this.rejectedFolder, `${buildIdealCertificateName(certificate)}.pem`);
1410
1422
  await fs9.promises.writeFile(filename, pem);
1411
- this._thumbs.rejected[fingerprint2] = {
1412
- certificate,
1413
- filename
1414
- };
1423
+ this._thumbs.rejected.set(fingerprint2, { certificate, filename });
1415
1424
  status = "rejected";
1416
1425
  }
1417
1426
  return status;
@@ -1462,30 +1471,27 @@ var CertificateManager = class {
1462
1471
  */
1463
1472
  async isCertificateTrusted(certificate) {
1464
1473
  const fingerprint2 = makeFingerprint(certificate);
1465
- const certificateInTrust = this._thumbs.trusted[fingerprint2]?.certificate;
1466
- if (certificateInTrust) {
1474
+ if (this._thumbs.trusted.has(fingerprint2)) {
1467
1475
  return "Good";
1468
- } else {
1469
- const certificateInRejected = this._thumbs.rejected[fingerprint2];
1470
- if (!certificateInRejected) {
1471
- const certificateFilenameInRejected = path7.join(
1472
- this.rejectedFolder,
1473
- `${buildIdealCertificateName(certificate)}.pem`
1474
- );
1475
- if (!this.untrustUnknownCertificate) {
1476
- return "Good";
1477
- }
1478
- try {
1479
- const _certificateInfo = exploreCertificateInfo(certificate);
1480
- _certificateInfo;
1481
- } catch (_err) {
1482
- return "BadCertificateInvalid";
1483
- }
1484
- debugLog("certificate has never been seen before and is now rejected (untrusted) ", certificateFilenameInRejected);
1485
- await fsWriteFile(certificateFilenameInRejected, toPem(certificate, "CERTIFICATE"));
1476
+ }
1477
+ if (!this._thumbs.rejected.has(fingerprint2)) {
1478
+ if (!this.untrustUnknownCertificate) {
1479
+ return "Good";
1480
+ }
1481
+ try {
1482
+ exploreCertificateInfo(certificate);
1483
+ } catch (_err) {
1484
+ return "BadCertificateInvalid";
1486
1485
  }
1487
- return "BadCertificateUntrusted";
1486
+ const filename = path7.join(
1487
+ this.rejectedFolder,
1488
+ `${buildIdealCertificateName(certificate)}.pem`
1489
+ );
1490
+ debugLog("certificate has never been seen before and is now rejected (untrusted) ", filename);
1491
+ await fsWriteFile(filename, toPem(certificate, "CERTIFICATE"));
1492
+ this._thumbs.rejected.set(fingerprint2, { certificate, filename });
1488
1493
  }
1494
+ return "BadCertificateUntrusted";
1489
1495
  }
1490
1496
  async _innerVerifyCertificateAsync(certificate, _isIssuer, level, options) {
1491
1497
  if (level >= 5) {
@@ -1580,7 +1586,7 @@ var CertificateManager = class {
1580
1586
  return "BadCertificateUntrusted" /* BadCertificateUntrusted */;
1581
1587
  }
1582
1588
  const _c2 = chain[1] ? exploreCertificateInfo(chain[1]) : "non";
1583
- _c2;
1589
+ debugLog("chain[1] info=", _c2);
1584
1590
  const certificateInfo = exploreCertificateInfo(certificate);
1585
1591
  const now = /* @__PURE__ */ new Date();
1586
1592
  let isTimeInvalid = false;
@@ -1603,7 +1609,6 @@ var CertificateManager = class {
1603
1609
  if (status === "trusted") {
1604
1610
  return isTimeInvalid ? "BadCertificateTimeInvalid" /* BadCertificateTimeInvalid */ : "Good" /* Good */;
1605
1611
  }
1606
- assert10(status === "unknown");
1607
1612
  if (hasIssuerKey) {
1608
1613
  if (!hasTrustedIssuer) {
1609
1614
  return "BadCertificateUntrusted" /* BadCertificateUntrusted */;
@@ -1646,7 +1651,9 @@ var CertificateManager = class {
1646
1651
  return;
1647
1652
  }
1648
1653
  this.state = 1 /* Initializing */;
1649
- await this._initialize();
1654
+ this._initializingPromise = this._initialize();
1655
+ await this._initializingPromise;
1656
+ this._initializingPromise = void 0;
1650
1657
  this.state = 2 /* Initialized */;
1651
1658
  }
1652
1659
  async _initialize() {
@@ -1665,11 +1672,9 @@ var CertificateManager = class {
1665
1672
  mkdirRecursiveSync(path7.join(pkiDir, "issuers/crl"));
1666
1673
  if (!fs9.existsSync(this.configFile) || !fs9.existsSync(this.privateKey)) {
1667
1674
  return await this.withLock2(async () => {
1668
- assert10(this.state !== 3 /* Disposing */);
1669
- if (this.state === 4 /* Disposed */) {
1675
+ if (this.state === 3 /* Disposing */ || this.state === 4 /* Disposed */) {
1670
1676
  return;
1671
1677
  }
1672
- assert10(this.state === 1 /* Initializing */);
1673
1678
  if (!fs9.existsSync(this.configFile)) {
1674
1679
  fs9.writeFileSync(this.configFile, configurationFileSimpleTemplate);
1675
1680
  }
@@ -1699,8 +1704,9 @@ var CertificateManager = class {
1699
1704
  return;
1700
1705
  }
1701
1706
  if (this.state === 1 /* Initializing */) {
1702
- await new Promise((resolve) => setTimeout(resolve, 100));
1703
- return await this.dispose();
1707
+ if (this._initializingPromise) {
1708
+ await this._initializingPromise;
1709
+ }
1704
1710
  }
1705
1711
  try {
1706
1712
  this.state = 3 /* Disposing */;
@@ -1713,6 +1719,30 @@ var CertificateManager = class {
1713
1719
  this.state = 4 /* Disposed */;
1714
1720
  }
1715
1721
  }
1722
+ /**
1723
+ * Force a full re-scan of all PKI folders, rebuilding
1724
+ * the in-memory `_thumbs` index from scratch.
1725
+ *
1726
+ * Call this after external processes have modified the
1727
+ * PKI folders (e.g. via `writeTrustList` or CLI tools)
1728
+ * to ensure the CertificateManager sees the latest
1729
+ * state without waiting for file-system events.
1730
+ */
1731
+ async reloadCertificates() {
1732
+ await Promise.all(this._watchers.map((w) => w.close()));
1733
+ for (const w of this._watchers) {
1734
+ w.removeAllListeners();
1735
+ }
1736
+ this._watchers.splice(0);
1737
+ this._thumbs.rejected.clear();
1738
+ this._thumbs.trusted.clear();
1739
+ this._thumbs.issuers.certs.clear();
1740
+ this._thumbs.crl.clear();
1741
+ this._thumbs.issuersCrl.clear();
1742
+ this._filenameToHash.clear();
1743
+ this._readCertificatesCalled = false;
1744
+ await this._readCertificates();
1745
+ }
1716
1746
  async withLock2(action) {
1717
1747
  const lockFileName = path7.join(this.rootDir, "mutex.lock");
1718
1748
  return withLock({ fileToLock: lockFileName }, async () => {
@@ -1725,7 +1755,9 @@ var CertificateManager = class {
1725
1755
  *
1726
1756
  */
1727
1757
  async createSelfSignedCertificate(params) {
1728
- assert10(typeof params.applicationUri === "string", "expecting applicationUri");
1758
+ if (typeof params.applicationUri !== "string") {
1759
+ throw new Error("createSelfSignedCertificate: expecting applicationUri to be a string");
1760
+ }
1729
1761
  if (!fs9.existsSync(this.privateKey)) {
1730
1762
  throw new Error(`Cannot find private key ${this.privateKey}`);
1731
1763
  }
@@ -1741,14 +1773,13 @@ var CertificateManager = class {
1741
1773
  });
1742
1774
  }
1743
1775
  async createCertificateRequest(params) {
1744
- assert10(params);
1776
+ if (!params) {
1777
+ throw new Error("params is required");
1778
+ }
1745
1779
  const _params = params;
1746
1780
  if (Object.prototype.hasOwnProperty.call(_params, "rootDir")) {
1747
1781
  throw new Error("rootDir should not be specified ");
1748
1782
  }
1749
- assert10(!_params.rootDir);
1750
- assert10(!_params.configFile);
1751
- assert10(!_params.privateKey);
1752
1783
  _params.rootDir = path7.resolve(this.rootDir);
1753
1784
  _params.configFile = path7.resolve(this.configFile);
1754
1785
  _params.privateKey = path7.resolve(this.privateKey);
@@ -1777,12 +1808,12 @@ var CertificateManager = class {
1777
1808
  }
1778
1809
  const pemCertificate = toPem(certificate, "CERTIFICATE");
1779
1810
  const fingerprint2 = makeFingerprint(certificate);
1780
- if (this._thumbs.issuers.certs[fingerprint2]) {
1811
+ if (this._thumbs.issuers.certs.has(fingerprint2)) {
1781
1812
  return "Good" /* Good */;
1782
1813
  }
1783
1814
  const filename = path7.join(this.issuersCertFolder, `issuer_${buildIdealCertificateName(certificate)}.pem`);
1784
1815
  await fs9.promises.writeFile(filename, pemCertificate, "ascii");
1785
- this._thumbs.issuers.certs[fingerprint2] = { certificate, filename };
1816
+ this._thumbs.issuers.certs.set(fingerprint2, { certificate, filename });
1786
1817
  if (addInTrustList) {
1787
1818
  await this.trustCertificate(certificate);
1788
1819
  }
@@ -1800,8 +1831,8 @@ var CertificateManager = class {
1800
1831
  const folder = target === "trusted" ? this.crlFolder : this.issuersCrlFolder;
1801
1832
  const crlInfo = exploreCertificateRevocationList(crl);
1802
1833
  const key = crlInfo.tbsCertList.issuerFingerprint;
1803
- if (!index[key]) {
1804
- index[key] = { crls: [], serialNumbers: {} };
1834
+ if (!index.has(key)) {
1835
+ index.set(key, { crls: [], serialNumbers: {} });
1805
1836
  }
1806
1837
  const pemCertificate = toPem(crl, "X509 CRL");
1807
1838
  const filename = path7.join(folder, `crl_${buildIdealCertificateName(crl)}.pem`);
@@ -1836,9 +1867,7 @@ var CertificateManager = class {
1836
1867
  throw err;
1837
1868
  }
1838
1869
  }
1839
- for (const key of Object.keys(index)) {
1840
- delete index[key];
1841
- }
1870
+ index.clear();
1842
1871
  };
1843
1872
  if (target === "issuers" || target === "all") {
1844
1873
  await clearFolder(this.issuersCrlFolder, this._thumbs.issuersCrl);
@@ -1855,7 +1884,7 @@ var CertificateManager = class {
1855
1884
  async hasIssuer(thumbprint) {
1856
1885
  await this._readCertificates();
1857
1886
  const normalized = thumbprint.toLowerCase();
1858
- return Object.prototype.hasOwnProperty.call(this._thumbs.issuers.certs, normalized);
1887
+ return this._thumbs.issuers.certs.has(normalized);
1859
1888
  }
1860
1889
  /**
1861
1890
  * Remove a trusted certificate identified by its SHA-1 thumbprint.
@@ -1867,7 +1896,7 @@ var CertificateManager = class {
1867
1896
  async removeTrustedCertificate(thumbprint) {
1868
1897
  await this._readCertificates();
1869
1898
  const normalized = thumbprint.toLowerCase();
1870
- const entry = this._thumbs.trusted[normalized];
1899
+ const entry = this._thumbs.trusted.get(normalized);
1871
1900
  if (!entry) {
1872
1901
  return null;
1873
1902
  }
@@ -1878,7 +1907,7 @@ var CertificateManager = class {
1878
1907
  throw err;
1879
1908
  }
1880
1909
  }
1881
- delete this._thumbs.trusted[normalized];
1910
+ this._thumbs.trusted.delete(normalized);
1882
1911
  return entry.certificate;
1883
1912
  }
1884
1913
  /**
@@ -1891,7 +1920,7 @@ var CertificateManager = class {
1891
1920
  async removeIssuer(thumbprint) {
1892
1921
  await this._readCertificates();
1893
1922
  const normalized = thumbprint.toLowerCase();
1894
- const entry = this._thumbs.issuers.certs[normalized];
1923
+ const entry = this._thumbs.issuers.certs.get(normalized);
1895
1924
  if (!entry) {
1896
1925
  return null;
1897
1926
  }
@@ -1902,7 +1931,7 @@ var CertificateManager = class {
1902
1931
  throw err;
1903
1932
  }
1904
1933
  }
1905
- delete this._thumbs.issuers.certs[normalized];
1934
+ this._thumbs.issuers.certs.delete(normalized);
1906
1935
  return entry.certificate;
1907
1936
  }
1908
1937
  /**
@@ -1915,7 +1944,7 @@ var CertificateManager = class {
1915
1944
  const issuerInfo = exploreCertificate(issuerCertificate);
1916
1945
  const issuerFingerprint = issuerInfo.tbsCertificate.subjectFingerPrint;
1917
1946
  const processIndex = async (index) => {
1918
- const crlData = index[issuerFingerprint];
1947
+ const crlData = index.get(issuerFingerprint);
1919
1948
  if (!crlData) return;
1920
1949
  for (const crlEntry of crlData.crls) {
1921
1950
  try {
@@ -1926,7 +1955,7 @@ var CertificateManager = class {
1926
1955
  }
1927
1956
  }
1928
1957
  }
1929
- delete index[issuerFingerprint];
1958
+ index.delete(issuerFingerprint);
1930
1959
  };
1931
1960
  if (target === "issuers" || target === "all") {
1932
1961
  await processIndex(this._thumbs.issuersCrl);
@@ -1964,9 +1993,23 @@ var CertificateManager = class {
1964
1993
  return "BadCertificateInvalid" /* BadCertificateInvalid */;
1965
1994
  }
1966
1995
  if (certificates.length > 1) {
1996
+ const issuerFolder = this.issuersCertFolder;
1997
+ const issuerThumbprints = /* @__PURE__ */ new Set();
1998
+ const files = await fs9.promises.readdir(issuerFolder);
1999
+ for (const file of files) {
2000
+ const ext = path7.extname(file).toLowerCase();
2001
+ if (ext === ".pem" || ext === ".der") {
2002
+ try {
2003
+ const issuerCert = readCertificate(path7.join(issuerFolder, file));
2004
+ const fp = makeFingerprint(issuerCert);
2005
+ issuerThumbprints.add(fp);
2006
+ } catch (_err) {
2007
+ }
2008
+ }
2009
+ }
1967
2010
  for (const issuerCert of certificates.slice(1)) {
1968
2011
  const thumbprint = makeFingerprint(issuerCert);
1969
- if (!Object.prototype.hasOwnProperty.call(this._thumbs.issuers.certs, thumbprint)) {
2012
+ if (!issuerThumbprints.has(thumbprint)) {
1970
2013
  return "BadCertificateChainIncomplete" /* BadCertificateChainIncomplete */;
1971
2014
  }
1972
2015
  }
@@ -1988,7 +2031,7 @@ var CertificateManager = class {
1988
2031
  */
1989
2032
  async isIssuerInUseByTrustedCertificate(issuerCertificate) {
1990
2033
  await this._readCertificates();
1991
- for (const entry of Object.values(this._thumbs.trusted)) {
2034
+ for (const entry of this._thumbs.trusted.values()) {
1992
2035
  if (!entry.certificate) continue;
1993
2036
  try {
1994
2037
  if (verifyCertificateSignature(entry.certificate, issuerCertificate)) {
@@ -2023,7 +2066,7 @@ var CertificateManager = class {
2023
2066
  debugLog("Certificate has no extension 3");
2024
2067
  return null;
2025
2068
  }
2026
- const issuerCertificates = Object.values(this._thumbs.issuers.certs);
2069
+ const issuerCertificates = [...this._thumbs.issuers.certs.values()];
2027
2070
  const selectedIssuerCertificates = findMatchingIssuerKey(issuerCertificates, wantedIssuerKey);
2028
2071
  if (selectedIssuerCertificates.length > 0) {
2029
2072
  if (selectedIssuerCertificates.length > 1) {
@@ -2031,7 +2074,7 @@ var CertificateManager = class {
2031
2074
  }
2032
2075
  return selectedIssuerCertificates[0].certificate || null;
2033
2076
  }
2034
- const trustedCertificates = Object.values(this._thumbs.trusted);
2077
+ const trustedCertificates = [...this._thumbs.trusted.values()];
2035
2078
  const selectedTrustedCertificates = findMatchingIssuerKey(trustedCertificates, wantedIssuerKey);
2036
2079
  if (selectedTrustedCertificates.length > 1) {
2037
2080
  warningLog(
@@ -2047,48 +2090,47 @@ var CertificateManager = class {
2047
2090
  * @private
2048
2091
  */
2049
2092
  async _checkRejectedOrTrusted(certificate) {
2050
- assert10(certificate instanceof Buffer);
2051
2093
  const fingerprint2 = makeFingerprint(certificate);
2052
2094
  debugLog("_checkRejectedOrTrusted fingerprint ", short(fingerprint2));
2053
2095
  await this._readCertificates();
2054
- if (Object.prototype.hasOwnProperty.call(this._thumbs.rejected, fingerprint2)) {
2096
+ if (this._thumbs.rejected.has(fingerprint2)) {
2055
2097
  return "rejected";
2056
2098
  }
2057
- if (Object.prototype.hasOwnProperty.call(this._thumbs.trusted, fingerprint2)) {
2099
+ if (this._thumbs.trusted.has(fingerprint2)) {
2058
2100
  return "trusted";
2059
2101
  }
2060
2102
  return "unknown";
2061
2103
  }
2062
2104
  async _moveCertificate(certificate, newStatus) {
2063
- assert10(certificate instanceof Buffer);
2064
- const fingerprint2 = makeFingerprint(certificate);
2065
- const status = await this.getCertificateStatus(certificate);
2066
- debugLog("_moveCertificate", fingerprint2.substring(0, 10), "from", status, "to", newStatus);
2067
- assert10(status === "rejected" || status === "trusted");
2068
- if (status !== newStatus) {
2069
- const indexSrc = status === "rejected" ? this._thumbs.rejected : this._thumbs.trusted;
2070
- const certificateSrc = indexSrc[fingerprint2]?.filename;
2071
- if (!certificateSrc) {
2072
- debugLog(" cannot find certificate ", fingerprint2.substring(0, 10), " in", this._thumbs, [status]);
2073
- throw new Error("internal");
2105
+ await this.withLock2(async () => {
2106
+ const fingerprint2 = makeFingerprint(certificate);
2107
+ const status = await this.getCertificateStatus(certificate);
2108
+ debugLog("_moveCertificate", fingerprint2.substring(0, 10), "from", status, "to", newStatus);
2109
+ if (status !== "rejected" && status !== "trusted") {
2110
+ throw new Error(`_moveCertificate: unexpected status '${status}' for certificate ${fingerprint2.substring(0, 10)}`);
2074
2111
  }
2075
- const destFolder = newStatus === "rejected" ? this.rejectedFolder : newStatus === "trusted" ? this.trustedFolder : this.rejectedFolder;
2076
- const certificateDest = path7.join(destFolder, path7.basename(certificateSrc));
2077
- debugLog("_moveCertificate1", fingerprint2.substring(0, 10), "old name", certificateSrc);
2078
- debugLog("_moveCertificate1", fingerprint2.substring(0, 10), "new name", certificateDest);
2079
- await fs9.promises.rename(certificateSrc, certificateDest);
2080
- delete indexSrc[fingerprint2];
2081
- const indexDest = newStatus === "rejected" ? this._thumbs.rejected : newStatus === "trusted" ? this._thumbs.trusted : this._thumbs.rejected;
2082
- indexDest[fingerprint2] = {
2083
- certificate,
2084
- filename: certificateDest
2085
- };
2086
- }
2112
+ if (status !== newStatus) {
2113
+ const indexSrc = status === "rejected" ? this._thumbs.rejected : this._thumbs.trusted;
2114
+ const srcEntry = indexSrc.get(fingerprint2);
2115
+ if (!srcEntry) {
2116
+ debugLog(" cannot find certificate ", fingerprint2.substring(0, 10), " in", status);
2117
+ throw new Error(`_moveCertificate: certificate ${fingerprint2.substring(0, 10)} not found in ${status} index`);
2118
+ }
2119
+ const destFolder = newStatus === "trusted" ? this.trustedFolder : this.rejectedFolder;
2120
+ const certificateDest = path7.join(destFolder, path7.basename(srcEntry.filename));
2121
+ debugLog("_moveCertificate", fingerprint2.substring(0, 10), "old name", srcEntry.filename);
2122
+ debugLog("_moveCertificate", fingerprint2.substring(0, 10), "new name", certificateDest);
2123
+ await fs9.promises.rename(srcEntry.filename, certificateDest);
2124
+ indexSrc.delete(fingerprint2);
2125
+ const indexDest = newStatus === "trusted" ? this._thumbs.trusted : this._thumbs.rejected;
2126
+ indexDest.set(fingerprint2, { certificate, filename: certificateDest });
2127
+ }
2128
+ });
2087
2129
  }
2088
2130
  _findAssociatedCRLs(issuerCertificate) {
2089
2131
  const issuerCertificateInfo = exploreCertificate(issuerCertificate);
2090
2132
  const key = issuerCertificateInfo.tbsCertificate.subjectFingerPrint;
2091
- return this._thumbs.issuersCrl[key] ? this._thumbs.issuersCrl[key] : this._thumbs.crl[key] ? this._thumbs.crl[key] : null;
2133
+ return this._thumbs.issuersCrl.get(key) ?? this._thumbs.crl.get(key) ?? null;
2092
2134
  }
2093
2135
  async isCertificateRevoked(certificate, issuerCertificate) {
2094
2136
  if (isSelfSigned3(certificate)) {
@@ -2107,7 +2149,7 @@ var CertificateManager = class {
2107
2149
  const certInfo = exploreCertificate(certificate);
2108
2150
  const serialNumber = certInfo.tbsCertificate.serialNumber || certInfo.tbsCertificate.extensions?.authorityKeyIdentifier?.serial || "";
2109
2151
  const key = certInfo.tbsCertificate.extensions?.authorityKeyIdentifier?.authorityCertIssuerFingerPrint || "<unknown>";
2110
- const crl2 = this._thumbs.crl[key] || null;
2152
+ const crl2 = this._thumbs.crl.get(key) ?? null;
2111
2153
  if (crls.serialNumbers[serialNumber] || crl2?.serialNumbers[serialNumber]) {
2112
2154
  return "BadCertificateRevoked" /* BadCertificateRevoked */;
2113
2155
  }
@@ -2132,19 +2174,18 @@ var CertificateManager = class {
2132
2174
  const crlInfo = exploreCertificateRevocationList(crl);
2133
2175
  debugLog(chalk6.cyan("add CRL in folder "), filename);
2134
2176
  const fingerprint2 = crlInfo.tbsCertList.issuerFingerprint;
2135
- index[fingerprint2] = index[fingerprint2] || {
2136
- crls: [],
2137
- serialNumbers: {}
2138
- };
2139
- index[fingerprint2].crls.push({ crlInfo, filename });
2140
- const serialNumbers = index[fingerprint2].serialNumbers;
2177
+ if (!index.has(fingerprint2)) {
2178
+ index.set(fingerprint2, { crls: [], serialNumbers: {} });
2179
+ }
2180
+ const data = index.get(fingerprint2) || { crls: [], serialNumbers: {} };
2181
+ data.crls.push({ crlInfo, filename });
2141
2182
  for (const revokedCertificate of crlInfo.tbsCertList.revokedCertificates) {
2142
2183
  const serialNumber = revokedCertificate.userCertificate;
2143
- if (!serialNumbers[serialNumber]) {
2144
- serialNumbers[serialNumber] = revokedCertificate.revocationDate;
2184
+ if (!data.serialNumbers[serialNumber]) {
2185
+ data.serialNumbers[serialNumber] = revokedCertificate.revocationDate;
2145
2186
  }
2146
2187
  }
2147
- debugLog(chalk6.cyan("CRL"), fingerprint2, "serial numbers = ", Object.keys(serialNumbers));
2188
+ debugLog(chalk6.cyan("CRL"), fingerprint2, "serial numbers = ", Object.keys(data.serialNumbers));
2148
2189
  } catch (err) {
2149
2190
  debugLog("CRL filename error =");
2150
2191
  debugLog(err);
@@ -2164,28 +2205,28 @@ var CertificateManager = class {
2164
2205
  return;
2165
2206
  }
2166
2207
  this._readCertificatesCalled = true;
2208
+ const usePolling = process.env.OPCUA_PKI_USE_POLLING === "true";
2167
2209
  const options = {
2168
- usePolling: true,
2169
- interval: Math.min(10 * 60 * 1e3, Math.max(100, this.folderPoolingInterval)),
2170
- persistent: false,
2171
- awaitWriteFinish: {
2172
- stabilityThreshold: 2e3,
2173
- pollInterval: 600
2174
- }
2210
+ usePolling,
2211
+ ...usePolling ? { interval: Math.min(10 * 60 * 1e3, Math.max(100, this.folderPoolingInterval)) } : {},
2212
+ persistent: false
2175
2213
  };
2176
2214
  async function _walkCRLFiles(folder, index) {
2177
2215
  await new Promise((resolve, _reject) => {
2178
2216
  const w = chokidar.watch(folder, options);
2179
- w.on("unlink", (filename, stat) => {
2180
- filename;
2181
- stat;
2217
+ w.on("unlink", (filename) => {
2218
+ for (const [key, data] of index.entries()) {
2219
+ data.crls = data.crls.filter((c) => c.filename !== filename);
2220
+ if (data.crls.length === 0) {
2221
+ index.delete(key);
2222
+ }
2223
+ }
2182
2224
  });
2183
- w.on("add", (filename, stat) => {
2184
- stat;
2225
+ w.on("add", (filename) => {
2185
2226
  this._on_crl_file_added(index, filename);
2186
2227
  });
2187
- w.on("change", (path9, stat) => {
2188
- debugLog("change in folder ", folder, path9, stat);
2228
+ w.on("change", (changedPath) => {
2229
+ debugLog("change in folder ", folder, changedPath);
2189
2230
  });
2190
2231
  this._watchers.push(w);
2191
2232
  w.on("ready", () => {
@@ -2195,26 +2236,21 @@ var CertificateManager = class {
2195
2236
  }
2196
2237
  async function _walkAllFiles(folder, index) {
2197
2238
  const w = chokidar.watch(folder, options);
2198
- w.on("unlink", (filename, stat) => {
2199
- stat;
2239
+ w.on("unlink", (filename) => {
2200
2240
  debugLog(chalk6.cyan(`unlink in folder ${folder}`), filename);
2201
- const h = this._filenameToHash[filename];
2202
- if (h && index[h]) {
2203
- delete index[h];
2241
+ const h = this._filenameToHash.get(filename);
2242
+ if (h && index.has(h)) {
2243
+ index.delete(h);
2204
2244
  }
2205
2245
  });
2206
- w.on("add", (filename, stat) => {
2207
- stat;
2246
+ w.on("add", (filename) => {
2208
2247
  debugLog(chalk6.cyan(`add in folder ${folder}`), filename);
2209
2248
  try {
2210
2249
  const certificate = readCertificate(filename);
2211
2250
  const info = exploreCertificate(certificate);
2212
2251
  const fingerprint2 = makeFingerprint(certificate);
2213
- index[fingerprint2] = {
2214
- certificate,
2215
- filename
2216
- };
2217
- this._filenameToHash[filename] = fingerprint2;
2252
+ index.set(fingerprint2, { certificate, filename, info });
2253
+ this._filenameToHash.set(filename, fingerprint2);
2218
2254
  debugLog(
2219
2255
  chalk6.magenta("CERT"),
2220
2256
  info.tbsCertificate.subjectFingerPrint,
@@ -2226,15 +2262,26 @@ var CertificateManager = class {
2226
2262
  debugLog(err);
2227
2263
  }
2228
2264
  });
2229
- w.on("change", (path9, stat) => {
2230
- stat;
2231
- debugLog("change in folder ", folder, path9);
2265
+ w.on("change", (changedPath) => {
2266
+ debugLog(chalk6.cyan(`change in folder ${folder}`), changedPath);
2267
+ try {
2268
+ const certificate = readCertificate(changedPath);
2269
+ const newFingerprint = makeFingerprint(certificate);
2270
+ const oldHash = this._filenameToHash.get(changedPath);
2271
+ if (oldHash && oldHash !== newFingerprint) {
2272
+ index.delete(oldHash);
2273
+ }
2274
+ index.set(newFingerprint, { certificate, filename: changedPath, info: exploreCertificate(certificate) });
2275
+ this._filenameToHash.set(changedPath, newFingerprint);
2276
+ } catch (err) {
2277
+ debugLog(`change event: failed to re-read ${changedPath}`, err);
2278
+ }
2232
2279
  });
2233
2280
  this._watchers.push(w);
2234
2281
  await new Promise((resolve, _reject) => {
2235
2282
  w.on("ready", () => {
2236
2283
  debugLog("ready");
2237
- debugLog(Object.entries(index).map((kv) => kv[0].substring(0, 10)));
2284
+ debugLog([...index.keys()].map((k) => k.substring(0, 10)));
2238
2285
  resolve();
2239
2286
  });
2240
2287
  });
@@ -2280,8 +2327,8 @@ var next_year = get_offset_date(today, 365);
2280
2327
  var gLocalConfig = {};
2281
2328
  var g_certificateAuthority;
2282
2329
  async function construct_CertificateAuthority2(subject) {
2283
- assert11(typeof gLocalConfig.CAFolder === "string", "expecting a CAFolder in config");
2284
- assert11(typeof gLocalConfig.keySize === "number", "expecting a keySize in config");
2330
+ assert10(typeof gLocalConfig.CAFolder === "string", "expecting a CAFolder in config");
2331
+ assert10(typeof gLocalConfig.keySize === "number", "expecting a keySize in config");
2285
2332
  if (!g_certificateAuthority) {
2286
2333
  g_certificateAuthority = new CertificateAuthority({
2287
2334
  keySize: gLocalConfig.keySize,
@@ -2293,7 +2340,7 @@ async function construct_CertificateAuthority2(subject) {
2293
2340
  }
2294
2341
  var certificateManager;
2295
2342
  async function construct_CertificateManager() {
2296
- assert11(typeof gLocalConfig.PKIFolder === "string", "expecting a PKIFolder in config");
2343
+ assert10(typeof gLocalConfig.PKIFolder === "string", "expecting a PKIFolder in config");
2297
2344
  if (!certificateManager) {
2298
2345
  certificateManager = new CertificateManager({
2299
2346
  keySize: gLocalConfig.keySize,
@@ -2320,7 +2367,7 @@ function default_template_content() {
2320
2367
  return default_config_template2;
2321
2368
  }
2322
2369
  const default_config_template = find_default_config_template();
2323
- assert11(fs10.existsSync(default_config_template));
2370
+ assert10(fs10.existsSync(default_config_template));
2324
2371
  const default_config_template_content = fs10.readFileSync(default_config_template, "utf8");
2325
2372
  return default_config_template_content;
2326
2373
  }
@@ -2332,7 +2379,7 @@ function find_module_root_folder() {
2332
2379
  }
2333
2380
  rootFolder = path8.join(rootFolder, "..");
2334
2381
  }
2335
- assert11(fs10.existsSync(path8.join(rootFolder, "package.json")), "root folder must have a package.json file");
2382
+ assert10(fs10.existsSync(path8.join(rootFolder, "package.json")), "root folder must have a package.json file");
2336
2383
  return rootFolder;
2337
2384
  }
2338
2385
  async function readConfiguration(argv) {
@@ -2361,10 +2408,10 @@ async function readConfiguration(argv) {
2361
2408
  return makePath(tmp);
2362
2409
  }
2363
2410
  certificateDir = argv.root;
2364
- assert11(typeof certificateDir === "string");
2411
+ assert10(typeof certificateDir === "string");
2365
2412
  certificateDir = prepare(certificateDir);
2366
2413
  mkdirRecursiveSync(certificateDir);
2367
- assert11(fs10.existsSync(certificateDir));
2414
+ assert10(fs10.existsSync(certificateDir));
2368
2415
  const default_config = path8.join(certificateDir, "config.js");
2369
2416
  if (!fs10.existsSync(default_config)) {
2370
2417
  debugLog(chalk7.yellow(" Creating default g_config file "), chalk7.cyan(default_config));
@@ -2429,7 +2476,7 @@ async function readConfiguration(argv) {
2429
2476
  }
2430
2477
  }
2431
2478
  async function createDefaultCertificate(base_name, prefix, key_length, applicationUri, dev) {
2432
- assert11(key_length === 1024 || key_length === 2048 || key_length === 3072 || key_length === 4096);
2479
+ assert10(key_length === 1024 || key_length === 2048 || key_length === 3072 || key_length === 4096);
2433
2480
  const private_key_file = makePath(base_name, `${prefix}key_${key_length}.pem`);
2434
2481
  const public_key_file = makePath(base_name, `${prefix}public_key_${key_length}.pub`);
2435
2482
  const certificate_file = makePath(base_name, `${prefix}cert_${key_length}.pem`);
@@ -2535,9 +2582,9 @@ async function wrap(func) {
2535
2582
  }
2536
2583
  }
2537
2584
  async function create_default_certificates(dev) {
2538
- assert11(gLocalConfig);
2585
+ assert10(gLocalConfig);
2539
2586
  const base_name = gLocalConfig.certificateDir || "";
2540
- assert11(fs10.existsSync(base_name));
2587
+ assert10(fs10.existsSync(base_name));
2541
2588
  let clientURN;
2542
2589
  let serverURN;
2543
2590
  let discoveryServerURN;
@@ -2692,7 +2739,7 @@ ${epilog}`
2692
2739
  await readConfiguration(local_argv);
2693
2740
  if (local_argv.clean) {
2694
2741
  displayTitle("Cleaning old certificates");
2695
- assert11(gLocalConfig);
2742
+ assert10(gLocalConfig);
2696
2743
  const certificateDir = gLocalConfig.certificateDir || "";
2697
2744
  const files = await fs10.promises.readdir(certificateDir);
2698
2745
  for (const file of files) {
@@ -2804,7 +2851,7 @@ ${epilog}`
2804
2851
  await readConfiguration(local_argv2);
2805
2852
  await construct_CertificateManager();
2806
2853
  await construct_CertificateAuthority2("");
2807
- assert11(fs10.existsSync(gLocalConfig.CAFolder || ""), " CA folder must exist");
2854
+ assert10(fs10.existsSync(gLocalConfig.CAFolder || ""), " CA folder must exist");
2808
2855
  gLocalConfig.privateKey = void 0;
2809
2856
  gLocalConfig.subject = local_argv2.subject && local_argv2.subject.length > 1 ? local_argv2.subject : gLocalConfig.subject;
2810
2857
  const csr_file = await certificateManager.createCertificateRequest(
@@ -2823,7 +2870,7 @@ ${epilog}`
2823
2870
  csr_file,
2824
2871
  gLocalConfig
2825
2872
  );
2826
- assert11(typeof gLocalConfig.outputFile === "string");
2873
+ assert10(typeof gLocalConfig.outputFile === "string");
2827
2874
  fs10.writeFileSync(gLocalConfig.outputFile || "", fs10.readFileSync(certificate, "ascii"));
2828
2875
  }
2829
2876
  await wrap(async () => await command_certificate(local_argv));
@@ -2957,7 +3004,7 @@ ${epilog}`
2957
3004
  csr_file,
2958
3005
  gLocalConfig
2959
3006
  );
2960
- assert11(typeof gLocalConfig.outputFile === "string");
3007
+ assert10(typeof gLocalConfig.outputFile === "string");
2961
3008
  fs10.writeFileSync(gLocalConfig.outputFile || "", fs10.readFileSync(certificate, "ascii"));
2962
3009
  });
2963
3010
  return;