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.d.mts +15 -0
- package/dist/index.d.ts +15 -0
- package/dist/index.js +202 -157
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +203 -157
- 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";
|
|
@@ -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
|
-
|
|
1309
|
-
const info =
|
|
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
|
-
|
|
1376
|
-
|
|
1377
|
-
|
|
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
|
|
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
|
-
|
|
1465
|
-
if (certificateInTrust) {
|
|
1474
|
+
if (this._thumbs.trusted.has(fingerprint2)) {
|
|
1466
1475
|
return "Good";
|
|
1467
|
-
}
|
|
1468
|
-
|
|
1469
|
-
if (!
|
|
1470
|
-
|
|
1471
|
-
|
|
1472
|
-
|
|
1473
|
-
);
|
|
1474
|
-
|
|
1475
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
1702
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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
|
|
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
|
|
1803
|
-
index
|
|
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
|
-
|
|
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
|
|
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
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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
|
|
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
|
|
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
|
|
1962
|
-
|
|
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 (!
|
|
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
|
|
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 =
|
|
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 =
|
|
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 (
|
|
2096
|
+
if (this._thumbs.rejected.has(fingerprint2)) {
|
|
2056
2097
|
return "rejected";
|
|
2057
2098
|
}
|
|
2058
|
-
if (
|
|
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
|
-
|
|
2065
|
-
|
|
2066
|
-
|
|
2067
|
-
|
|
2068
|
-
|
|
2069
|
-
|
|
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
|
-
|
|
2077
|
-
|
|
2078
|
-
|
|
2079
|
-
|
|
2080
|
-
|
|
2081
|
-
|
|
2082
|
-
|
|
2083
|
-
|
|
2084
|
-
|
|
2085
|
-
filename
|
|
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
|
|
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
|
|
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
|
-
|
|
2137
|
-
crls: [],
|
|
2138
|
-
|
|
2139
|
-
};
|
|
2140
|
-
|
|
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
|
|
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
|
|
2181
|
-
|
|
2182
|
-
|
|
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
|
|
2185
|
-
stat;
|
|
2225
|
+
w.on("add", (filename) => {
|
|
2186
2226
|
this._on_crl_file_added(index, filename);
|
|
2187
2227
|
});
|
|
2188
|
-
w.on("change", (
|
|
2189
|
-
debugLog("change in folder ", folder,
|
|
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
|
|
2200
|
-
stat;
|
|
2239
|
+
w.on("unlink", (filename) => {
|
|
2201
2240
|
debugLog(chalk6.cyan(`unlink in folder ${folder}`), filename);
|
|
2202
|
-
const h = this._filenameToHash
|
|
2203
|
-
if (h && index
|
|
2204
|
-
delete
|
|
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
|
|
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
|
|
2215
|
-
|
|
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", (
|
|
2231
|
-
|
|
2232
|
-
|
|
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(
|
|
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
|
-
|
|
2285
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
2411
|
+
assert10(typeof certificateDir === "string");
|
|
2366
2412
|
certificateDir = prepare(certificateDir);
|
|
2367
2413
|
mkdirRecursiveSync(certificateDir);
|
|
2368
|
-
|
|
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
|
-
|
|
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
|
-
|
|
2585
|
+
assert10(gLocalConfig);
|
|
2540
2586
|
const base_name = gLocalConfig.certificateDir || "";
|
|
2541
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
3007
|
+
assert10(typeof gLocalConfig.outputFile === "string");
|
|
2962
3008
|
fs10.writeFileSync(gLocalConfig.outputFile || "", fs10.readFileSync(certificate, "ascii"));
|
|
2963
3009
|
});
|
|
2964
3010
|
return;
|