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.d.mts +15 -0
- package/dist/index.d.ts +15 -0
- package/dist/index.js +199 -152
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +199 -152
- package/dist/index.mjs.map +1 -1
- package/package.json +2 -2
- package/readme.md +24 -7
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
|
|
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
|
-
|
|
1310
|
-
const info =
|
|
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
|
-
|
|
1377
|
-
|
|
1378
|
-
|
|
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
|
|
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
|
-
|
|
1466
|
-
if (certificateInTrust) {
|
|
1474
|
+
if (this._thumbs.trusted.has(fingerprint2)) {
|
|
1467
1475
|
return "Good";
|
|
1468
|
-
}
|
|
1469
|
-
|
|
1470
|
-
if (!
|
|
1471
|
-
|
|
1472
|
-
|
|
1473
|
-
|
|
1474
|
-
);
|
|
1475
|
-
|
|
1476
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
1703
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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
|
|
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
|
|
1804
|
-
index
|
|
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
|
-
|
|
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
|
|
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
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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
|
|
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
|
|
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 (!
|
|
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
|
|
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 =
|
|
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 =
|
|
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 (
|
|
2096
|
+
if (this._thumbs.rejected.has(fingerprint2)) {
|
|
2055
2097
|
return "rejected";
|
|
2056
2098
|
}
|
|
2057
|
-
if (
|
|
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
|
-
|
|
2064
|
-
|
|
2065
|
-
|
|
2066
|
-
|
|
2067
|
-
|
|
2068
|
-
|
|
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
|
-
|
|
2076
|
-
|
|
2077
|
-
|
|
2078
|
-
|
|
2079
|
-
|
|
2080
|
-
|
|
2081
|
-
|
|
2082
|
-
|
|
2083
|
-
|
|
2084
|
-
filename
|
|
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
|
|
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
|
|
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
|
-
|
|
2136
|
-
crls: [],
|
|
2137
|
-
|
|
2138
|
-
};
|
|
2139
|
-
|
|
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
|
|
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
|
|
2180
|
-
|
|
2181
|
-
|
|
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
|
|
2184
|
-
stat;
|
|
2225
|
+
w.on("add", (filename) => {
|
|
2185
2226
|
this._on_crl_file_added(index, filename);
|
|
2186
2227
|
});
|
|
2187
|
-
w.on("change", (
|
|
2188
|
-
debugLog("change in folder ", folder,
|
|
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
|
|
2199
|
-
stat;
|
|
2239
|
+
w.on("unlink", (filename) => {
|
|
2200
2240
|
debugLog(chalk6.cyan(`unlink in folder ${folder}`), filename);
|
|
2201
|
-
const h = this._filenameToHash
|
|
2202
|
-
if (h && index
|
|
2203
|
-
delete
|
|
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
|
|
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
|
|
2214
|
-
|
|
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", (
|
|
2230
|
-
|
|
2231
|
-
|
|
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(
|
|
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
|
-
|
|
2284
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
2411
|
+
assert10(typeof certificateDir === "string");
|
|
2365
2412
|
certificateDir = prepare(certificateDir);
|
|
2366
2413
|
mkdirRecursiveSync(certificateDir);
|
|
2367
|
-
|
|
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
|
-
|
|
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
|
-
|
|
2585
|
+
assert10(gLocalConfig);
|
|
2539
2586
|
const base_name = gLocalConfig.certificateDir || "";
|
|
2540
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
3007
|
+
assert10(typeof gLocalConfig.outputFile === "string");
|
|
2961
3008
|
fs10.writeFileSync(gLocalConfig.outputFile || "", fs10.readFileSync(certificate, "ascii"));
|
|
2962
3009
|
});
|
|
2963
3010
|
return;
|