node-opcua-common 2.165.0 → 2.168.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.
@@ -1 +1 @@
1
- {"version":3,"file":"applicationurn.js","sourceRoot":"","sources":["../source/applicationurn.ts"],"names":[],"mappings":";;AAOA,gDAgBC;AAvBD;;GAEG;AACH,mCAAkC;AAElC,yDAA2C;AAE3C,SAAgB,kBAAkB,CAAC,QAAgB,EAAE,MAAc;IAE/D,IAAA,0BAAM,EAAC,CAAC,MAAM,CAAC,KAAK,CAAC,MAAM,CAAC,EAAE,6BAA6B,CAAC,CAAC;IAC7D,sEAAsE;IACtE,2DAA2D;IAC3D,wEAAwE;IACxE,eAAe;IACf,IAAI,YAAY,GAAG,QAAQ,CAAC;IAC5B,IAAI,YAAY,CAAC,MAAM,GAAG,CAAC,GAAG,MAAM,CAAC,MAAM,IAAI,EAAE,EAAE,CAAC;QAChD,0DAA0D;QAC1D,kCAAkC;QAClC,YAAY,GAAG,IAAA,mBAAU,EAAC,KAAK,CAAC,CAAC,MAAM,CAAC,QAAQ,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,SAAS,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC;IACrF,CAAC;IACD,MAAM,cAAc,GAAG,MAAM,GAAG,YAAY,GAAG,GAAG,GAAG,MAAM,CAAC;IAC5D,IAAA,0BAAM,EAAC,cAAc,CAAC,MAAM,IAAI,EAAE,CAAC,CAAC;IACpC,OAAO,cAAc,CAAC;AAC1B,CAAC"}
1
+ {"version":3,"file":"applicationurn.js","sourceRoot":"","sources":["../source/applicationurn.ts"],"names":[],"mappings":";;AAOA,gDAeC;AAtBD;;GAEG;AACH,mCAAoC;AAEpC,yDAA2C;AAE3C,SAAgB,kBAAkB,CAAC,QAAgB,EAAE,MAAc;IAC/D,IAAA,0BAAM,EAAC,CAAC,MAAM,CAAC,KAAK,CAAC,MAAM,CAAC,EAAE,6BAA6B,CAAC,CAAC;IAC7D,sEAAsE;IACtE,2DAA2D;IAC3D,wEAAwE;IACxE,eAAe;IACf,IAAI,YAAY,GAAG,QAAQ,CAAC;IAC5B,IAAI,YAAY,CAAC,MAAM,GAAG,CAAC,GAAG,MAAM,CAAC,MAAM,IAAI,EAAE,EAAE,CAAC;QAChD,0DAA0D;QAC1D,kCAAkC;QAClC,YAAY,GAAG,IAAA,mBAAU,EAAC,KAAK,CAAC,CAAC,MAAM,CAAC,QAAQ,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,SAAS,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC;IACrF,CAAC;IACD,MAAM,cAAc,GAAG,MAAM,GAAG,YAAY,GAAG,GAAG,GAAG,MAAM,CAAC;IAC5D,IAAA,0BAAM,EAAC,cAAc,CAAC,MAAM,IAAI,EAAE,CAAC,CAAC;IACpC,OAAO,cAAc,CAAC;AAC1B,CAAC"}
@@ -0,0 +1,52 @@
1
+ /**
2
+ * Pluggable certificate chain provider for OPC UA endpoints.
3
+ *
4
+ * Abstracts how an endpoint obtains its certificate chain and private key,
5
+ * allowing both static (in-memory) and dynamic (disk-based) strategies
6
+ * without monkey-patching.
7
+ *
8
+ * @module node-opcua-common
9
+ */
10
+ import type { Certificate, PrivateKey } from "node-opcua-crypto/web";
11
+ import type { ICertificateKeyPairProvider } from "./opcua_secure_object";
12
+ /**
13
+ * Provides a certificate chain and private key to an OPC UA endpoint.
14
+ *
15
+ * Implementations may read from memory, disk, or any other source.
16
+ * See also {@link SecretHolder} which implements this interface for
17
+ * disk-based access with lazy caching.
18
+ */
19
+ export interface ICertificateChainProvider extends ICertificateKeyPairProvider {
20
+ /**
21
+ * Invalidate any cached values so the next access re-reads
22
+ * from the underlying source. No-op for static providers.
23
+ */
24
+ invalidate(): void;
25
+ }
26
+ /**
27
+ * Holds a certificate chain and private key in memory.
28
+ *
29
+ * Used as the default provider when push certificate management
30
+ * is NOT installed. The chain can be replaced in-place via `update()`.
31
+ */
32
+ export declare class StaticCertificateChainProvider implements ICertificateChainProvider {
33
+ #private;
34
+ constructor(chain: Certificate[], key: PrivateKey);
35
+ getCertificate(): Certificate;
36
+ getCertificateChain(): Certificate[];
37
+ getPrivateKey(): PrivateKey;
38
+ /**
39
+ * No-op for static provider — the chain is already in memory.
40
+ * Use `update()` to replace the chain explicitly.
41
+ */
42
+ invalidate(): void;
43
+ /**
44
+ * Replace the certificate chain and optionally the private key.
45
+ *
46
+ * This immediately affects all consumers that call
47
+ * `getCertificateChain()` on this provider (including
48
+ * endpoint descriptions with dynamic `serverCertificate` getters).
49
+ */
50
+ update(chain: Certificate[], key?: PrivateKey): void;
51
+ toJSON(): Record<string, string>;
52
+ }
@@ -0,0 +1,59 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.StaticCertificateChainProvider = void 0;
4
+ /**
5
+ * Holds a certificate chain and private key in memory.
6
+ *
7
+ * Used as the default provider when push certificate management
8
+ * is NOT installed. The chain can be replaced in-place via `update()`.
9
+ */
10
+ class StaticCertificateChainProvider {
11
+ #chain;
12
+ #key;
13
+ constructor(chain, key) {
14
+ this.#chain = chain;
15
+ this.#key = key;
16
+ }
17
+ getCertificate() {
18
+ return this.#chain[0];
19
+ }
20
+ getCertificateChain() {
21
+ return this.#chain;
22
+ }
23
+ getPrivateKey() {
24
+ return this.#key;
25
+ }
26
+ /**
27
+ * No-op for static provider — the chain is already in memory.
28
+ * Use `update()` to replace the chain explicitly.
29
+ */
30
+ invalidate() {
31
+ // nothing to invalidate for a static provider
32
+ }
33
+ /**
34
+ * Replace the certificate chain and optionally the private key.
35
+ *
36
+ * This immediately affects all consumers that call
37
+ * `getCertificateChain()` on this provider (including
38
+ * endpoint descriptions with dynamic `serverCertificate` getters).
39
+ */
40
+ update(chain, key) {
41
+ if (chain.length === 0) {
42
+ throw new Error("StaticCertificateChainProvider.update: chain must not be empty");
43
+ }
44
+ this.#chain = chain;
45
+ if (key !== undefined) {
46
+ this.#key = key;
47
+ }
48
+ }
49
+ // Prevent secrets from leaking through JSON serialization
50
+ toJSON() {
51
+ return { provider: "StaticCertificateChainProvider" };
52
+ }
53
+ // Prevent secrets from leaking through console.log / util.inspect
54
+ [Symbol.for("nodejs.util.inspect.custom")]() {
55
+ return "StaticCertificateChainProvider { <in-memory> }";
56
+ }
57
+ }
58
+ exports.StaticCertificateChainProvider = StaticCertificateChainProvider;
59
+ //# sourceMappingURL=certificate_chain_provider.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"certificate_chain_provider.js","sourceRoot":"","sources":["../source/certificate_chain_provider.ts"],"names":[],"mappings":";;;AA4BA;;;;;GAKG;AACH,MAAa,8BAA8B;IACvC,MAAM,CAAgB;IACtB,IAAI,CAAa;IAEjB,YAAY,KAAoB,EAAE,GAAe;QAC7C,IAAI,CAAC,MAAM,GAAG,KAAK,CAAC;QACpB,IAAI,CAAC,IAAI,GAAG,GAAG,CAAC;IACpB,CAAC;IAEM,cAAc;QACjB,OAAO,IAAI,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC;IAC1B,CAAC;IAEM,mBAAmB;QACtB,OAAO,IAAI,CAAC,MAAM,CAAC;IACvB,CAAC;IAEM,aAAa;QAChB,OAAO,IAAI,CAAC,IAAI,CAAC;IACrB,CAAC;IAED;;;OAGG;IACI,UAAU;QACb,8CAA8C;IAClD,CAAC;IAED;;;;;;OAMG;IACI,MAAM,CAAC,KAAoB,EAAE,GAAgB;QAChD,IAAI,KAAK,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;YACrB,MAAM,IAAI,KAAK,CAAC,gEAAgE,CAAC,CAAC;QACtF,CAAC;QACD,IAAI,CAAC,MAAM,GAAG,KAAK,CAAC;QACpB,IAAI,GAAG,KAAK,SAAS,EAAE,CAAC;YACpB,IAAI,CAAC,IAAI,GAAG,GAAG,CAAC;QACpB,CAAC;IACL,CAAC;IAED,0DAA0D;IACnD,MAAM;QACT,OAAO,EAAE,QAAQ,EAAE,gCAAgC,EAAE,CAAC;IAC1D,CAAC;IAED,kEAAkE;IAC3D,CAAC,MAAM,CAAC,GAAG,CAAC,4BAA4B,CAAC,CAAC;QAC7C,OAAO,gDAAgD,CAAC;IAC5D,CAAC;CACJ;AAvDD,wEAuDC"}
package/dist/index.d.ts CHANGED
@@ -23,12 +23,13 @@
23
23
  /**
24
24
  * @module node-opcua-common
25
25
  */
