node-opcua-server-configuration 2.168.0 → 2.169.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.
@@ -2,7 +2,7 @@ import fs from "node:fs";
2
2
  import path from "node:path";
3
3
  import { assert } from "node-opcua-assert";
4
4
  import { exploreCertificate, readPrivateKey } from "node-opcua-crypto";
5
- import { certificateMatchesPrivateKey, coercePEMorDerToPrivateKey, coercePrivateKeyPem, makeSHA1Thumbprint, toPem } from "node-opcua-crypto/web";
5
+ import { certificateMatchesPrivateKey, coercePEMorDerToPrivateKey, coercePrivateKeyPem, combine_der, makeSHA1Thumbprint, toPem } from "node-opcua-crypto/web";
6
6
  import { checkDebugFlag, make_debugLog, make_warningLog } from "node-opcua-debug";
7
7
  import { StatusCodes } from "node-opcua-status-code";
8
8
  import { validateCertificateAndChain } from "../certificate_validation.js";
@@ -28,13 +28,25 @@ async function preInstallIssuerCertificates(serverImpl, certificateManager, issu
28
28
  }
29
29
  }
30
30
  // Helper: Stage main certificate to temporary files
31
- async function preInstallCertificate(serverImpl, certificateManager, certificate) {
31
+ //
32
+ // The PEM file contains the full chain (leaf + issuer CAs) so that
33
+ // getCertificateChain() returns the complete chain. This allows
34
+ // OpenSecureChannel and CreateSession to present the full chain to
35
+ // connecting clients, enabling them to build and verify the trust
36
+ // path without needing all CA certificates pre-installed.
37
+ async function preInstallCertificate(serverImpl, certificateManager, certificate, issuerCertificates) {
32
38
  const certFolder = certificateManager.ownCertFolder;
33
- const destDER = path.join(certFolder, "certificate.der");
34
39
  const destPEM = path.join(certFolder, "certificate.pem");
35
- const certificatePEM = toPem(certificate, "CERTIFICATE");
36
- await serverImpl.fileTransactionManager.stageFile(destDER, certificate);
40
+ // Build the full chain: leaf first, then issuer CAs
41
+ const certificateChain = [certificate, ...(issuerCertificates ?? [])];
42
+ const certificatePEM = toPem(combine_der(certificateChain), "CERTIFICATE");
37
43
  await serverImpl.fileTransactionManager.stageFile(destPEM, certificatePEM, "utf-8");
44
+ // Clean up any leftover certificate.der from previous installations.
45
+ // Since DER is no longer written, a stale file would be misleading.
46
+ const legacyDER = path.join(certFolder, "certificate.der");
47
+ if (fs.existsSync(legacyDER)) {
48
+ serverImpl.fileTransactionManager.stageFileRemoval(legacyDER);
49
+ }
38
50
  }
39
51
  // Helper: Stage private key to temporary file
40
52
  async function preInstallPrivateKey(serverImpl, certificateManager, privateKeyFormat, privateKey) {
@@ -96,7 +108,7 @@ export async function executeUpdateCertificate(serverImpl, certificateGroupId, c
96
108
  return { statusCode: StatusCodes.BadSecurityChecksFailed, applyChangesRequired: false };
97
109
  }
98
110
  await preInstallIssuerCertificates(serverImpl, certificateManager, issuerCertificates);
99
- await preInstallCertificate(serverImpl, certificateManager, certificate);
111
+ await preInstallCertificate(serverImpl, certificateManager, certificate, issuerCertificates);
100
112
  serverImpl.emit("certificateUpdated", certificateGroupId, certificate);
101
113
  return { statusCode: StatusCodes.Good, applyChangesRequired: true };
102
114
  }
@@ -131,7 +143,7 @@ export async function executeUpdateCertificate(serverImpl, certificateGroupId, c
131
143
  }
132
144
  await preInstallPrivateKey(serverImpl, certificateManager, privateKeyFormat, tempPrivateKey);
133
145
  await preInstallIssuerCertificates(serverImpl, certificateManager, issuerCertificates);
134
- await preInstallCertificate(serverImpl, certificateManager, certificate);
146
+ await preInstallCertificate(serverImpl, certificateManager, certificate, issuerCertificates);
135
147
  serverImpl.emit("certificateUpdated", certificateGroupId, certificate);
136
148
  return { statusCode: StatusCodes.Good, applyChangesRequired: true };
137
149
  }
