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/index.mjs CHANGED
@@ -808,15 +808,16 @@ nsComment = "CA Generated by Node-OPCUA Certificate utility usin
808
808
  subjectKeyIdentifier = hash
809
809
  basicConstraints = CA:TRUE
810
810
  keyUsage = critical, cRLSign, keyCertSign
811
+ subjectAltName = $ENV::ALTNAME
811
812
  nsComment = "CA CSR generated by Node-OPCUA Certificate utility using openssl"
812
813
  [ v3_ca ]
813
814
  subjectKeyIdentifier = hash
814
815
  authorityKeyIdentifier = keyid:always,issuer:always
815
816
  basicConstraints = CA:TRUE
816
817
  keyUsage = critical, cRLSign, keyCertSign
818
+ subjectAltName = $ENV::ALTNAME
817
819
  nsComment = "CA Certificate generated by Node-OPCUA Certificate utility using openssl"
818
820
  #nsCertType = sslCA, emailCA
819
- #subjectAltName = email:copy
820
821
  #issuerAltName = issuer:copy
821
822
  #obj = DER:02:03
822
823
  crlDistributionPoints = @crl_info
@@ -903,7 +904,8 @@ async function construct_CertificateAuthority(certificateAuthority) {
903
904
  await fs7.promises.writeFile(caConfigFile, data);
904
905
  }
905
906
  const subjectOpt = ` -subj "${subject.toString()}" `;
906
- processAltNames({});
907
+ const caCommonName = subject.commonName || "NodeOPCUA-CA";
908
+ setEnv("ALTNAME", `URI:urn:${caCommonName}`);
907
909
  const options = { cwd: caRootDir };
908
910
  const configFile = generateStaticConfig("conf/caconfig.cnf", options);
909
911
  const configOption = ` -config ${q3(n4(configFile))}`;
@@ -2022,6 +2024,14 @@ var CertificateManagerState = /* @__PURE__ */ ((CertificateManagerState2) => {
2022
2024
  CertificateManagerState2[CertificateManagerState2["Disposed"] = 4] = "Disposed";
2023
2025
  return CertificateManagerState2;
2024
2026
  })(CertificateManagerState || {});