26
- export { ServerState, ServerStatusDataType, // ServerStatus
26
+ export { BuildInfo, DataTypeDefinition, EnumValueType, ModelChangeStructureDataType, // ModelChangeStructure
27
27
  RedundantServerDataType, // RedundantServer
28
- ModelChangeStructureDataType, // ModelChangeStructure
29
- SubscriptionDiagnosticsDataType, // SubscriptionDiagnostics
30
28
  SamplingIntervalDiagnosticsDataType, // SamplingIntervalDiagnostics
31
29
  SemanticChangeStructureDataType, // SemanticChangeStructure
32
- ServerDiagnosticsSummaryDataType, SessionSecurityDiagnosticsDataType, ServiceCounterDataType, SessionDiagnosticsDataType, BuildInfo, DataTypeDefinition, EnumValueType, TimeZoneDataType, } from "node-opcua-types";
30
+ ServerDiagnosticsSummaryDataType, ServerState, ServerStatusDataType, // ServerStatus
31
+ ServiceCounterDataType, SessionDiagnosticsDataType, SessionSecurityDiagnosticsDataType, SubscriptionDiagnosticsDataType, // SubscriptionDiagnostics
32
+ TimeZoneDataType } from "node-opcua-types";
33
33
  export * from "./applicationurn";
34
+ export * from "./certificate_chain_provider";
34
35
  export * from "./opcua_secure_object";
package/dist/index.js CHANGED
@@ -14,7 +14,7 @@ var __exportStar = (this && this.__exportStar) || function(m, exports) {
14
14
  for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
15
15
  };
16
16
  Object.defineProperty(exports, "__esModule", { value: true });
17
- exports.TimeZoneDataType = exports.EnumValueType = exports.DataTypeDefinition = exports.BuildInfo = exports.SessionDiagnosticsDataType = exports.ServiceCounterDataType = exports.SessionSecurityDiagnosticsDataType = exports.ServerDiagnosticsSummaryDataType = exports.SemanticChangeStructureDataType = exports.SamplingIntervalDiagnosticsDataType = exports.SubscriptionDiagnosticsDataType = exports.ModelChangeStructureDataType = exports.RedundantServerDataType = exports.ServerStatusDataType = exports.ServerState = void 0;
17
+ exports.TimeZoneDataType = exports.SubscriptionDiagnosticsDataType = exports.SessionSecurityDiagnosticsDataType = exports.SessionDiagnosticsDataType = exports.ServiceCounterDataType = exports.ServerStatusDataType = exports.ServerState = exports.ServerDiagnosticsSummaryDataType = exports.SemanticChangeStructureDataType = exports.SamplingIntervalDiagnosticsDataType = exports.RedundantServerDataType = exports.ModelChangeStructureDataType = exports.EnumValueType = exports.DataTypeDefinition = exports.BuildInfo = void 0;
18
18
  /*!
19
19
  * The MIT License (MIT)
20
20
  * Copyright (c) 2022-2025 Sterfive SAS - 833264583 RCS ORLEANS - France (https://www.sterfive.com)
@@ -41,21 +41,22 @@ exports.TimeZoneDataType = exports.EnumValueType = exports.DataTypeDefinition =
41
41
  * @module node-opcua-common
42
42
  */
43
43
  var node_opcua_types_1 = require("node-opcua-types");
44
- Object.defineProperty(exports, "ServerState", { enumerable: true, get: function () { return node_opcua_types_1.ServerState; } });
45
- Object.defineProperty(exports, "ServerStatusDataType", { enumerable: true, get: function () { return node_opcua_types_1.ServerStatusDataType; } });
46
- Object.defineProperty(exports, "RedundantServerDataType", { enumerable: true, get: function () { return node_opcua_types_1.RedundantServerDataType; } });
44
+ Object.defineProperty(exports, "BuildInfo", { enumerable: true, get: function () { return node_opcua_types_1.BuildInfo; } });
45
+ Object.defineProperty(exports, "DataTypeDefinition", { enumerable: true, get: function () { return node_opcua_types_1.DataTypeDefinition; } });
46
+ Object.defineProperty(exports, "EnumValueType", { enumerable: true, get: function () { return node_opcua_types_1.EnumValueType; } });
47
47
  Object.defineProperty(exports, "ModelChangeStructureDataType", { enumerable: true, get: function () { return node_opcua_types_1.ModelChangeStructureDataType; } });
