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/index.d.mts CHANGED
@@ -671,6 +671,20 @@ interface CertificateManagerOptions {
671
671
  * Defaults are secure — all checks enabled.
672
672
  */
673
673
  addCertificateValidationOptions?: AddCertificateValidationOptions;
674
+ /**
675
+ * When `true`, the CertificateManager will **not** start
676
+ * chokidar file-system watchers on the PKI folders.
677
+ *
678
+ * The initial file-system scan still runs so the in-memory
679
+ * indexes are populated, but live change detection is
680
+ * disabled. This is useful in test / CI environments where
681
+ * many CertificateManager instances are created in parallel
682
+ * and the accumulated `fs.watch` handles exhaust the libuv
683
+ * thread-pool, causing event-loop starvation.
684
+ *
685
+ * @defaultValue false
686
+ */
687
+ disableFileWatchers?: boolean;
674
688
  }
675
689
  /**
676
690
  * Parameters for {@link createSelfSignedCertificate}.
@@ -857,6 +871,33 @@ declare enum CertificateManagerState {
857
871
  * await cm.dispose();
858
872
  * ```
859
873
  */
874
+ /**
875
+ * Status codes returned by {@link CertificateManager.completeCertificateChain}.
876
+ */
877
+ declare enum ChainCompletionStatus {
878
+ /** The chain already reached a self-signed root — no action was needed. */
879
+ AlreadyComplete = "AlreadyComplete",
880
+ /** One or more issuer certificates were successfully appended. */
881
+ ChainCompleted = "ChainCompleted",
882
+ /** The issuer for the last certificate in the chain could not be found
883
+ * in the issuers or trusted stores. The chain is still partial. */
884
+ IssuerNotFound = "IssuerNotFound",
885
+ /** The input chain was empty. */
886
+ EmptyChain = "EmptyChain",
887
+ /** Chain completion was stopped because the maximum depth was reached. */
888
+ MaxDepthReached = "MaxDepthReached"
889
+ }
890
+ /**
891
+ * Result of {@link CertificateManager.completeCertificateChain}.
892
+ */
893
+ interface ChainCompletionResult {
894
+ /** The (possibly completed) certificate chain, leaf first. */
895
+ chain: Certificate[];
896
+ /** Status code indicating whether completion succeeded and why/why not. */
897
+ status: ChainCompletionStatus;
898
+ /** Human-readable diagnostic message. */
899
+ message: string;
900
+ }
860
901
  declare class CertificateManager extends EventEmitter {
861
902
  #private;
862
903
  /**
@@ -1150,6 +1191,24 @@ declare class CertificateManager extends EventEmitter {
1150
1191
  *
1151
1192
  */
1152
1193
  findIssuerCertificate(certificate: Certificate | Certificate[]): Promise<Certificate | null>;
1194
+ /**
1195
+ * Outcome status for {@link CertificateManager.completeCertificateChain}.
1196
+ */
1197
+ static readonly ChainCompletionStatus: typeof ChainCompletionStatus;
1198
+ /**
1199
+ * Complete a certificate chain by walking the issuer store.
1200
+ *
1201
+ * Starting from the last certificate in the provided chain, this method
1202
+ * repeatedly calls {@link findIssuerCertificate} to locate the parent
1203
+ * certificate until it reaches a self-signed root or can no longer find
1204
+ * an issuer.
1205
+ *
1206
+ * @param chain - the (potentially partial) certificate chain, leaf first
1207
+ * @param maxDepth - maximum number of issuers to append (default: 10)
1208
+ * @returns a {@link ChainCompletionResult} containing the (possibly completed)
1209
+ * chain, a status code, and an optional diagnostic message.
1210
+ */
1211
+ completeCertificateChain(chain: Certificate[], maxDepth?: number): Promise<ChainCompletionResult>;
1153
1212
  /**
1154
1213
  * Check whether a certificate has been revoked by its issuer's CRL.
1155
1214
  *
@@ -1305,4 +1364,4 @@ declare function dumpPFX(pfxFile: Filename, passphrase?: string): Promise<string
1305
1364
  */
1306
1365
  declare function install_prerequisite(): Promise<string>;
1307
1366
 
1308
- export { type AddCertificateValidationOptions, CertificateAuthority, type CertificateAuthorityOptions, CertificateManager, type CertificateManagerEvents, type CertificateManagerOptions, CertificateManagerState, type CertificateStatus, type CertificateStore, type CreateCertificateSigningRequestOptions, type CreateCertificateSigningRequestWithConfigOptions, type CreatePFXOptions, type CreateSelfSignCertificateParam, type CreateSelfSignCertificateParam1, type CreateSelfSignCertificateWithConfigParam, type CrlStore, type ExtractPFXOptions, type ExtractPFXResult, type Filename, type GenerateKeyPairAndSignOptions, type GenerateKeyPairAndSignPFXOptions, type InitializeCSRResult, type InstallCACertificateResult, type KeyLength, type KeySize, type Params, type ProcessAltNamesParam, type SignCertificateOptions, type StartDateEndDateParam, type Thumbprint, VerificationStatus, type VerifyCertificateOptions, adjustApplicationUri, adjustDate, coerceCertificateChain, convertPFXtoPEM, createPFX, dumpPFX, extractAllFromPFX, extractCACertificatesFromPFX, extractCertificateFromPFX, extractPrivateKeyFromPFX, findIssuerCertificateInChain, install_prerequisite, isIntermediateIssuer, isIssuer, isRootIssuer, makeFingerprint, quote };
1367
+ export { type AddCertificateValidationOptions, CertificateAuthority, type CertificateAuthorityOptions, CertificateManager, type CertificateManagerEvents, type CertificateManagerOptions, CertificateManagerState, type CertificateStatus, type CertificateStore, type ChainCompletionResult, ChainCompletionStatus, type CreateCertificateSigningRequestOptions, type CreateCertificateSigningRequestWithConfigOptions, type CreatePFXOptions, type CreateSelfSignCertificateParam, type CreateSelfSignCertificateParam1, type CreateSelfSignCertificateWithConfigParam, type CrlStore, type ExtractPFXOptions, type ExtractPFXResult, type Filename, type GenerateKeyPairAndSignOptions, type GenerateKeyPairAndSignPFXOptions, type InitializeCSRResult, type InstallCACertificateResult, type KeyLength, type KeySize, type Params, type ProcessAltNamesParam, type SignCertificateOptions, type StartDateEndDateParam, type Thumbprint, VerificationStatus, type VerifyCertificateOptions, adjustApplicationUri, adjustDate, coerceCertificateChain, convertPFXtoPEM, createPFX, dumpPFX, extractAllFromPFX, extractCACertificatesFromPFX, extractCertificateFromPFX, extractPrivateKeyFromPFX, findIssuerCertificateInChain, install_prerequisite, isIntermediateIssuer, isIssuer, isRootIssuer, makeFingerprint, quote };
package/dist/index.d.ts CHANGED
@@ -671,6 +671,20 @@ interface CertificateManagerOptions {
671
671
  * Defaults are secure — all checks enabled.
672
672
  */
673
673
  addCertificateValidationOptions?: AddCertificateValidationOptions;
674
+ /**
675
+ * When `true`, the CertificateManager will **not** start
676
+ * chokidar file-system watchers on the PKI folders.
677
+ *
678
+ * The initial file-system scan still runs so the in-memory
679
+ * indexes are populated, but live change detection is
680
+ * disabled. This is useful in test / CI environments where
681
+ * many CertificateManager instances are created in parallel
682
+ * and the accumulated `fs.watch` handles exhaust the libuv
683
+ * thread-pool, causing event-loop starvation.
684
+ *
685
+ * @defaultValue false
686
+ */
687
+ disableFileWatchers?: boolean;
674
688
  }
675
689
  /**
676
690
  * Parameters for {@link createSelfSignedCertificate}.
@@ -857,6 +871,33 @@ declare enum CertificateManagerState {
857
871
  * await cm.dispose();
858
872
  * ```
859
873
  */
874
+ /**
875
+ * Status codes returned by {@link CertificateManager.completeCertificateChain}.
876
+ */
877
+ declare enum ChainCompletionStatus {
878
+ /** The chain already reached a self-signed root — no action was needed. */
879
+ AlreadyComplete = "AlreadyComplete",
880
+ /** One or more issuer certificates were successfully appended. */
881
+ ChainCompleted = "ChainCompleted",
882
+ /** The issuer for the last certificate in the chain could not be found
883
+ * in the issuers or trusted stores. The chain is still partial. */
884
+ IssuerNotFound = "IssuerNotFound",
885
+ /** The input chain was empty. */
886
+ EmptyChain = "EmptyChain",
887
+ /** Chain completion was stopped because the maximum depth was reached. */
888
+ MaxDepthReached = "MaxDepthReached"
889
+ }
890
+ /**
891
+ * Result of {@link CertificateManager.completeCertificateChain}.
892
+ */
893
+ interface ChainCompletionResult {
894
+ /** The (possibly completed) certificate chain, leaf first. */
895
+ chain: Certificate[];
896
+ /** Status code indicating whether completion succeeded and why/why not. */
897
+ status: ChainCompletionStatus;
898
+ /** Human-readable diagnostic message. */
899
+ message: string;
900
+ }
860
901
  declare class CertificateManager extends EventEmitter {
861
902
  #private;
862
903
  /**
@@ -1150,6 +1191,24 @@ declare class CertificateManager extends EventEmitter {
1150
1191
  *
1151
1192
  */
1152
1193
  findIssuerCertificate(certificate: Certificate | Certificate[]): Promise<Certificate | null>;
1194
+ /**
1195
+ * Outcome status for {@link CertificateManager.completeCertificateChain}.
1196
+ */
1197
+ static readonly ChainCompletionStatus: typeof ChainCompletionStatus;
1198
+ /**
1199
+ * Complete a certificate chain by walking the issuer store.
1200
+ *
1201
+ * Starting from the last certificate in the provided chain, this method
1202
+ * repeatedly calls {@link findIssuerCertificate} to locate the parent
1203
+ * certificate until it reaches a self-signed root or can no longer find
1204
+ * an issuer.
1205
+ *
1206
+ * @param chain - the (potentially partial) certificate chain, leaf first
1207
+ * @param maxDepth - maximum number of issuers to append (default: 10)
1208
+ * @returns a {@link ChainCompletionResult} containing the (possibly completed)
1209
+ * chain, a status code, and an optional diagnostic message.
1210
+ */
1211
+ completeCertificateChain(chain: Certificate[], maxDepth?: number): Promise<ChainCompletionResult>;
1153
1212
  /**
1154
1213
  * Check whether a certificate has been revoked by its issuer's CRL.
1155
1214
  *
@@ -1305,4 +1364,4 @@ declare function dumpPFX(pfxFile: Filename, passphrase?: string): Promise<string
1305
1364
  */
1306
1365
  declare function install_prerequisite(): Promise<string>;
1307
1366
 
1308
- export { type AddCertificateValidationOptions, CertificateAuthority, type CertificateAuthorityOptions, CertificateManager, type CertificateManagerEvents, type CertificateManagerOptions, CertificateManagerState, type CertificateStatus, type CertificateStore, type CreateCertificateSigningRequestOptions, type CreateCertificateSigningRequestWithConfigOptions, type CreatePFXOptions, type CreateSelfSignCertificateParam, type CreateSelfSignCertificateParam1, type CreateSelfSignCertificateWithConfigParam, type CrlStore, type ExtractPFXOptions, type ExtractPFXResult, type Filename, type GenerateKeyPairAndSignOptions, type GenerateKeyPairAndSignPFXOptions, type InitializeCSRResult, type InstallCACertificateResult, type KeyLength, type KeySize, type Params, type ProcessAltNamesParam, type SignCertificateOptions, type StartDateEndDateParam, type Thumbprint, VerificationStatus, type VerifyCertificateOptions, adjustApplicationUri, adjustDate, coerceCertificateChain, convertPFXtoPEM, createPFX, dumpPFX, extractAllFromPFX, extractCACertificatesFromPFX, extractCertificateFromPFX, extractPrivateKeyFromPFX, findIssuerCertificateInChain, install_prerequisite, isIntermediateIssuer, isIssuer, isRootIssuer, makeFingerprint, quote };
1367
+ export { type AddCertificateValidationOptions, CertificateAuthority, type CertificateAuthorityOptions, CertificateManager, type CertificateManagerEvents, type CertificateManagerOptions, CertificateManagerState, type CertificateStatus, type CertificateStore, type ChainCompletionResult, ChainCompletionStatus, type CreateCertificateSigningRequestOptions, type CreateCertificateSigningRequestWithConfigOptions, type CreatePFXOptions, type CreateSelfSignCertificateParam, type CreateSelfSignCertificateParam1, type CreateSelfSignCertificateWithConfigParam, type CrlStore, type ExtractPFXOptions, type ExtractPFXResult, type Filename, type GenerateKeyPairAndSignOptions, type GenerateKeyPairAndSignPFXOptions, type InitializeCSRResult, type InstallCACertificateResult, type KeyLength, type KeySize, type Params, type ProcessAltNamesParam, type SignCertificateOptions, type StartDateEndDateParam, type Thumbprint, VerificationStatus, type VerifyCertificateOptions, adjustApplicationUri, adjustDate, coerceCertificateChain, convertPFXtoPEM, createPFX, dumpPFX, extractAllFromPFX, extractCACertificatesFromPFX, extractCertificateFromPFX, extractPrivateKeyFromPFX, findIssuerCertificateInChain, install_prerequisite, isIntermediateIssuer, isIssuer, isRootIssuer, makeFingerprint, quote };
package/dist/index.js CHANGED
@@ -33,6 +33,7 @@ __export(lib_exports, {
33
33
  CertificateAuthority: () => CertificateAuthority,
34
34
  CertificateManager: () => CertificateManager,
35
35
  CertificateManagerState: () => CertificateManagerState,
36
+ ChainCompletionStatus: () => ChainCompletionStatus,
36
37
  Subject: () => import_node_opcua_crypto.Subject,
37
38
  VerificationStatus: () => VerificationStatus,
38
39
  adjustApplicationUri: () => adjustApplicationUri,
@@ -2050,6 +2051,14 @@ var CertificateManagerState = /* @__PURE__ */ ((CertificateManagerState2) => {
2050
2051
  CertificateManagerState2[CertificateManagerState2["Disposed"] = 4] = "Disposed";
2051
2052
  return CertificateManagerState2;
2052
2053
  })(CertificateManagerState || {});
2054
+ var ChainCompletionStatus = /* @__PURE__ */ ((ChainCompletionStatus2) => {
2055
+ ChainCompletionStatus2["AlreadyComplete"] = "AlreadyComplete";
2056
+ ChainCompletionStatus2["ChainCompleted"] = "ChainCompleted";
2057
+ ChainCompletionStatus2["IssuerNotFound"] = "IssuerNotFound";
2058
+ ChainCompletionStatus2["EmptyChain"] = "EmptyChain";
2059
+ ChainCompletionStatus2["MaxDepthReached"] = "MaxDepthReached";
2060
+ return ChainCompletionStatus2;
2061
+ })(ChainCompletionStatus || {});
2053
2062
  var CertificateManager = class _CertificateManager extends import_node_events.EventEmitter {
2054
2063
  // ── Global instance registry ─────────────────────────────────
2055
2064
  // Tracks all initialized CertificateManager instances so their
@@ -2142,6 +2151,7 @@ var CertificateManager = class _CertificateManager extends import_node_events.Ev
2142
2151
  #filenameToHash = /* @__PURE__ */ new Map();
2143
2152
  #initializingPromise;
2144
2153
  #addCertValidation;
2154
+ #disableFileWatchers;
2145
2155
  #thumbs = {
2146
2156
  rejected: /* @__PURE__ */ new Map(),
2147
2157
  trusted: /* @__PURE__ */ new Map(),
@@ -2175,6 +2185,7 @@ var CertificateManager = class _CertificateManager extends import_node_events.Ev
2175
2185
  ignoreMissingRevocationList: v.ignoreMissingRevocationList ?? false,
2176
2186
  maxChainLength: v.maxChainLength ?? 5
2177
2187
  };
2188
+ this.#disableFileWatchers = options.disableFileWatchers ?? process.env.OPCUA_PKI_DISABLE_FILE_WATCHERS === "true";
2178
2189
  mkdirRecursiveSync(options.location);
2179
2190
  if (!import_node_fs10.default.existsSync(this.#location)) {
2180
2191
  throw new Error(`CertificateManager cannot access location ${this.#location}`);
@@ -3039,6 +3050,75 @@ var CertificateManager = class _CertificateManager extends import_node_events.Ev
3039
3050
  }
3040
3051
  return selectedTrustedCertificates.length > 0 ? selectedTrustedCertificates[0].certificate : null;
3041
3052
  }
3053
+ /**
3054
+ * Outcome status for {@link CertificateManager.completeCertificateChain}.
3055
+ */
3056
+ static ChainCompletionStatus = ChainCompletionStatus;
3057
+ /**
3058
+ * Complete a certificate chain by walking the issuer store.
3059
+ *
3060
+ * Starting from the last certificate in the provided chain, this method
3061
+ * repeatedly calls {@link findIssuerCertificate} to locate the parent
3062
+ * certificate until it reaches a self-signed root or can no longer find
3063
+ * an issuer.
3064
+ *
3065
+ * @param chain - the (potentially partial) certificate chain, leaf first
3066
+ * @param maxDepth - maximum number of issuers to append (default: 10)
3067
+ * @returns a {@link ChainCompletionResult} containing the (possibly completed)
3068
+ * chain, a status code, and an optional diagnostic message.
3069
+ */
3070
+ async completeCertificateChain(chain, maxDepth = 10) {
3071
+ if (chain.length === 0) {
3072
+ return {
3073
+ chain,
3074
+ status: "EmptyChain" /* EmptyChain */,
3075
+ message: "Input chain is empty \u2014 nothing to complete."
3076
+ };
3077
+ }
3078
+ await this.#scanCertFolder(this.issuersCertFolder, this.#thumbs.issuers.certs);
3079
+ const result = [...chain];
3080
+ let depth = 0;
3081
+ while (depth < maxDepth) {
3082
+ const lastCert = result[result.length - 1];
3083
+ const lastInfo = (0, import_node_opcua_crypto5.exploreCertificate)(lastCert);
3084
+ if (isSelfSigned2(lastInfo)) {
3085
+ const wasExtended = result.length > chain.length;
3086
+ return {
3087
+ chain: result,
3088
+ status: wasExtended ? "ChainCompleted" /* ChainCompleted */ : "AlreadyComplete" /* AlreadyComplete */,
3089
+ 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}").`
3090
+ };
3091
+ }
3092
+ const issuerCert = await this.findIssuerCertificate(lastCert);
3093
+ if (!issuerCert) {
3094
+ const cn = lastInfo.tbsCertificate.subject.commonName ?? "?";
3095
+ const akid = lastInfo.tbsCertificate.extensions?.authorityKeyIdentifier?.keyIdentifier ?? "?";
3096
+ const msg = `Cannot find issuer for "${cn}" (authorityKeyIdentifier: ${akid}). Ensure the CA certificate is present in the issuers/certs folder.`;
3097
+ warningLog(`completeCertificateChain: ${msg}`);
3098
+ return {
3099
+ chain: result,
3100
+ status: "IssuerNotFound" /* IssuerNotFound */,
3101
+ message: msg
3102
+ };
3103
+ }
3104
+ const issuerFingerprint = makeFingerprint(issuerCert);
3105
+ const alreadyInChain = result.some((c) => makeFingerprint(c) === issuerFingerprint);
3106
+ if (alreadyInChain) {
3107
+ return {
3108
+ chain: result,
3109
+ status: "AlreadyComplete" /* AlreadyComplete */,
3110
+ message: `Chain ends at root "${(0, import_node_opcua_crypto5.exploreCertificate)(issuerCert).tbsCertificate.subject.commonName}" (already present in chain).`
3111
+ };
3112
+ }
3113
+ result.push(issuerCert);
3114
+ depth++;
3115
+ }
3116
+ return {
3117
+ chain: result,
3118
+ status: "MaxDepthReached" /* MaxDepthReached */,
3119
+ message: `Chain completion stopped after ${maxDepth} iterations \u2014 possible circular chain or very deep hierarchy.`
3120
+ };
3121
+ }
3042
3122
  /**
3043
3123
  *
3044
3124
  * check if the certificate explicitly appear in the trust list, the reject list or none.
@@ -3234,11 +3314,15 @@ var CertificateManager = class _CertificateManager extends import_node_events.Ev
3234
3314
  this.#scanCrlFolder(this.crlFolder, this.#thumbs.crl),
3235
3315
  this.#scanCrlFolder(this.issuersCrlFolder, this.#thumbs.issuersCrl)
3236
3316
  ]);
3237
- this.#startWatcher(this.trustedFolder, this.#thumbs.trusted, createUnreffedWatcher, "trusted");
3238
- this.#startWatcher(this.issuersCertFolder, this.#thumbs.issuers.certs, createUnreffedWatcher, "issuersCerts");
3239
- this.#startWatcher(this.rejectedFolder, this.#thumbs.rejected, createUnreffedWatcher, "rejected");
3240
- this.#startCrlWatcher(this.crlFolder, this.#thumbs.crl, createUnreffedWatcher, "crl");
3241
- this.#startCrlWatcher(this.issuersCrlFolder, this.#thumbs.issuersCrl, createUnreffedWatcher, "issuersCrl");
3317
+ if (this.#disableFileWatchers) {
3318
+ import_node_fs10.default.watch = origWatch;
3319
+ } else {
3320
+ this.#startWatcher(this.trustedFolder, this.#thumbs.trusted, createUnreffedWatcher, "trusted");
3321
+ this.#startWatcher(this.issuersCertFolder, this.#thumbs.issuers.certs, createUnreffedWatcher, "issuersCerts");
3322
+ this.#startWatcher(this.rejectedFolder, this.#thumbs.rejected, createUnreffedWatcher, "rejected");
3323
+ this.#startCrlWatcher(this.crlFolder, this.#thumbs.crl, createUnreffedWatcher, "crl");
3324
+ this.#startCrlWatcher(this.issuersCrlFolder, this.#thumbs.issuersCrl, createUnreffedWatcher, "issuersCrl");
3325
+ }
3242
3326
  }
3243
3327
  /**
3244
3328
  * Scan a certificate folder and populate the in-memory index.
@@ -3420,6 +3504,7 @@ var CertificateManager = class _CertificateManager extends import_node_events.Ev
3420
3504
  CertificateAuthority,
3421
3505
  CertificateManager,
3422
3506
  CertificateManagerState,
3507
+ ChainCompletionStatus,
3423
3508
  Subject,
3424
3509
  VerificationStatus,
3425
3510
  adjustApplicationUri,