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