2027
+ var ChainCompletionStatus = /* @__PURE__ */ ((ChainCompletionStatus2) => {
2028
+ ChainCompletionStatus2["AlreadyComplete"] = "AlreadyComplete";
2029
+ ChainCompletionStatus2["ChainCompleted"] = "ChainCompleted";
2030
+ ChainCompletionStatus2["IssuerNotFound"] = "IssuerNotFound";
2031
+ ChainCompletionStatus2["EmptyChain"] = "EmptyChain";
2032
+ ChainCompletionStatus2["MaxDepthReached"] = "MaxDepthReached";
2033
+ return ChainCompletionStatus2;
2034
+ })(ChainCompletionStatus || {});
2025
2035
  var CertificateManager = class _CertificateManager extends EventEmitter {
2026
2036
  // ── Global instance registry ─────────────────────────────────
2027
2037
  // Tracks all initialized CertificateManager instances so their
@@ -2114,6 +2124,7 @@ var CertificateManager = class _CertificateManager extends EventEmitter {
2114
2124
  #filenameToHash = /* @__PURE__ */ new Map();
2115
2125
  #initializingPromise;
2116
2126
  #addCertValidation;
2127
+ #disableFileWatchers;
2117
2128
  #thumbs = {
2118
2129
  rejected: /* @__PURE__ */ new Map(),
2119
2130
  trusted: /* @__PURE__ */ new Map(),
@@ -2147,6 +2158,7 @@ var CertificateManager = class _CertificateManager extends EventEmitter {
2147
2158
  ignoreMissingRevocationList: v.ignoreMissingRevocationList ?? false,
2148
2159
  maxChainLength: v.maxChainLength ?? 5
2149
2160
  };
2161
+ this.#disableFileWatchers = options.disableFileWatchers ?? process.env.OPCUA_PKI_DISABLE_FILE_WATCHERS === "true";
2150
2162
  mkdirRecursiveSync(options.location);
2151
2163
  if (!fs10.existsSync(this.#location)) {
2152
2164
  throw new Error(`CertificateManager cannot access location ${this.#location}`);
@@ -3011,6 +3023,75 @@ var CertificateManager = class _CertificateManager extends EventEmitter {
3011
3023
  }
3012
3024
  return selectedTrustedCertificates.length > 0 ? selectedTrustedCertificates[0].certificate : null;
3013
3025
  }
3026
+ /**
3027
+ * Outcome status for {@link CertificateManager.completeCertificateChain}.
3028
+ */
3029
+ static ChainCompletionStatus = ChainCompletionStatus;
3030
+ /**
3031
+ * Complete a certificate chain by walking the issuer store.
3032
+ *
3033
+ * Starting from the last certificate in the provided chain, this method
3034
+ * repeatedly calls {@link findIssuerCertificate} to locate the parent
3035
+ * certificate until it reaches a self-signed root or can no longer find
3036
+ * an issuer.
3037
+ *
3038
+ * @param chain - the (potentially partial) certificate chain, leaf first
3039
+ * @param maxDepth - maximum number of issuers to append (default: 10)
3040
+ * @returns a {@link ChainCompletionResult} containing the (possibly completed)
3041
+ * chain, a status code, and an optional diagnostic message.
3042
+ */
3043
+ async completeCertificateChain(chain, maxDepth = 10) {
3044
+ if (chain.length === 0) {
3045
+ return {
3046
+ chain,
3047
+ status: "EmptyChain" /* EmptyChain */,
3048
+ message: "Input chain is empty \u2014 nothing to complete."
3049
+ };
3050
+ }
3051
+ await this.#scanCertFolder(this.issuersCertFolder, this.#thumbs.issuers.certs);
3052
+ const result = [...chain];
3053
+ let depth = 0;
3054
+ while (depth < maxDepth) {
3055
+ const lastCert = result[result.length - 1];
3056
+ const lastInfo = exploreCertificate2(lastCert);
3057
+ if (isSelfSigned2(lastInfo)) {
3058
+ const wasExtended = result.length > chain.length;
3059
+ return {
3060
+ chain: result,
3061
+ status: wasExtended ? "ChainCompleted" /* ChainCompleted */ : "AlreadyComplete" /* AlreadyComplete */,
3062
+ 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}").`
3063
+ };
3064
+ }
3065
+ const issuerCert = await this.findIssuerCertificate(lastCert);
3066
+ if (!issuerCert) {
3067
+ const cn = lastInfo.tbsCertificate.subject.commonName ?? "?";
3068
+ const akid = lastInfo.tbsCertificate.extensions?.authorityKeyIdentifier?.keyIdentifier ?? "?";
3069
+ const msg = `Cannot find issuer for "${cn}" (authorityKeyIdentifier: ${akid}). Ensure the CA certificate is present in the issuers/certs folder.`;
3070
+ warningLog(`completeCertificateChain: ${msg}`);
3071
+ return {
3072
+ chain: result,
3073
+ status: "IssuerNotFound" /* IssuerNotFound */,
3074
+ message: msg
3075
+ };
3076
+ }
3077
+ const issuerFingerprint = makeFingerprint(issuerCert);
3078
+ const alreadyInChain = result.some((c) => makeFingerprint(c) === issuerFingerprint);
3079
+ if (alreadyInChain) {
3080
+ return {
3081
+ chain: result,
3082
+ status: "AlreadyComplete" /* AlreadyComplete */,
3083
+ message: `Chain ends at root "${exploreCertificate2(issuerCert).tbsCertificate.subject.commonName}" (already present in chain).`
3084
+ };
3085
+ }
3086
+ result.push(issuerCert);
3087
+ depth++;
3088
+ }
3089
+ return {
3090
+ chain: result,
3091
+ status: "MaxDepthReached" /* MaxDepthReached */,
3092
+ message: `Chain completion stopped after ${maxDepth} iterations \u2014 possible circular chain or very deep hierarchy.`
3093
+ };
3094
+ }
3014
3095
  /**
3015
3096
  *
3016
3097
  * check if the certificate explicitly appear in the trust list, the reject list or none.
@@ -3206,11 +3287,15 @@ var CertificateManager = class _CertificateManager extends EventEmitter {
3206
3287
  this.#scanCrlFolder(this.crlFolder, this.#thumbs.crl),
3207
3288
  this.#scanCrlFolder(this.issuersCrlFolder, this.#thumbs.issuersCrl)
3208
3289
  ]);
3209
- this.#startWatcher(this.trustedFolder, this.#thumbs.trusted, createUnreffedWatcher, "trusted");
3210
- this.#startWatcher(this.issuersCertFolder, this.#thumbs.issuers.certs, createUnreffedWatcher, "issuersCerts");
3211
- this.#startWatcher(this.rejectedFolder, this.#thumbs.rejected, createUnreffedWatcher, "rejected");
3212
- this.#startCrlWatcher(this.crlFolder, this.#thumbs.crl, createUnreffedWatcher, "crl");
3213
- this.#startCrlWatcher(this.issuersCrlFolder, this.#thumbs.issuersCrl, createUnreffedWatcher, "issuersCrl");
3290
+ if (this.#disableFileWatchers) {
3291
+ fs10.watch = origWatch;
3292
+ } else {
3293
+ this.#startWatcher(this.trustedFolder, this.#thumbs.trusted, createUnreffedWatcher, "trusted");
3294
+ this.#startWatcher(this.issuersCertFolder, this.#thumbs.issuers.certs, createUnreffedWatcher, "issuersCerts");
3295
+ this.#startWatcher(this.rejectedFolder, this.#thumbs.rejected, createUnreffedWatcher, "rejected");
3296
+ this.#startCrlWatcher(this.crlFolder, this.#thumbs.crl, createUnreffedWatcher, "crl");
3297
+ this.#startCrlWatcher(this.issuersCrlFolder, this.#thumbs.issuersCrl, createUnreffedWatcher, "issuersCrl");
3298
+ }
3214
3299
  }
3215
3300
  /**
3216
3301
  * Scan a certificate folder and populate the in-memory index.
@@ -3391,6 +3476,7 @@ export {
3391
3476
  CertificateAuthority,
3392
3477
  CertificateManager,
3393
3478
  CertificateManagerState,
3479
+ ChainCompletionStatus,
3394
3480
  Subject,
3395
3481
  VerificationStatus,
3396
3482
  adjustApplicationUri,