node-opcua-pki 6.12.2 → 6.14.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/bin/pki.mjs +93 -8
- package/dist/bin/pki.mjs.map +1 -1
- package/dist/index.d.mts +60 -1
- package/dist/index.d.ts +60 -1
- package/dist/index.js +94 -7
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +93 -7
- package/dist/index.mjs.map +1 -1
- package/package.json +2 -2
package/dist/bin/pki.mjs
CHANGED
|
@@ -455,7 +455,7 @@ function findIssuerCertificateInChain(certificate, chain) {
|
|
|
455
455
|
}
|
|
456
456
|
return null;
|
|
457
457
|
}
|
|
458
|
-
var configurationFileSimpleTemplate, fsWriteFile, forbiddenChars, CertificateManager;
|
|
458
|
+
var configurationFileSimpleTemplate, fsWriteFile, forbiddenChars, ChainCompletionStatus, CertificateManager;
|
|
459
459
|
var init_certificate_manager = __esm({
|
|
460
460
|
"packages/node-opcua-pki/lib/pki/certificate_manager.ts"() {
|
|
461
461
|
"use strict";
|
|
@@ -467,6 +467,14 @@ var init_certificate_manager = __esm({
|
|
|
467
467
|
configurationFileSimpleTemplate = simple_config_template_cnf_default;
|
|
468
468
|
fsWriteFile = fs4.promises.writeFile;
|
|
469
469
|
forbiddenChars = /[\x00-\x1F<>:"/\\|?*]/g;
|
|
470
|
+
ChainCompletionStatus = /* @__PURE__ */ ((ChainCompletionStatus2) => {
|
|
471
|
+
ChainCompletionStatus2["AlreadyComplete"] = "AlreadyComplete";
|
|
472
|
+
ChainCompletionStatus2["ChainCompleted"] = "ChainCompleted";
|
|
473
|
+
ChainCompletionStatus2["IssuerNotFound"] = "IssuerNotFound";
|
|
474
|
+
ChainCompletionStatus2["EmptyChain"] = "EmptyChain";
|
|
475
|
+
ChainCompletionStatus2["MaxDepthReached"] = "MaxDepthReached";
|
|
476
|
+
return ChainCompletionStatus2;
|
|
477
|
+
})(ChainCompletionStatus || {});
|
|
470
478
|
CertificateManager = class _CertificateManager extends EventEmitter {
|
|
471
479
|
// ── Global instance registry ─────────────────────────────────
|
|
472
480
|
// Tracks all initialized CertificateManager instances so their
|
|
@@ -559,6 +567,7 @@ var init_certificate_manager = __esm({
|
|
|
559
567
|
#filenameToHash = /* @__PURE__ */ new Map();
|
|
560
568
|
#initializingPromise;
|
|
561
569
|
#addCertValidation;
|
|
570
|
+
#disableFileWatchers;
|
|
562
571
|
#thumbs = {
|
|
563
572
|
rejected: /* @__PURE__ */ new Map(),
|
|
564
573
|
trusted: /* @__PURE__ */ new Map(),
|
|
@@ -592,6 +601,7 @@ var init_certificate_manager = __esm({
|
|
|
592
601
|
ignoreMissingRevocationList: v.ignoreMissingRevocationList ?? false,
|
|
593
602
|
maxChainLength: v.maxChainLength ?? 5
|
|
594
603
|
};
|
|
604
|
+
this.#disableFileWatchers = options.disableFileWatchers ?? process.env.OPCUA_PKI_DISABLE_FILE_WATCHERS === "true";
|
|
595
605
|
mkdirRecursiveSync(options.location);
|
|
596
606
|
if (!fs4.existsSync(this.#location)) {
|
|
597
607
|
throw new Error(`CertificateManager cannot access location ${this.#location}`);
|
|
@@ -1456,6 +1466,75 @@ var init_certificate_manager = __esm({
|
|
|
1456
1466
|
}
|
|
1457
1467
|
return selectedTrustedCertificates.length > 0 ? selectedTrustedCertificates[0].certificate : null;
|
|
1458
1468
|
}
|
|
1469
|
+
/**
|
|
1470
|
+
* Outcome status for {@link CertificateManager.completeCertificateChain}.
|
|
1471
|
+
*/
|
|
1472
|
+
static ChainCompletionStatus = ChainCompletionStatus;
|
|
1473
|
+
/**
|
|
1474
|
+
* Complete a certificate chain by walking the issuer store.
|
|
1475
|
+
*
|
|
1476
|
+
* Starting from the last certificate in the provided chain, this method
|
|
1477
|
+
* repeatedly calls {@link findIssuerCertificate} to locate the parent
|
|
1478
|
+
* certificate until it reaches a self-signed root or can no longer find
|
|
1479
|
+
* an issuer.
|
|
1480
|
+
*
|
|
1481
|
+
* @param chain - the (potentially partial) certificate chain, leaf first
|
|
1482
|
+
* @param maxDepth - maximum number of issuers to append (default: 10)
|
|
1483
|
+
* @returns a {@link ChainCompletionResult} containing the (possibly completed)
|
|
1484
|
+
* chain, a status code, and an optional diagnostic message.
|
|
1485
|
+
*/
|
|
1486
|
+
async completeCertificateChain(chain, maxDepth = 10) {
|
|
1487
|
+
if (chain.length === 0) {
|
|
1488
|
+
return {
|
|
1489
|
+
chain,
|
|
1490
|
+
status: "EmptyChain" /* EmptyChain */,
|
|
1491
|
+
message: "Input chain is empty \u2014 nothing to complete."
|
|
1492
|
+
};
|
|
1493
|
+
}
|
|
1494
|
+
await this.#scanCertFolder(this.issuersCertFolder, this.#thumbs.issuers.certs);
|
|
1495
|
+
const result = [...chain];
|
|
1496
|
+
let depth = 0;
|
|
1497
|
+
while (depth < maxDepth) {
|
|
1498
|
+
const lastCert = result[result.length - 1];
|
|
1499
|
+
const lastInfo = exploreCertificate(lastCert);
|
|
1500
|
+
if (isSelfSigned2(lastInfo)) {
|
|
1501
|
+
const wasExtended = result.length > chain.length;
|
|
1502
|
+
return {
|
|
1503
|
+
chain: result,
|
|
1504
|
+
status: wasExtended ? "ChainCompleted" /* ChainCompleted */ : "AlreadyComplete" /* AlreadyComplete */,
|
|
1505
|
+
message: wasExtended ? `Chain completed: ${result.length - chain.length} issuer(s) appended, ending at self-signed root "${lastInfo.tbsCertificate.subject.commonName}".` : `Chain is already complete (self-signed root "${lastInfo.tbsCertificate.subject.commonName}").`
|
|
1506
|
+
};
|
|
1507
|
+
}
|
|
1508
|
+
const issuerCert = await this.findIssuerCertificate(lastCert);
|
|
1509
|
+
if (!issuerCert) {
|
|
1510
|
+
const cn = lastInfo.tbsCertificate.subject.commonName ?? "?";
|
|
1511
|
+
const akid = lastInfo.tbsCertificate.extensions?.authorityKeyIdentifier?.keyIdentifier ?? "?";
|
|
1512
|
+
const msg = `Cannot find issuer for "${cn}" (authorityKeyIdentifier: ${akid}). Ensure the CA certificate is present in the issuers/certs folder.`;
|
|
1513
|
+
warningLog(`completeCertificateChain: ${msg}`);
|
|
1514
|
+
return {
|
|
1515
|
+
chain: result,
|
|
1516
|
+
status: "IssuerNotFound" /* IssuerNotFound */,
|
|
1517
|
+
message: msg
|
|
1518
|
+
};
|
|
1519
|
+
}
|
|
1520
|
+
const issuerFingerprint = makeFingerprint(issuerCert);
|
|
1521
|
+
const alreadyInChain = result.some((c) => makeFingerprint(c) === issuerFingerprint);
|
|
1522
|
+
if (alreadyInChain) {
|
|
1523
|
+
return {
|
|
1524
|
+
chain: result,
|
|
1525
|
+
status: "AlreadyComplete" /* AlreadyComplete */,
|
|
1526
|
+
message: `Chain ends at root "${exploreCertificate(issuerCert).tbsCertificate.subject.commonName}" (already present in chain).`
|
|
1527
|
+
};
|
|
1528
|
+
}
|
|
1529
|
+
result.push(issuerCert);
|
|
1530
|
+
depth++;
|
|
1531
|
+
}
|
|
1532
|
+
return {
|
|
1533
|
+
chain: result,
|
|
1534
|
+
status: "MaxDepthReached" /* MaxDepthReached */,
|
|
1535
|
+
message: `Chain completion stopped after ${maxDepth} iterations \u2014 possible circular chain or very deep hierarchy.`
|
|
1536
|
+
};
|
|
1537
|
+
}
|
|
1459
1538
|
/**
|
|
1460
1539
|
*
|
|
1461
1540
|
* check if the certificate explicitly appear in the trust list, the reject list or none.
|
|
@@ -1651,11 +1730,15 @@ var init_certificate_manager = __esm({
|
|
|
1651
1730
|
this.#scanCrlFolder(this.crlFolder, this.#thumbs.crl),
|
|
1652
1731
|
this.#scanCrlFolder(this.issuersCrlFolder, this.#thumbs.issuersCrl)
|
|
1653
1732
|
]);
|
|
1654
|
-
|
|
1655
|
-
|
|
1656
|
-
|
|
1657
|
-
|
|
1658
|
-
|
|
1733
|
+
if (this.#disableFileWatchers) {
|
|
1734
|
+
fs4.watch = origWatch;
|
|
1735
|
+
} else {
|
|
1736
|
+
this.#startWatcher(this.trustedFolder, this.#thumbs.trusted, createUnreffedWatcher, "trusted");
|
|
1737
|
+
this.#startWatcher(this.issuersCertFolder, this.#thumbs.issuers.certs, createUnreffedWatcher, "issuersCerts");
|
|
1738
|
+
this.#startWatcher(this.rejectedFolder, this.#thumbs.rejected, createUnreffedWatcher, "rejected");
|
|
1739
|
+
this.#startCrlWatcher(this.crlFolder, this.#thumbs.crl, createUnreffedWatcher, "crl");
|
|
1740
|
+
this.#startCrlWatcher(this.issuersCrlFolder, this.#thumbs.issuersCrl, createUnreffedWatcher, "issuersCrl");
|
|
1741
|
+
}
|
|
1659
1742
|
}
|
|
1660
1743
|
/**
|
|
1661
1744
|
* Scan a certificate folder and populate the in-memory index.
|
|
@@ -2600,15 +2683,16 @@ nsComment = "CA Generated by Node-OPCUA Certificate utility usin
|
|
|
2600
2683
|
subjectKeyIdentifier = hash
|
|
2601
2684
|
basicConstraints = CA:TRUE
|
|
2602
2685
|
keyUsage = critical, cRLSign, keyCertSign
|
|
2686
|
+
subjectAltName = $ENV::ALTNAME
|
|
2603
2687
|
nsComment = "CA CSR generated by Node-OPCUA Certificate utility using openssl"
|
|
2604
2688
|
[ v3_ca ]
|
|
2605
2689
|
subjectKeyIdentifier = hash
|
|
2606
2690
|
authorityKeyIdentifier = keyid:always,issuer:always
|
|
2607
2691
|
basicConstraints = CA:TRUE
|
|
2608
2692
|
keyUsage = critical, cRLSign, keyCertSign
|
|
2693
|
+
subjectAltName = $ENV::ALTNAME
|
|
2609
2694
|
nsComment = "CA Certificate generated by Node-OPCUA Certificate utility using openssl"
|
|
2610
2695
|
#nsCertType = sslCA, emailCA
|
|
2611
|
-
#subjectAltName = email:copy
|
|
2612
2696
|
#issuerAltName = issuer:copy
|
|
2613
2697
|
#obj = DER:02:03
|
|
2614
2698
|
crlDistributionPoints = @crl_info
|
|
@@ -2704,7 +2788,8 @@ async function construct_CertificateAuthority(certificateAuthority) {
|
|
|
2704
2788
|
await fs10.promises.writeFile(caConfigFile, data);
|
|
2705
2789
|
}
|
|
2706
2790
|
const subjectOpt = ` -subj "${subject.toString()}" `;
|
|
2707
|
-
|
|
2791
|
+
const caCommonName = subject.commonName || "NodeOPCUA-CA";
|
|
2792
|
+
setEnv("ALTNAME", `URI:urn:${caCommonName}`);
|
|
2708
2793
|
const options = { cwd: caRootDir };
|
|
2709
2794
|
const configFile = generateStaticConfig("conf/caconfig.cnf", options);
|
|
2710
2795
|
const configOption = ` -config ${q4(n5(configFile))}`;
|