48
- Object.defineProperty(exports, "SubscriptionDiagnosticsDataType", { enumerable: true, get: function () { return node_opcua_types_1.SubscriptionDiagnosticsDataType; } });
48
+ Object.defineProperty(exports, "RedundantServerDataType", { enumerable: true, get: function () { return node_opcua_types_1.RedundantServerDataType; } });
49
49
  Object.defineProperty(exports, "SamplingIntervalDiagnosticsDataType", { enumerable: true, get: function () { return node_opcua_types_1.SamplingIntervalDiagnosticsDataType; } });
50
50
  Object.defineProperty(exports, "SemanticChangeStructureDataType", { enumerable: true, get: function () { return node_opcua_types_1.SemanticChangeStructureDataType; } });
51
51
  Object.defineProperty(exports, "ServerDiagnosticsSummaryDataType", { enumerable: true, get: function () { return node_opcua_types_1.ServerDiagnosticsSummaryDataType; } });
52
- Object.defineProperty(exports, "SessionSecurityDiagnosticsDataType", { enumerable: true, get: function () { return node_opcua_types_1.SessionSecurityDiagnosticsDataType; } });
52
+ Object.defineProperty(exports, "ServerState", { enumerable: true, get: function () { return node_opcua_types_1.ServerState; } });
53
+ Object.defineProperty(exports, "ServerStatusDataType", { enumerable: true, get: function () { return node_opcua_types_1.ServerStatusDataType; } });
53
54
  Object.defineProperty(exports, "ServiceCounterDataType", { enumerable: true, get: function () { return node_opcua_types_1.ServiceCounterDataType; } });
54
55
  Object.defineProperty(exports, "SessionDiagnosticsDataType", { enumerable: true, get: function () { return node_opcua_types_1.SessionDiagnosticsDataType; } });
55
- Object.defineProperty(exports, "BuildInfo", { enumerable: true, get: function () { return node_opcua_types_1.BuildInfo; } });
56
- Object.defineProperty(exports, "DataTypeDefinition", { enumerable: true, get: function () { return node_opcua_types_1.DataTypeDefinition; } });
57
- Object.defineProperty(exports, "EnumValueType", { enumerable: true, get: function () { return node_opcua_types_1.EnumValueType; } });
56
+ Object.defineProperty(exports, "SessionSecurityDiagnosticsDataType", { enumerable: true, get: function () { return node_opcua_types_1.SessionSecurityDiagnosticsDataType; } });
57
+ Object.defineProperty(exports, "SubscriptionDiagnosticsDataType", { enumerable: true, get: function () { return node_opcua_types_1.SubscriptionDiagnosticsDataType; } });
58
58
  Object.defineProperty(exports, "TimeZoneDataType", { enumerable: true, get: function () { return node_opcua_types_1.TimeZoneDataType; } });
59
59
  __exportStar(require("./applicationurn"), exports);
60
+ __exportStar(require("./certificate_chain_provider"), exports);
60
61
  __exportStar(require("./opcua_secure_object"), exports);
61
62
  //# sourceMappingURL=index.js.map
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../source/index.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;;AAAA;;;;;;;;;;;;;;;;;;;;;GAqBG;AACH;;GAEG;AACH,qDAgB0B;AAftB,+GAAA,WAAW,OAAA;AACX,wHAAA,oBAAoB,OAAA;AACpB,2HAAA,uBAAuB,OAAA;AACvB,gIAAA,4BAA4B,OAAA;AAC5B,mIAAA,+BAA+B,OAAA;AAC/B,uIAAA,mCAAmC,OAAA;AACnC,mIAAA,+BAA+B,OAAA;AAC/B,oIAAA,gCAAgC,OAAA;AAChC,sIAAA,kCAAkC,OAAA;AAClC,0HAAA,sBAAsB,OAAA;AACtB,8HAAA,0BAA0B,OAAA;AAC1B,6GAAA,SAAS,OAAA;AACT,sHAAA,kBAAkB,OAAA;AAClB,iHAAA,aAAa,OAAA;AACb,oHAAA,gBAAgB,OAAA;AAGpB,mDAAiC;AACjC,wDAAsC"}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../source/index.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;;AAAA;;;;;;;;;;;;;;;;;;;;;GAqBG;AACH;;GAEG;AACH,qDAgB0B;AAftB,6GAAA,SAAS,OAAA;AACT,sHAAA,kBAAkB,OAAA;AAClB,iHAAA,aAAa,OAAA;AACb,gIAAA,4BAA4B,OAAA;AAC5B,2HAAA,uBAAuB,OAAA;AACvB,uIAAA,mCAAmC,OAAA;AACnC,mIAAA,+BAA+B,OAAA;AAC/B,oIAAA,gCAAgC,OAAA;AAChC,+GAAA,WAAW,OAAA;AACX,wHAAA,oBAAoB,OAAA;AACpB,0HAAA,sBAAsB,OAAA;AACtB,8HAAA,0BAA0B,OAAA;AAC1B,sIAAA,kCAAkC,OAAA;AAClC,mIAAA,+BAA+B,OAAA;AAC/B,oHAAA,gBAAgB,OAAA;AAGpB,mDAAiC;AACjC,+DAA6C;AAC7C,wDAAsC"}
@@ -1,32 +1,76 @@
1
1
  /**
2
2
  * @module node-opcua-common
3
3
  */
4
- import { EventEmitter } from "events";
5
- import { Certificate, PrivateKey } from "node-opcua-crypto/web";
4
+ import { EventEmitter } from "node:events";
5
+ import { type Certificate, type PrivateKey } from "node-opcua-crypto/web";
6
+ import type { ICertificateChainProvider } from "./certificate_chain_provider";
6
7
  export interface ICertificateKeyPairProvider {
7
8
  getCertificate(): Certificate;
8
- getCertificateChain(): Certificate;
9
+ getCertificateChain(): Certificate[];
9
10
  getPrivateKey(): PrivateKey;
10
11
  }
