node-opcua-pki 6.12.2 → 6.13.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 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
- this.#startWatcher(this.trustedFolder, this.#thumbs.trusted, createUnreffedWatcher, "trusted");
1655
- this.#startWatcher(this.issuersCertFolder, this.#thumbs.issuers.certs, createUnreffedWatcher, "issuersCerts");
1656
- this.#startWatcher(this.rejectedFolder, this.#thumbs.rejected, createUnreffedWatcher, "rejected");
1657
- this.#startCrlWatcher(this.crlFolder, this.#thumbs.crl, createUnreffedWatcher, "crl");
1658
- this.#startCrlWatcher(this.issuersCrlFolder, this.#thumbs.issuersCrl, createUnreffedWatcher, "issuersCrl");
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.