@@ -1 +1 @@
1
- {"version":3,"file":"update_certificate.js","sourceRoot":"","sources":["../../../source/server/push_certificate_manager/update_certificate.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"update_certificate.js","sourceRoot":"","sources":["../../../source/server/push_certificate_manager/update_certificate.ts"],"names":[],"mappings":"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"}
@@ -18,6 +18,11 @@ async function readAll(folder) {
18
18
  const ext = path.extname(file);
19
19
  if (ext === ".der" || ext === ".pem") {
20
20
  const chain = await readCertificateChainAsync(file);
21
+ // Return the full chain as a single ByteString.
22
+ // When addTrustedCertificateFromChain stores a chain-on-disk
23
+ // (leaf + issuer CAs in a single PEM), we preserve that
24
+ // chain so clients reading the TrustList can use it for
25
+ // chain-building.
21
26
  const concatenated = Buffer.concat(chain);
22
27
  results.push(concatenated);
23
28
  }
@@ -1 +1 @@
1
- {"version":3,"file":"trust_list_server.js","sourceRoot":"","sources":["../../source/server/trust_list_server.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"trust_list_server.js","sourceRoot":"","sources":["../../source/server/trust_list_server.ts"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "node-opcua-server-configuration",
3
- "version": "2.168.0",
3
+ "version": "2.169.0",
4
4
  "description": "pure nodejs OPCUA SDK - module server-configuration",
5
5
  "scripts": {
6
6
  "build": "tsc -b",
@@ -15,34 +15,34 @@
15
15
  "dependencies": {
16
16
  "chalk": "4.1.2",
17
17
  "memfs": "^4.57.1",
18
- "node-opcua-address-space": "2.168.0",
19
- "node-opcua-address-space-base": "2.168.0",
18
+ "node-opcua-address-space": "2.169.0",
19
+ "node-opcua-address-space-base": "2.169.0",
20
20
  "node-opcua-assert": "2.164.0",
21
- "node-opcua-basic-types": "2.168.0",
22
- "node-opcua-binary-stream": "2.168.0",
23
- "node-opcua-certificate-manager": "2.168.0",
24
- "node-opcua-common": "2.168.0",
21
+ "node-opcua-basic-types": "2.169.0",
22
+ "node-opcua-binary-stream": "2.169.0",
23
+ "node-opcua-certificate-manager": "2.169.0",
24
+ "node-opcua-common": "2.169.0",
25
25
  "node-opcua-constants": "2.157.0",
26
- "node-opcua-crypto": "5.3.3",
27
- "node-opcua-data-model": "2.168.0",
26
+ "node-opcua-crypto": "5.3.5",
27
+ "node-opcua-data-model": "2.169.0",
28
28
  "node-opcua-debug": "2.168.0",
29
- "node-opcua-file-transfer": "2.168.0",
29
+ "node-opcua-file-transfer": "2.169.0",
30
30
  "node-opcua-hostname": "2.167.0",
31
- "node-opcua-nodeid": "2.168.0",
32
- "node-opcua-pki": "6.12.0",
33
- "node-opcua-pseudo-session": "2.168.0",
34
- "node-opcua-secure-channel": "2.168.0",
35
- "node-opcua-server": "2.168.0",
36
- "node-opcua-service-translate-browse-path": "2.168.0",
37
- "node-opcua-status-code": "2.168.0",
38
- "node-opcua-types": "2.168.0",
39
- "node-opcua-variant": "2.168.0"
31
+ "node-opcua-nodeid": "2.169.0",
32
+ "node-opcua-pki": "6.13.0",
33
+ "node-opcua-pseudo-session": "2.169.0",
34
+ "node-opcua-secure-channel": "2.169.0",
35
+ "node-opcua-server": "2.169.0",
36
+ "node-opcua-service-translate-browse-path": "2.169.0",
37
+ "node-opcua-status-code": "2.169.0",
38
+ "node-opcua-types": "2.169.0",
39
+ "node-opcua-variant": "2.169.0"
40
40
  },
41
41
  "devDependencies": {
42
42
  "bcryptjs": "3.0.3",
43
- "node-opcua-client": "2.168.0",
44
- "node-opcua-data-value": "2.168.0",
45
- "node-opcua-leak-detector": "2.168.0",
43
+ "node-opcua-client": "2.169.0",
44
+ "node-opcua-data-value": "2.169.0",
45
+ "node-opcua-leak-detector": "2.169.0",
46
46
  "node-opcua-nodesets": "2.163.1"
47
47
  },
48
48
  "author": "Etienne Rossignon",
@@ -60,7 +60,7 @@
60
60
  "internet of things"
61
61
  ],
62
62
  "homepage": "http://node-opcua.github.io/",
63
- "gitHead": "653b6d6df801ca17298308089dee32e5b12102b6",
63
+ "gitHead": "82d570d3e95bea689cbbe30096279885c5282245",
64
64
  "files": [
65
65
  "dist",
66
66
  "source"
package/source/index.ts CHANGED
@@ -11,4 +11,5 @@ export * from "./server/promote_trust_list.js";
11
11
  export * from "./server/push_certificate_manager/subject_to_string.js";
12
12
  export * from "./server/push_certificate_manager_helpers.js";
13
13
  export * from "./server/push_certificate_manager_server_impl.js";
14
+ export * from "./server/trust_list_server.js";
14
15
  export * from "./standard_certificate_types.js";
@@ -93,6 +93,31 @@ export class FileTransactionManager {
93
93
  this.addFileOp(() => this.#moveFileWithBackupTracked(tempFilePath, destinationPath));
94
94
  }
95
95
 
96
+ /**
97
+ * Stages a file for deletion during the transaction.
98
+ *
99
+ * The file is backed up before removal so it can be restored
100
+ * if the transaction is rolled back. If the file does not
101
+ * exist at apply time the operation is silently skipped.
102
+ *
103
+ * @param filePath - absolute path of the file to remove
104
+ */
105
+ public stageFileRemoval(filePath: string): void {
106
+ this.addFileOp(async () => {
107
+ if (!fs.existsSync(filePath)) {
108
+ return;
109
+ }
110
+ // Create a backup before deleting so rollback can restore it
111
+ const tmpDir = await this.getTmpDir();
112
+ const uniqueFileName = `${crypto.randomBytes(16).toString("hex")}_backup.tmp`;
113
+ const backupPath = path.join(tmpDir, uniqueFileName);
114
+ this.#backupFiles.set(filePath, backupPath);
115
+
116
+ await _copyFile(filePath, backupPath);
117
+ await _deleteFile(filePath);
118
+ });
119
+ }
120
+
96
121
  public addFileOp(functor: Functor): void {
97
122
  this.#pendingFileOps.push(functor);
98
123
  }
@@ -9,9 +9,11 @@ import type { AddressSpace, UAServerConfiguration } from "node-opcua-address-spa
9
9
  import { assert } from "node-opcua-assert";
10
10
  import type { OPCUACertificateManager } from "node-opcua-certificate-manager";
11
11
  import { type ICertificateKeyPairProvider, invalidateCachedSecrets } from "node-opcua-common";
12
- import { checkDebugFlag, make_debugLog, make_errorLog } from "node-opcua-debug";
13
- import { invalidateServerCertificateCache, type OPCUAServer } from "node-opcua-server";
14
- import type { ApplicationDescriptionOptions } from "node-opcua-types";
12
+ import { type Certificate, split_der, exploreCertificateInfo } from "node-opcua-crypto/web";
13
+ import { checkDebugFlag, make_debugLog, make_errorLog, make_warningLog } from "node-opcua-debug";
14
+ import { invalidateServerCertificateCache, type OPCUAServer, type OPCUAServerEndPoint } from "node-opcua-server";
15
+ import { type StatusCode, StatusCodes } from "node-opcua-status-code";
16
+ import { type ApplicationDescriptionOptions, ServerState } from "node-opcua-types";
15
17
 
16
18
  import { installPushCertificateManagement } from "./push_certificate_manager_helpers.js";
17
19
  import type { ActionQueue, PushCertificateManagerServerImpl } from "./push_certificate_manager_server_impl.js";
@@ -19,6 +21,7 @@ import type { ActionQueue, PushCertificateManagerServerImpl } from "./push_certi
19
21
  const debugLog = make_debugLog("ServerConfiguration");
20
22
  const doDebug = checkDebugFlag("ServerConfiguration");
21
23
  const errorLog = make_errorLog("ServerConfiguration");
24
+ const warningLog = make_warningLog("ServerConfiguration");
22
25
 
23
26
  /** Relative path from cert manager root to the leaf certificate PEM. */
24
27
  const CERT_PEM_RELATIVE_PATH = "own/certs/certificate.pem";
@@ -157,4 +160,173 @@ export async function installPushCertificateManagementOnServer(server: OPCUAServ
157
160
  }
158
161
  });
159
162
  });
163
+
164
+ // ── Install NoConfiguration certificate relaxation ─────────
165
+ //
166
+ // When the server is in NoConfiguration state (awaiting GDS
167
+ // provisioning), relax certain certificate trust/CRL errors
168
+ // so that the GDS client can connect and provision the server.
169
+ //
170
+ // This hook is ONLY installed when push certificate management
171
+ // is active — bare servers are completely unaffected.
172
+ installCertificateRelaxationHook(server);
173
+ }
174
+
175
+ // ── Certificate relaxation for NoConfiguration state ──────────
176
+
177
+ /**
178
+ * Status codes that can be relaxed during NoConfiguration state.
179
+ *
180
+ * These represent "trust infrastructure not yet set up" situations
181
+ * (missing issuers, missing CRLs) — NOT security violations
182
+ * (revoked, expired, invalid).
183
+ *
184
+ * ### Why `BadCertificateUntrusted` MUST be included (SECURITY NOTE)
185
+ *
186
+ * In `NoConfiguration` the server's trust store is **empty** — no
187
+ * trusted certificates and no CRLs exist yet. A GDS client that
188
+ * connects to provision the server will inevitably present a
189
+ * certificate that is "untrusted" simply because nothing is trusted
190
+ * yet. Removing `BadCertificateUntrusted` from this list would make
191
+ * it impossible for the GDS to connect, breaking the entire push
192
+ * certificate management provisioning workflow (chicken-and-egg).
193
+ *
194
+ * **Security boundary:** this relaxation is ONLY active while the
195
+ * server state is `ServerState.NoConfiguration`. Once the server
196
+ * transitions to `Running` (after successful provisioning) the
197
+ * relaxation hook returns the original status code unchanged and
198
+ * normal strict certificate validation applies. The accepted
199
+ * certificate is auto-trusted (see `autoTrustCertificateChain`)
200
+ * so that subsequent connections succeed under normal validation.
201
+ *
202
+ * Errors that indicate an active security violation (revoked,
203
+ * expired, invalid signature, wrong usage) are **never** relaxed,
204
+ * even in `NoConfiguration`.
205
+ */
206
+ function isRelaxableCertificateError(statusCode: StatusCode): boolean {
207
+ return (
208
+ StatusCodes.BadCertificateUntrusted.equals(statusCode) ||
209
+ StatusCodes.BadCertificateRevocationUnknown.equals(statusCode) ||
210
+ StatusCodes.BadCertificateIssuerRevocationUnknown.equals(statusCode) ||
211
+ StatusCodes.BadCertificateChainIncomplete.equals(statusCode)
212
+ );
213
+ }
214
+
215
+ /**
216
+ * Auto-trust the client's leaf certificate during NoConfiguration.
217
+ *
218
+ * Only the **leaf** (chain[0]) is placed in the trusted store —
219
+ * this is sufficient for the GDS client to reconnect after the
220
+ * server transitions to Running (the PKI verifier short-circuits
221
+ * to `Good` when the leaf itself is explicitly trusted).
222
+ *
223
+ * Issuer (CA) certificates from the chain are added to the
224
+ * **issuers/** store for chain-building purposes, but they do
225
+ * NOT become trust anchors. This prevents a single provisioning
226
+ * connection from unintentionally granting trust to every
227
+ * certificate signed by the same CA.
228
+ */
229
+ async function autoTrustCertificateChain(
230
+ server: OPCUAServer,
231
+ certificate: Certificate
232
+ ): Promise<void> {
233
+ let chain: Certificate[];
234
+ try {
235
+ chain = split_der(certificate);
236
+ } catch (err) {
237
+ warningLog(
238
+ "[NoConfiguration] Cannot parse certificate chain for auto-trust:",
239
+ (err as Error).message
240
+ );
241
+ return;
242
+ }
243
+
244
+ const cm = server.serverCertificateManager;
245
+
246
+ for (let i = 0; i < chain.length; i++) {
247
+ const cert = chain[i];
248
+ // Validate the DER structure before persisting.
249
+ // Garbage data (e.g. zero-filled buffers) parses into tiny blobs
250
+ // that are not valid X.509 certificates.
251
+ try {
252
+ exploreCertificateInfo(cert);
253
+ } catch (err) {
254
+ warningLog(
255
+ "[NoConfiguration] Skipping invalid certificate in chain for auto-trust:",
256
+ (err as Error).message
257
+ );
258
+ continue;
259
+ }
260
+
261
+ if (i === 0) {
262
+ // Leaf certificate → trust explicitly
263
+ try {
264
+ await cm.trustCertificate(cert);
265
+ } catch (err) {
266
+ // ENOENT can happen if another concurrent call already
267
+ // moved the cert from rejected to trusted.
268
+ if ((err as Error & { code?: string }).code !== "ENOENT") {
269
+ warningLog(
270
+ "[NoConfiguration] Failed to auto-trust leaf certificate:",
271
+ (err as Error).message
272
+ );
273
+ }
274
+ }
275
+ } else {
276
+ // Issuer CA certificate → add to issuers/ (chain-building
277
+ // only, does NOT establish a trust anchor)
278
+ try {
279
+ await cm.addIssuer(cert);
280
+ } catch (err) {
281
+ warningLog(
282
+ "[NoConfiguration] Failed to add issuer certificate:",
283
+ (err as Error).message
284
+ );
285
+ }
286
+ }
287
+ }
288
+ }
289
+
290
+ /**
291
+ * Install the `onAdjustCertificateStatus` hook on every endpoint.
292
+ *
293
+ * When the server is in `ServerState.NoConfiguration`, the hook
294
+ * relaxes trust/CRL errors so that a GDS client with a valid
295
+ * full-chain certificate can connect and provision the server.
296
+ *
297
+ * The leaf certificate is auto-trusted so that after the server
298
+ * transitions to Running, the same client is accepted by normal
299
+ * validation. Issuer CAs are placed in `issuers/` for chain
300
+ * building but do not become trust anchors.
301
+ */
302
+ function installCertificateRelaxationHook(server: OPCUAServer): void {
303
+ const adjustCertificateStatus = async (
304
+ statusCode: StatusCode,
305
+ certificate: Certificate
306
+ ): Promise<StatusCode> => {
307
+ // Only relax in NoConfiguration state
308
+ if (server.engine.getServerState() !== ServerState.NoConfiguration) {
309
+ return statusCode;
310
+ }
311
+
312
+ // Only relax trust-infrastructure errors, NOT security errors
313
+ if (!isRelaxableCertificateError(statusCode)) {
314
+ return statusCode;
315
+ }
316
+
317
+ doDebug && warningLog(
318
+ `[NoConfiguration] Relaxing certificate check:`,
319
+ `${statusCode.toString()} → Good`,
320
+ "(server is awaiting GDS provisioning)"
321
+ );
322
+
323
+ // Auto-trust the leaf certificate; issuer CAs go to issuers/
324
+ await autoTrustCertificateChain(server, certificate);
325
+
326
+ return StatusCodes.Good;
327
+ };
328
+
329
+ for (const endpoint of server.endpoints) {
330
+ (endpoint as OPCUAServerEndPoint).onAdjustCertificateStatus = adjustCertificateStatus;
331
+ }
160
332
  }
@@ -2,6 +2,9 @@
2
2
  * @module node-opcua-server-configuration
3
3
  */
4
4
 
5
+ import fs from "node:fs";
6
+ import path from "node:path";
7
+
5
8
  import { fs as MemFs } from "memfs";
6
9
 
7
10
  import type {
@@ -97,6 +100,151 @@ function updateLastUpdateTime(trustList: UATrustList): void {
97
100
  }
98
101
  }
99
102
 
103
+ /**
104
+ * Scan the PKI store folders (trusted/certs, trusted/crl, issuers/certs,
105
+ * issuers/crl) and return the most recent modification time across all
106
+ * files. Returns null if no files are found.
107
+ *
108
+ * Uses async fs.promises to avoid blocking the event loop on startup
109
+ * when PKI directories are large or on slow filesystems.
110
+ */
111
+ async function getNewestMtimeFromPkiStore(
112
+ cm: OPCUACertificateManager,
113
+ isAborted?: () => boolean
114
+ ): Promise<Date | null> {
115
+ const dirs = [cm.trustedFolder, cm.crlFolder, cm.issuersCertFolder, cm.issuersCrlFolder];
116
+ let newest: Date | null = null;
117
+
118
+ for (const dir of dirs) {
119
+ if (isAborted?.()) break;
120
+
121
+ try {
122
+ await fs.promises.access(dir);
123
+ } catch {
124
+ continue;
125
+ }
126
+ let entries: string[];
127
+ try {
128
+ entries = await fs.promises.readdir(dir);
129
+ } catch {
130
+ continue;
131
+ }
132
+
133
+ // Process stats sequentially to avoid threadpool exhaustion
134
+ // and event-loop lag when directories have thousands of files.
135
+ for (const entry of entries) {
136
+ if (isAborted?.()) return null;
137
+ try {
138
+ const stat = await fs.promises.stat(path.join(dir, entry));
139
+ if (stat.isFile() && (!newest || stat.mtime > newest)) {
140
+ newest = stat.mtime;
141
+ }
142
+ } catch {
143
+ // skip unreadable entries
144
+ }
145
+ }
146
+ }
147
+ return newest;
148
+ }
149
+
150
+ /**
151
+ * Initialize the LastUpdateTime property from the PKI store's
152
+ * filesystem timestamps. This avoids displaying MinDate
153
+ * (0001-01-01T00:00:00Z) when the trust store already contains
154
+ * certificates or CRLs (e.g. populated by selfOnboard or addIssuer).
155
+ *
156
+ * Also subscribes to the CertificateManager's filesystem watcher
157
+ * events (certificateAdded, certificateRemoved, certificateChange,
158
+ * crlAdded, crlRemoved) so that LastUpdateTime stays current even
159
+ * when the trust store is modified externally (e.g. manual file
160
+ * copy, programmatic addIssuer/trustCertificate calls).
161
+ *
162
+ * Listeners are installed at most once per TrustList node
163
+ * (guarded by $$listenersInstalled) and are removed via
164
+ * addressSpace.registerShutdownTask to prevent leaks.
165
+ */
166
+ async function _initializeLastUpdateTimeFromFilesystem(trustList: UATrustListEx): Promise<void> {
167
+ const cm = trustList.$$certificateManager;
168
+ if (!cm) return;
169
+
170
+ if (trustList.$$initaliseMTimePromise) {
171
+ return await trustList.$$initaliseMTimePromise;
172
+ }
173
+
174
+ trustList.$$initaliseMTimePromise = (async () => {
175
+ const startTime = Date.now();
176
+ let isAborted = false;
177
+
178
+ // Note: Removed abortHandler from registerShutdownTask because AddressSpace
179
+ // does not have an unregister mechanism, which causes `_shutdownTasks` to leak
180
+ // continuously if promoteTrustList is called multiple times.
181
+ // Sequential scanning is fast enough that it won't block shutdown significantly.
182
+
183
+ try {
184
+ const lastUpdateTimeNode = trustList.lastUpdateTime;
185
+ if (!lastUpdateTimeNode) return;
186
+
187
+ // Seed the initial timestamp from the filesystem only when
188
+ // the current value is still unset (MinDate). The event
189
+ // listeners below must always be installed regardless, so we
190
+ // must NOT return early here.
191
+ const currentValue = lastUpdateTimeNode.readValue().value.value as Date | undefined;
192
+ if (!currentValue || currentValue.getTime() <= 0) {
193
+ const newest = await getNewestMtimeFromPkiStore(cm, () => isAborted);
194
+ if (isAborted) {
195
+ console.log(`[node-opcua] _initializeLastUpdateTimeFromFilesystem aborted for ${trustList.browseName.toString()}`);
196
+ return;
197
+ }
198
+ if (newest) {
199
+ lastUpdateTimeNode.setValueFromSource({
200
+ dataType: DataType.DateTime,
201
+ value: newest
202
+ });
203
+ doDebug && debugLog("Initialized LastUpdateTime from filesystem:", newest.toISOString());
204
+ }
205
+ }
206
+
207
+ // Guard: install listeners at most once per TrustList node
208
+ // to prevent duplicate handler invocation on re-promotion.
209
+ if (trustList.$$listenersInstalled) {
210
+ return;
211
+ }
212
+ trustList.$$listenersInstalled = true;
213
+
214
+ // Subscribe to CertificateManager filesystem watcher events
215
+ // so LastUpdateTime stays current when the store is modified
216
+ // outside of OPC UA methods (e.g. addIssuer, trustCertificate,
217
+ // or manual file operations).
218
+ const _updateNow = () => {
219
+ updateLastUpdateTime(trustList);
220
+ };
221
+
222
+ const events = ["certificateAdded", "certificateRemoved", "certificateChange", "crlAdded", "crlRemoved"] as const;
223
+ for (const event of events) {
224
+ cm.on(event, _updateNow);
225
+ }
226
+
227
+ // Clean up listeners when the address space shuts down
228
+ // to avoid MaxListenersExceededWarning and memory leaks.
229
+ // Guard: addressSpace may be null if dispose() was called
230
+ // concurrently (e.g. fast test teardown).
231
+ if (trustList.addressSpace) {
232
+ trustList.addressSpace.registerShutdownTask(() => {
233
+ for (const event of events) {
234
+ cm.removeListener(event, _updateNow);
235
+ }
236
+ trustList.$$listenersInstalled = false;
237
+ });
238
+ }
239
+
240
+ console.log(`[node-opcua] _initializeLastUpdateTimeFromFilesystem took ${Date.now() - startTime}ms for ${trustList.browseName.toString()}`);
241
+ } finally {
242
+ trustList.$$initaliseMTimePromise = undefined;
243
+ }
244
+ })();
245
+ return await trustList.$$initaliseMTimePromise;
246
+ }
247
+
100
248
  interface UAMethodEx extends UAMethod {
101
249
  _asyncExecutionFunction?: MethodFunctor;
102
250
  }
@@ -104,6 +252,8 @@ interface UATrustListEx extends UATrustList {
104
252
  $$certificateManager: OPCUACertificateManager;
105
253
  $$filename: string;
106
254
  $$openedForWrite: boolean;
255
+ $$listenersInstalled: boolean;
256
+ $$initaliseMTimePromise?: Promise<void>;
107
257
  }
108
258
 
109
259
  async function applyTrustListChanges(cm: OPCUACertificateManager, trustListData: TrustListDataType): Promise<StatusCode> {
@@ -445,10 +595,18 @@ async function _removeCertificate(
445
595
  let counter = 0;
446
596
 
447
597
  export async function promoteTrustList(trustList: UATrustList) {
598
+ const trustListEx = trustList as UATrustListEx & { $$promoted?: boolean };
599
+
600
+ // Prevent double-binding if called multiple times testing
601
+ if (trustListEx.$$promoted) {
602
+ await _initializeLastUpdateTimeFromFilesystem(trustListEx);
603
+ return;
604
+ }
605
+ trustListEx.$$promoted = true;
606
+
448
607
  const filename = `/tmpFile${counter}`;
449
608
  counter += 1;
450
609
 
451
- const trustListEx = trustList as UATrustListEx;
452
610
  // Store filename for use in _closeAndUpdate
453
611
  trustListEx.$$filename = filename;
454
612
  // Initialize write lock flag
@@ -541,6 +699,17 @@ export async function promoteTrustList(trustList: UATrustList) {
541
699
  addCertificate.bindMethod(_addCertificate);
542
700
  removeCertificate.bindMethod(_removeCertificate);
543
701
 
702
+ function _closeCallback(
703
+ this: UAMethod,
704
+ inputArgs: Variant[],
705
+ context: ISessionContext,
706
+ callback: CallbackT<CallMethodResultOptions>
707
+ ) {
708
+ trustListEx.$$openedForWrite = false;
709
+ _close_asyncExecutionFunction.call(this, inputArgs, context, callback);
710
+ }
711
+ close.bindMethod(_closeCallback);
712
+
544
713
  // Wrapper to pass the underlying close method to _closeAndUpdate
545
714
  closeAndUpdate?.bindMethod(async function (
546
715
  this: UAMethod,
@@ -556,12 +725,17 @@ export async function promoteTrustList(trustList: UATrustList) {
556
725
  return;
557
726
  }
558
727
  fileType.open?.bindMethod(_openCallback);
728
+ fileType.close?.bindMethod(_closeCallback);
559
729
  fileType.addCertificate.bindMethod(_addCertificate);
560
730
  fileType.removeCertificate.bindMethod(_removeCertificate);
561
731
  fileType.openWithMasks?.bindMethod(_openWithMaskCallback);
562
732
  fileType.closeAndUpdate?.bindMethod(_closeAndUpdate);
563
733
  }
564
734
  install_method_handle_on_TrustListType(trustList.addressSpace);
735
+
736
+ // Initialize LastUpdateTime from the PKI store's filesystem timestamps
737
+ // so it doesn't show MinDate (0001-01-01) when files already exist.
738
+ await _initializeLastUpdateTimeFromFilesystem(trustListEx);
565
739
  }
566
740
 
567
741
  export function installAccessRestrictionOnTrustList(trustList: UAVariable | UAObject) {