11
- export interface ICertificateKeyPairProviderPriv extends ICertificateKeyPairProvider {
12
- $$certificate: null | Certificate;
13
- $$certificateChain: null | Certificate;
14
- $$privateKey: null | PrivateKey;
12
+ export interface IHasCertificateFile {
13
+ readonly certificateFile: string;
14
+ readonly privateKeyFile: string;
15
15
  }
16
- export declare function getPartialCertificateChain1(certificateChain?: Buffer | null, maxSize?: number): Buffer | undefined;
17
- export declare function getPartialCertificateChain(certificateChain?: Buffer | null, maxSize?: number): Buffer | undefined;
16
+ /**
17
+ * Holds cryptographic secrets (certificate chain and private key) for a
18
+ * certificate/key file pair. Secrets are lazily loaded from disk on first
19
+ * access and kept in truly private `#`-fields so they never appear in
20
+ * `JSON.stringify`, `console.log`, `Object.keys`, or `util.inspect`.
21
+ */
22
+ export declare class SecretHolder implements ICertificateChainProvider {
23
+ #private;
24
+ constructor(obj: IHasCertificateFile);
25
+ getCertificate(): Certificate;
26
+ getCertificateChain(): Certificate[];
27
+ getPrivateKey(): PrivateKey;
28
+ /**
29
+ * Clears cached secrets so the GC can reclaim sensitive material.
30
+ * After calling dispose the holder will re-read from disk on next access.
31
+ */
32
+ dispose(): void;
33
+ /**
34
+ * Alias for {@link dispose}.
35
+ * Implements `ICertificateChainProvider.invalidate()`.
36
+ */
37
+ invalidate(): void;
38
+ toJSON(): Record<string, string>;
39
+ }
40
+ /**
41
+ * Invalidate any cached certificate chain and private key for the given
42
+ * provider so that the next `getCertificate()` / `getPrivateKey()` call
43
+ * re-reads from disk.
44
+ *
45
+ * This is the public replacement for the old `$$certificateChain = null`
46
+ * / `$$privateKey = null` pattern.
47
+ */
48
+ export declare function invalidateCachedSecrets(obj: ICertificateKeyPairProvider): void;
49
+ /**
50
+ * Extract a partial certificate chain from a certificate chain so that the
51
+ * total size of the chain does not exceed maxSize.
52
+ * If maxSize is not provided, the full certificate chain is returned.
53
+ * If the first certificate in the chain already exceeds maxSize, an error is thrown.
54
+ *
55
+ * @param certificateChain - full certificate chain (single DER buffer or array)
56
+ * @param maxSize - optional byte budget
57
+ * @returns the truncated chain as an array of individual certificates
58
+ */
59
+ export declare function getPartialCertificateChain(certificateChain?: Certificate | Certificate[] | null, maxSize?: number): Certificate[];
18
60
  export interface IOPCUASecureObjectOptions {
19
61
  certificateFile?: string;
20
62
  privateKeyFile?: string;
21
63
  }
22
64
  /**
23
- * an object that provides a certificate and a privateKey
65
+ * An object that provides a certificate and a privateKey.
66
+ * Secrets are loaded lazily and stored in a module-private WeakMap
67
+ * so they never appear on the instance.
24
68
  */
25
- export declare class OPCUASecureObject extends EventEmitter implements ICertificateKeyPairProvider {
69
+ export declare class OPCUASecureObject<T extends Record<string | symbol, any> = any> extends EventEmitter<T> implements ICertificateKeyPairProvider, IHasCertificateFile {
26
70
  readonly certificateFile: string;
27
71
  readonly privateKeyFile: string;
28
72
  constructor(options: IOPCUASecureObjectOptions);
29
73
  getCertificate(): Certificate;
30
- getCertificateChain(): Certificate;
74
+ getCertificateChain(): Certificate[];
31
75
  getPrivateKey(): PrivateKey;
32
76
  }
@@ -3,52 +3,159 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
3
3
  return (mod && mod.__esModule) ? mod : { "default": mod };
4
4
  };
5
5
  Object.defineProperty(exports, "__esModule", { value: true });
6
- exports.OPCUASecureObject = void 0;
7
- exports.getPartialCertificateChain1 = getPartialCertificateChain1;
6
+ exports.OPCUASecureObject = exports.SecretHolder = void 0;
7
+ exports.invalidateCachedSecrets = invalidateCachedSecrets;
8
8
  exports.getPartialCertificateChain = getPartialCertificateChain;
9
9
  /**
10
10
  * @module node-opcua-common
11
11
  */
12
- const events_1 = require("events");
13
- const fs_1 = __importDefault(require("fs"));
12
+ const node_events_1 = require("node:events");
13
+ const node_fs_1 = __importDefault(require("node:fs"));
14
14
  const node_opcua_assert_1 = require("node-opcua-assert");
15
- const web_1 = require("node-opcua-crypto/web");
16
15
  const node_opcua_crypto_1 = require("node-opcua-crypto");
17
- function _load_certificate(certificateFilename) {
18
- const der = (0, node_opcua_crypto_1.readCertificate)(certificateFilename);
19
- return der;
16
+ const web_1 = require("node-opcua-crypto/web");
17
+ /**
18
+ * Holds cryptographic secrets (certificate chain and private key) for a
19
+ * certificate/key file pair. Secrets are lazily loaded from disk on first
20
+ * access and kept in truly private `#`-fields so they never appear in
21
+ * `JSON.stringify`, `console.log`, `Object.keys`, or `util.inspect`.
22
+ */
23
+ class SecretHolder {
24
+ #certificateChain = null;
25
+ #privateKey = null;
26
+ #obj;
27
+ constructor(obj) {
28
+ this.#obj = obj;
29
+ }
30
+ getCertificate() {
31
+ // Ensure the chain is loaded before accessing [0]
32
+ const chain = this.getCertificateChain();
33
+ return chain[0];
34
+ }
35
+ getCertificateChain() {
36
+ if (!this.#certificateChain) {
37
+ const file = this.#obj.certificateFile;
38
+ if (!node_fs_1.default.existsSync(file)) {
39
+ throw new Error(`Certificate file must exist: ${file}`);
40
+ }
41
+ const chain = (0, node_opcua_crypto_1.readCertificateChain)(file);
42
+ if (!chain || chain.length === 0) {
43
+ throw new Error(`Invalid certificate chain (length=0) ${file}`);
44
+ }
45
+ this.#certificateChain = chain;
46
+ }
47
+ return this.#certificateChain;
48
+ }
49
+ getPrivateKey() {
50
+ if (!this.#privateKey) {
51
+ const file = this.#obj.privateKeyFile;
52
+ if (!node_fs_1.default.existsSync(file)) {
53
+ throw new Error(`Private key file must exist: ${file}`);
54
+ }
55
+ const key = (0, node_opcua_crypto_1.readPrivateKey)(file);
56
+ if (key instanceof Buffer) {
57
+ throw new Error(`Invalid private key ${file}. Should not be a buffer`);
58
+ }
59
+ this.#privateKey = key;
60
+ }
61
+ return this.#privateKey;
62
+ }
63
+ /**
64
+ * Clears cached secrets so the GC can reclaim sensitive material.
65
+ * After calling dispose the holder will re-read from disk on next access.
66
+ */
67
+ dispose() {
68
+ this.#certificateChain = null;
69
+ this.#privateKey = null;
70
+ }
71
+ /**
72
+ * Alias for {@link dispose}.
73
+ * Implements `ICertificateChainProvider.invalidate()`.
74
+ */
75
+ invalidate() {
76
+ this.dispose();
77
+ }
78
+ // Prevent secrets from leaking through JSON serialization
79
+ toJSON() {
80
+ return { certificateFile: this.#obj.certificateFile, privateKeyFile: this.#obj.privateKeyFile };
81
+ }
82
+ // Prevent secrets from leaking through console.log / util.inspect
83
+ [Symbol.for("nodejs.util.inspect.custom")]() {
84
+ return `SecretHolder { certificateFile: "${this.#obj.certificateFile}", privateKeyFile: "${this.#obj.privateKeyFile}" }`;
85
+ }
20
86
  }
21
- function _load_private_key(privateKeyFilename) {
22
- return (0, node_opcua_crypto_1.readPrivateKey)(privateKeyFilename);
87
+ exports.SecretHolder = SecretHolder;
88
+ /**
89
+ * Module-private WeakMap that associates an ICertificateKeyPairProvider
90
+ * with its SecretHolder. Using a WeakMap means:
91
+ * - The secret holder is invisible from the outside (no enumerable property)
92
+ * - If the owning object is GC'd, the SecretHolder is automatically collected
93
+ */
94
+ const secretHolders = new WeakMap();
95
+ function getSecretHolder(obj) {
96
+ let holder = secretHolders.get(obj);
97
+ if (!holder) {
98
+ holder = new SecretHolder(obj);
99
+ secretHolders.set(obj, holder);
100
+ }
101
+ return holder;
23
102
  }
24
- function getPartialCertificateChain1(certificateChain, maxSize) {
25
- return certificateChain || undefined;
103
+ /**
104
+ * Invalidate any cached certificate chain and private key for the given
105
+ * provider so that the next `getCertificate()` / `getPrivateKey()` call
106
+ * re-reads from disk.
107
+ *
108
+ * This is the public replacement for the old `$$certificateChain = null`
109
+ * / `$$privateKey = null` pattern.
110
+ */
111
+ function invalidateCachedSecrets(obj) {
112
+ const holder = secretHolders.get(obj);
113
+ if (holder) {
114
+ holder.dispose();
115
+ }
26
116
  }
117
+ /**
118
+ * Extract a partial certificate chain from a certificate chain so that the
119
+ * total size of the chain does not exceed maxSize.
120
+ * If maxSize is not provided, the full certificate chain is returned.
121
+ * If the first certificate in the chain already exceeds maxSize, an error is thrown.
122
+ *
123
+ * @param certificateChain - full certificate chain (single DER buffer or array)
124
+ * @param maxSize - optional byte budget
125
+ * @returns the truncated chain as an array of individual certificates
126
+ */
27
127
  function getPartialCertificateChain(certificateChain, maxSize) {
28
- if (!certificateChain || certificateChain.length === 0) {
29
- return undefined;
128
+ if (!certificateChain ||
129
+ (Array.isArray(certificateChain) && certificateChain.length === 0) ||
130
+ (certificateChain instanceof Buffer && certificateChain.length === 0)) {
131
+ return [];
30
132
  }
133
+ const certificates = Array.isArray(certificateChain) ? certificateChain : (0, web_1.split_der)(certificateChain);
31
134
  if (maxSize === undefined) {
32
- return certificateChain;
135
+ return certificates;
33
136
  }
34
- const certificates = (0, web_1.split_der)(certificateChain);
35
137
  // at least include first certificate
36
- let buffer = certificates.length == 1 ? certificateChain : Buffer.from(certificates[0]);
138
+ const chainToReturn = [certificates[0]];
139
+ let cumulatedLength = certificates[0].length;
37
140
  // Throw if first certificate already exceed maxSize
38
- if (buffer.length > maxSize) {
39
- throw new Error(`getPartialCertificateChain not enough space for leaf certificate ${maxSize} < ${buffer.length}`);
141
+ if (cumulatedLength > maxSize) {
142
+ throw new Error(`getPartialCertificateChain not enough space for leaf certificate ${maxSize} < ${cumulatedLength}`);
40
143
  }
41
144
  let index = 1;
42
- while (index < certificates.length && buffer.length + certificates[index].length < maxSize) {
43
- buffer = Buffer.concat([buffer, certificates[index]]);
145
+ while (index < certificates.length && cumulatedLength + certificates[index].length <= maxSize) {
146
+ chainToReturn.push(certificates[index]);
147
+ cumulatedLength += certificates[index].length;
44
148
  index++;
45
149
  }
46
- return buffer;
150
+ return chainToReturn;
47
151
  }
48
152
  /**
49
- * an object that provides a certificate and a privateKey
153
+ * An object that provides a certificate and a privateKey.
154
+ * Secrets are loaded lazily and stored in a module-private WeakMap
155
+ * so they never appear on the instance.
50
156
  */
51
- class OPCUASecureObject extends events_1.EventEmitter {
157
+ // biome-ignore lint/suspicious/noExplicitAny: EventEmitter use any
158
+ class OPCUASecureObject extends node_events_1.EventEmitter {
52
159
  certificateFile;
53
160
  privateKeyFile;
54
161
  constructor(options) {
@@ -59,34 +166,13 @@ class OPCUASecureObject extends events_1.EventEmitter {
59
166
  this.privateKeyFile = options.privateKeyFile || "invalid private key file";
60
167
  }
61
168
  getCertificate() {
62
- const priv = this;
63
- if (!priv.$$certificate) {
64
- const certChain = this.getCertificateChain();
65
- priv.$$certificate = (0, web_1.split_der)(certChain)[0];
66
- }
67
- return priv.$$certificate;
169
+ return getSecretHolder(this).getCertificate();
68
170
  }
69
171
  getCertificateChain() {
70
- const priv = this;
71
- if (!priv.$$certificateChain) {
72
- (0, node_opcua_assert_1.assert)(fs_1.default.existsSync(this.certificateFile), "Certificate file must exist :" + this.certificateFile);
73
- priv.$$certificateChain = _load_certificate(this.certificateFile);
74
- if (priv.$$certificateChain && priv.$$certificateChain.length === 0) {
75
- // do it again for debug purposes
76
- priv.$$certificateChain = _load_certificate(this.certificateFile);
77
- throw new Error("Invalid certificate length = 0 " + this.certificateFile);
78
- }
79
- }
80
- return priv.$$certificateChain;
172
+ return getSecretHolder(this).getCertificateChain();
81
173
  }
82
174
  getPrivateKey() {
83
- const priv = this;
84
- if (!priv.$$privateKey) {
85
- (0, node_opcua_assert_1.assert)(fs_1.default.existsSync(this.privateKeyFile), "private file must exist :" + this.privateKeyFile);
86
- priv.$$privateKey = _load_private_key(this.privateKeyFile);
87
- }
88
- (0, node_opcua_assert_1.assert)(!(priv.$$privateKey instanceof Buffer), "should not be a buffer");
89
- return priv.$$privateKey;
175
+ return getSecretHolder(this).getPrivateKey();
90
176
  }
91
177
  }
92
178
  exports.OPCUASecureObject = OPCUASecureObject;
@@ -1 +1 @@
1
- {"version":3,"file":"opcua_secure_object.js","sourceRoot":"","sources":["../source/opcua_secure_object.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"opcua_secure_object.js","sourceRoot":"","sources":["../source/opcua_secure_object.ts"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -1,9 +1,10 @@
1
1
  {
2
2
  "name": "node-opcua-common",
3
- "version": "2.165.0",
3
+ "version": "2.168.0",
4
4
  "description": "pure nodejs OPCUA SDK - module common",
5
5
  "scripts": {
6
6
  "test": "mocha",
7
+ "test:check": "tsc --noEmit -p test/tsconfig.json",
7
8
  "clean": "npx rimraf -g node_modules dist *.tsbuildinfo",
8
9
  "build": "tsc -b",
9
10
  "lint": "eslint source/**/*.ts"
@@ -12,8 +13,8 @@
12
13
  "types": "./dist/index.d.ts",
13
14
  "dependencies": {
14
15
  "node-opcua-assert": "2.164.0",
15
- "node-opcua-crypto": "5.3.0",
16
- "node-opcua-types": "2.165.0"
16
+ "node-opcua-crypto": "5.3.3",
17
+ "node-opcua-types": "2.168.0"
17
18
  },
18
19
  "author": "Etienne Rossignon",
19
20
  "license": "MIT",
@@ -30,7 +31,7 @@
30
31
  "internet of things"
31
32
  ],
32
33
  "homepage": "http://node-opcua.github.io/",
33
- "gitHead": "fe53ac03427fcb223996446c991f7496635ba193",
34
+ "gitHead": "653b6d6df801ca17298308089dee32e5b12102b6",
34
35
  "files": [
35
36
  "dist",
36
37
  "source"
@@ -1,12 +1,11 @@
1
1
  /**
2
2
  * @module node-opcua-common
3
3
  */
4
- import {createHash} from "crypto";
4
+ import { createHash } from "crypto";
5
5
 
6
6
  import { assert } from "node-opcua-assert";
7
7
 
8
8
  export function makeApplicationUrn(hostname: string, suffix: string): string {
9
-
10
9
  assert(!suffix.match(/urn:/), "already a application URN ?");
11
10
  // beware : Openssl doesn't support urn with length greater than 64 !!
12
11
  // sometimes hostname length could be too long ...
@@ -0,0 +1,90 @@
1
+ /**
2
+ * Pluggable certificate chain provider for OPC UA endpoints.
3
+ *
4
+ * Abstracts how an endpoint obtains its certificate chain and private key,
5
+ * allowing both static (in-memory) and dynamic (disk-based) strategies
6
+ * without monkey-patching.
7
+ *
8
+ * @module node-opcua-common
9
+ */
10
+ import type { Certificate, PrivateKey } from "node-opcua-crypto/web";
11
+
12
+ import type { ICertificateKeyPairProvider } from "./opcua_secure_object";
13
+
14
+ /**
15
+ * Provides a certificate chain and private key to an OPC UA endpoint.
16
+ *
17
+ * Implementations may read from memory, disk, or any other source.
18
+ * See also {@link SecretHolder} which implements this interface for
19
+ * disk-based access with lazy caching.
20
+ */
21
+ export interface ICertificateChainProvider extends ICertificateKeyPairProvider {
22
+ /**
23
+ * Invalidate any cached values so the next access re-reads
24
+ * from the underlying source. No-op for static providers.
25
+ */
26
+ invalidate(): void;
27
+ }
28
+
29
+ /**
30
+ * Holds a certificate chain and private key in memory.
31
+ *
32
+ * Used as the default provider when push certificate management
33
+ * is NOT installed. The chain can be replaced in-place via `update()`.
34
+ */
35
+ export class StaticCertificateChainProvider implements ICertificateChainProvider {
36
+ #chain: Certificate[];
37
+ #key: PrivateKey;
38
+
39
+ constructor(chain: Certificate[], key: PrivateKey) {
40
+ this.#chain = chain;
41
+ this.#key = key;
42
+ }
43
+
44
+ public getCertificate(): Certificate {
45
+ return this.#chain[0];
46
+ }
47
+
48
+ public getCertificateChain(): Certificate[] {
49
+ return this.#chain;
50
+ }
51
+
52
+ public getPrivateKey(): PrivateKey {
53
+ return this.#key;
54
+ }
55
+
56
+ /**
57
+ * No-op for static provider — the chain is already in memory.
58
+ * Use `update()` to replace the chain explicitly.
59
+ */
60
+ public invalidate(): void {
61
+ // nothing to invalidate for a static provider
62
+ }
63
+
64
+ /**
65
+ * Replace the certificate chain and optionally the private key.
66
+ *
67
+ * This immediately affects all consumers that call
68
+ * `getCertificateChain()` on this provider (including
69
+ * endpoint descriptions with dynamic `serverCertificate` getters).
70
+ */
71
+ public update(chain: Certificate[], key?: PrivateKey): void {
72
+ if (chain.length === 0) {
73
+ throw new Error("StaticCertificateChainProvider.update: chain must not be empty");
74
+ }
75
+ this.#chain = chain;
76
+ if (key !== undefined) {
77
+ this.#key = key;
78
+ }
79
+ }
80
+
81
+ // Prevent secrets from leaking through JSON serialization
82
+ public toJSON(): Record<string, string> {
83
+ return { provider: "StaticCertificateChainProvider" };
84
+ }
85
+
86
+ // Prevent secrets from leaking through console.log / util.inspect
87
+ public [Symbol.for("nodejs.util.inspect.custom")](): string {
88
+ return "StaticCertificateChainProvider { <in-memory> }";
89
+ }
90
+ }
package/source/index.ts CHANGED
@@ -9,10 +9,10 @@
9
9
  * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
10
10
  * the Software, and to permit persons to whom the Software is furnished to do so,
11
11
  * subject to the following conditions:
12
- *
12
+ *
13
13
  * The above copyright notice and this permission notice shall be included in all
14
14
  * copies or substantial portions of the Software.
15
- *
15
+ *
16
16
  * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17
17
  * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
18
18
  * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
@@ -24,22 +24,23 @@
24
24
  * @module node-opcua-common
25
25
  */
26
26
  export {
27
- ServerState,
28
- ServerStatusDataType, // ServerStatus
29
- RedundantServerDataType, // RedundantServer
30
- ModelChangeStructureDataType, // ModelChangeStructure
31
- SubscriptionDiagnosticsDataType, // SubscriptionDiagnostics
27
+ BuildInfo,
28
+ DataTypeDefinition,
29
+ EnumValueType,
30
+ ModelChangeStructureDataType, // ModelChangeStructure
31
+ RedundantServerDataType, // RedundantServer
32
32
  SamplingIntervalDiagnosticsDataType, // SamplingIntervalDiagnostics
33
33
  SemanticChangeStructureDataType, // SemanticChangeStructure
34
34
  ServerDiagnosticsSummaryDataType,
35
- SessionSecurityDiagnosticsDataType,
35
+ ServerState,
36
+ ServerStatusDataType, // ServerStatus
36
37
  ServiceCounterDataType,
37
38
  SessionDiagnosticsDataType,
38
- BuildInfo,
39
- DataTypeDefinition,
40
- EnumValueType,
41
- TimeZoneDataType,
39
+ SessionSecurityDiagnosticsDataType,
40
+ SubscriptionDiagnosticsDataType, // SubscriptionDiagnostics
41
+ TimeZoneDataType
42
42
  } from "node-opcua-types";
43
43
 
44
44
  export * from "./applicationurn";
45
+ export * from "./certificate_chain_provider";
45
46
  export * from "./opcua_secure_object";
@@ -1,57 +1,172 @@
1
1
  /**
2
2
  * @module node-opcua-common
3
3
  */
4
- import { EventEmitter } from "events";
5
- import fs from "fs";
6
-
4
+ import { EventEmitter } from "node:events";
5
+ import fs from "node:fs";
7
6
  import { assert } from "node-opcua-assert";
8
- import { Certificate, PrivateKey, split_der } from "node-opcua-crypto/web";
9
- import { readCertificate, readPrivateKey } from "node-opcua-crypto";
7
+ import { readCertificateChain, readPrivateKey } from "node-opcua-crypto";
8
+ import { type Certificate, type PrivateKey, split_der } from "node-opcua-crypto/web";
9
+
10
+ import type { ICertificateChainProvider } from "./certificate_chain_provider";
11
+
10
12
  export interface ICertificateKeyPairProvider {
11
13
  getCertificate(): Certificate;
12
- getCertificateChain(): Certificate;
14
+ getCertificateChain(): Certificate[];
13
15
  getPrivateKey(): PrivateKey;
14
16
  }
15
- export interface ICertificateKeyPairProviderPriv extends ICertificateKeyPairProvider {
16
- $$certificate: null | Certificate;
17
- $$certificateChain: null | Certificate;
18
- $$privateKey: null | PrivateKey;
19
- }
20
- function _load_certificate(certificateFilename: string): Certificate {
21
- const der = readCertificate(certificateFilename);
22
- return der;
17
+
18
+ export interface IHasCertificateFile {
19
+ readonly certificateFile: string;
20
+ readonly privateKeyFile: string;
23
21
  }
24
22
 
25
- function _load_private_key(privateKeyFilename: string): PrivateKey {
26
- return readPrivateKey(privateKeyFilename);
23
+ /**
24
+ * Holds cryptographic secrets (certificate chain and private key) for a
25
+ * certificate/key file pair. Secrets are lazily loaded from disk on first
26
+ * access and kept in truly private `#`-fields so they never appear in
27
+ * `JSON.stringify`, `console.log`, `Object.keys`, or `util.inspect`.
28
+ */
29
+ export class SecretHolder implements ICertificateChainProvider {
30
+ #certificateChain: Certificate[] | null = null;
31
+ #privateKey: PrivateKey | null = null;
32
+ #obj: IHasCertificateFile;
33
+
34
+ constructor(obj: IHasCertificateFile) {
35
+ this.#obj = obj;
36
+ }
37
+
38
+ public getCertificate(): Certificate {
39
+ // Ensure the chain is loaded before accessing [0]
40
+ const chain = this.getCertificateChain();
41
+ return chain[0];
42
+ }
43
+
44
+ public getCertificateChain(): Certificate[] {
45
+ if (!this.#certificateChain) {
46
+ const file = this.#obj.certificateFile;
47
+ if (!fs.existsSync(file)) {
48
+ throw new Error(`Certificate file must exist: ${file}`);
49
+ }
50
+ const chain = readCertificateChain(file);
51
+ if (!chain || chain.length === 0) {
52
+ throw new Error(`Invalid certificate chain (length=0) ${file}`);
53
+ }
54
+ this.#certificateChain = chain;
55
+ }
56
+ return this.#certificateChain;
57
+ }
58
+
59
+ public getPrivateKey(): PrivateKey {
60
+ if (!this.#privateKey) {
61
+ const file = this.#obj.privateKeyFile;
62
+ if (!fs.existsSync(file)) {
63
+ throw new Error(`Private key file must exist: ${file}`);
64
+ }
65
+ const key = readPrivateKey(file);
66
+ if (key instanceof Buffer) {
67
+ throw new Error(`Invalid private key ${file}. Should not be a buffer`);
68
+ }
69
+ this.#privateKey = key;
70
+ }
71
+ return this.#privateKey;
72
+ }
73
+
74
+ /**
75
+ * Clears cached secrets so the GC can reclaim sensitive material.
76
+ * After calling dispose the holder will re-read from disk on next access.
77
+ */
78
+ public dispose(): void {
79
+ this.#certificateChain = null;
80
+ this.#privateKey = null;
81
+ }
82
+
83
+ /**
84
+ * Alias for {@link dispose}.
85
+ * Implements `ICertificateChainProvider.invalidate()`.
86
+ */
87
+ public invalidate(): void {
88
+ this.dispose();
89
+ }
90
+
91
+ // Prevent secrets from leaking through JSON serialization
92
+ public toJSON(): Record<string, string> {
93
+ return { certificateFile: this.#obj.certificateFile, privateKeyFile: this.#obj.privateKeyFile };
94
+ }
95
+
96
+ // Prevent secrets from leaking through console.log / util.inspect
97
+ public [Symbol.for("nodejs.util.inspect.custom")](): string {
98
+ return `SecretHolder { certificateFile: "${this.#obj.certificateFile}", privateKeyFile: "${this.#obj.privateKeyFile}" }`;
99
+ }
27
100
  }
28
101
 
29
- export function getPartialCertificateChain1(certificateChain?: Buffer| null, maxSize?: number ): Buffer| undefined {
102
+ /**
103
+ * Module-private WeakMap that associates an ICertificateKeyPairProvider
104
+ * with its SecretHolder. Using a WeakMap means:
105
+ * - The secret holder is invisible from the outside (no enumerable property)
106
+ * - If the owning object is GC'd, the SecretHolder is automatically collected
107
+ */
108
+ const secretHolders = new WeakMap<object, SecretHolder>();
30
109
 
31
- return certificateChain || undefined;
110
+ function getSecretHolder(obj: ICertificateKeyPairProvider & IHasCertificateFile): SecretHolder {
111
+ let holder = secretHolders.get(obj);
112
+ if (!holder) {
113
+ holder = new SecretHolder(obj);
114
+ secretHolders.set(obj, holder);
115
+ }
116
+ return holder;
32
117
  }
33
- export function getPartialCertificateChain(certificateChain?: Buffer | null, maxSize?: number): Buffer | undefined {
34
-
35
- if (!certificateChain || certificateChain.length === 0) {
36
- return undefined;
118
+
119
+ /**
120
+ * Invalidate any cached certificate chain and private key for the given
121
+ * provider so that the next `getCertificate()` / `getPrivateKey()` call
122
+ * re-reads from disk.
123
+ *
124
+ * This is the public replacement for the old `$$certificateChain = null`
125
+ * / `$$privateKey = null` pattern.
126
+ */
127
+ export function invalidateCachedSecrets(obj: ICertificateKeyPairProvider): void {
128
+ const holder = secretHolders.get(obj);
129
+ if (holder) {
130
+ holder.dispose();
37
131
  }
132
+ }
133
+
134
+ /**
135
+ * Extract a partial certificate chain from a certificate chain so that the
136
+ * total size of the chain does not exceed maxSize.
137
+ * If maxSize is not provided, the full certificate chain is returned.
138
+ * If the first certificate in the chain already exceeds maxSize, an error is thrown.
139
+ *
140
+ * @param certificateChain - full certificate chain (single DER buffer or array)
141
+ * @param maxSize - optional byte budget
142
+ * @returns the truncated chain as an array of individual certificates
143
+ */
144
+ export function getPartialCertificateChain(certificateChain?: Certificate | Certificate[] | null, maxSize?: number): Certificate[] {
145
+ if (
146
+ !certificateChain ||
147
+ (Array.isArray(certificateChain) && certificateChain.length === 0) ||
148
+ (certificateChain instanceof Buffer && certificateChain.length === 0)
149
+ ) {
150
+ return [];
151
+ }
152
+ const certificates = Array.isArray(certificateChain) ? certificateChain : split_der(certificateChain);
38
153
  if (maxSize === undefined) {
39
- return certificateChain;
154
+ return certificates;
40
155
  }
41
- const certificates = split_der(certificateChain);
42
156
  // at least include first certificate
43
- let buffer = certificates.length == 1 ? certificateChain : Buffer.from(certificates[0]);
157
+ const chainToReturn: Certificate[] = [certificates[0]];
158
+ let cumulatedLength = certificates[0].length;
44
159
  // Throw if first certificate already exceed maxSize
45
- if (buffer.length> maxSize) {
46
- throw new Error(`getPartialCertificateChain not enough space for leaf certificate ${maxSize} < ${buffer.length}`);
160
+ if (cumulatedLength > maxSize) {
161
+ throw new Error(`getPartialCertificateChain not enough space for leaf certificate ${maxSize} < ${cumulatedLength}`);
47
162
  }
48
163
  let index = 1;
49
- while (index < certificates.length && buffer.length + certificates[index].length < maxSize) {
50
- buffer = Buffer.concat([buffer, certificates[index]]);
164
+ while (index < certificates.length && cumulatedLength + certificates[index].length <= maxSize) {
165
+ chainToReturn.push(certificates[index]);
166
+ cumulatedLength += certificates[index].length;
51
167
  index++;
52
168
  }
53
- return buffer;
54
-
169
+ return chainToReturn;
55
170
  }
56
171
 
57
172
  export interface IOPCUASecureObjectOptions {
@@ -60,9 +175,16 @@ export interface IOPCUASecureObjectOptions {
60
175
  }
61
176
 
62
177
  /**
63
- * an object that provides a certificate and a privateKey
178
+ * An object that provides a certificate and a privateKey.
179
+ * Secrets are loaded lazily and stored in a module-private WeakMap
180
+ * so they never appear on the instance.
64
181
  */
65
- export class OPCUASecureObject extends EventEmitter implements ICertificateKeyPairProvider {
182
+
183
+ // biome-ignore lint/suspicious/noExplicitAny: EventEmitter use any
184
+ export class OPCUASecureObject<T extends Record<string | symbol, any> = any>
185
+ extends EventEmitter<T>
186
+ implements ICertificateKeyPairProvider, IHasCertificateFile
187
+ {
66
188
  public readonly certificateFile: string;
67
189
  public readonly privateKeyFile: string;
68
190
 
@@ -70,41 +192,19 @@ export class OPCUASecureObject extends EventEmitter implements ICertificateKeyPa
70
192
  super();
71
193
  assert(typeof options.certificateFile === "string");
72
194
  assert(typeof options.privateKeyFile === "string");
73
-
74
195
  this.certificateFile = options.certificateFile || "invalid certificate file";
75
196
  this.privateKeyFile = options.privateKeyFile || "invalid private key file";
76
197
  }
77
198
 
78
199
  public getCertificate(): Certificate {
79
- const priv = this as unknown as ICertificateKeyPairProviderPriv;
80
- if (!priv.$$certificate) {
81
- const certChain = this.getCertificateChain();
82
- priv.$$certificate = split_der(certChain)[0] as Certificate;
83
- }
84
- return priv.$$certificate;
85
- }
86
-
87
- public getCertificateChain(): Certificate {
88
- const priv = this as unknown as ICertificateKeyPairProviderPriv;
89
- if (!priv.$$certificateChain) {
90
- assert(fs.existsSync(this.certificateFile), "Certificate file must exist :" + this.certificateFile);
91
- priv.$$certificateChain = _load_certificate(this.certificateFile);
92
- if (priv.$$certificateChain && priv.$$certificateChain.length === 0) {
93
- // do it again for debug purposes
94
- priv.$$certificateChain = _load_certificate(this.certificateFile);
95
- throw new Error("Invalid certificate length = 0 " + this.certificateFile);
96
- }
97
- }
98
- return priv.$$certificateChain;
200
+ return getSecretHolder(this).getCertificate();
201
+ }
202
+
203
+ public getCertificateChain(): Certificate[] {
204
+ return getSecretHolder(this).getCertificateChain();
99
205
  }
100
206
 
101
207
  public getPrivateKey(): PrivateKey {
102
- const priv = this as unknown as ICertificateKeyPairProviderPriv;
103
- if (!priv.$$privateKey) {
104
- assert(fs.existsSync(this.privateKeyFile), "private file must exist :" + this.privateKeyFile);
105
- priv.$$privateKey = _load_private_key(this.privateKeyFile);
106
- }
107
- assert(!(priv.$$privateKey instanceof Buffer), "should not be a buffer");
108
- return priv.$$privateKey;
208
+ return getSecretHolder(this).getPrivateKey();
109
209
  }
110
210
  }