node-opcua-server-configuration 2.167.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.
Files changed (34) hide show
  1. package/dist/clientTools/push_certificate_management_client.js.map +1 -1
  2. package/dist/index.d.ts +2 -1
  3. package/dist/index.js +2 -1
  4. package/dist/index.js.map +1 -1
  5. package/dist/server/file_transaction_manager.d.ts +10 -0
  6. package/dist/server/file_transaction_manager.js +23 -0
  7. package/dist/server/file_transaction_manager.js.map +1 -1
  8. package/dist/server/{install_push_certitifate_management.d.ts → install_push_certificate_management.d.ts} +3 -2
  9. package/dist/server/install_push_certificate_management.js +263 -0
  10. package/dist/server/install_push_certificate_management.js.map +1 -0
  11. package/dist/server/promote_trust_list.js +154 -3
  12. package/dist/server/promote_trust_list.js.map +1 -1
  13. package/dist/server/push_certificate_manager/create_signing_request.js +19 -13
  14. package/dist/server/push_certificate_manager/create_signing_request.js.map +1 -1
  15. package/dist/server/push_certificate_manager/update_certificate.js +21 -9
  16. package/dist/server/push_certificate_manager/update_certificate.js.map +1 -1
  17. package/dist/server/push_certificate_manager_helpers.js.map +1 -1
  18. package/dist/server/push_certificate_manager_server_impl.js.map +1 -1
  19. package/dist/server/trust_list_server.js +5 -0
  20. package/dist/server/trust_list_server.js.map +1 -1
  21. package/package.json +24 -26
  22. package/source/clientTools/push_certificate_management_client.ts +4 -8
  23. package/source/index.ts +2 -1
  24. package/source/server/file_transaction_manager.ts +25 -0
  25. package/source/server/install_push_certificate_management.ts +332 -0
  26. package/source/server/promote_trust_list.ts +185 -9
  27. package/source/server/push_certificate_manager/create_signing_request.ts +27 -17
  28. package/source/server/push_certificate_manager/update_certificate.ts +25 -8
  29. package/source/server/push_certificate_manager_helpers.ts +1 -1
  30. package/source/server/push_certificate_manager_server_impl.ts +3 -9
  31. package/source/server/trust_list_server.ts +7 -2
  32. package/dist/server/install_push_certitifate_management.js +0 -144
  33. package/dist/server/install_push_certitifate_management.js.map +0 -1
  34. package/source/server/install_push_certitifate_management.ts +0 -193
@@ -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 {
@@ -25,11 +28,10 @@ import { VerificationStatus } from "node-opcua-pki";
25
28
  import { type CallbackT, type StatusCode, StatusCodes } from "node-opcua-status-code";
26
29
  import { type CallMethodResultOptions, TrustListDataType } from "node-opcua-types";
27
30
  import { DataType, Variant } from "node-opcua-variant";
31
+ import type { PushCertificateManagerServerImpl } from "./push_certificate_manager_server_impl.js";
28
32
  import { rolePermissionAdminOnly } from "./roles_and_permissions.js";
29
-
30
33
  import { hasEncryptedChannel, hasExpectedUserAccess } from "./tools.js";
31
34
  import { TrustListMasks, writeTrustList } from "./trust_list_server.js";
32
- import type { PushCertificateManagerServerImpl } from "./push_certificate_manager_server_impl.js";
33
35
 
34
36
  const debugLog = make_debugLog("ServerConfiguration");
35
37
  const doDebug = checkDebugFlag("ServerConfiguration");
@@ -46,13 +48,14 @@ function emitTrustListUpdated(trustList: UATrustList): void {
46
48
  const certificateGroup = trustList.parent;
47
49
  const groupName = certificateGroup?.browseName?.name ?? "Unknown";
48
50
 
49
- const serverConfiguration = trustList.addressSpace.rootFolder
50
- .objects.server.getChildByName("ServerConfiguration");
51
+ const serverConfiguration = trustList.addressSpace.rootFolder.objects.server.getChildByName("ServerConfiguration");
51
52
  if (!serverConfiguration) return;
52
53
 
53
- const pushManager = (serverConfiguration as unknown as {
54
- $pushCertificateManager?: PushCertificateManagerServerImpl;
55
- }).$pushCertificateManager;
54
+ const pushManager = (
55
+ serverConfiguration as unknown as {
56
+ $pushCertificateManager?: PushCertificateManagerServerImpl;
57
+ }
58
+ ).$pushCertificateManager;
56
59
 
57
60
  if (pushManager) {
58
61
  pushManager.emit("trustListUpdated", groupName);
@@ -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
@@ -507,7 +665,9 @@ export async function promoteTrustList(trustList: UATrustList) {
507
665
  callback(err, { statusCode: StatusCodes.BadInternalError });
508
666
  });
509
667
  } else {
510
- warningLog("certificateManager is not defined on trustlist do something to update the trust list document before we open it");
668
+ warningLog(
669
+ "certificateManager is not defined on trustlist do something to update the trust list document before we open it"
670
+ );
511
671
  return _open_asyncExecutionFunction.call(this, inputArgs, context, callback);
512
672
  }
513
673
  }
@@ -539,6 +699,17 @@ export async function promoteTrustList(trustList: UATrustList) {
539
699
  addCertificate.bindMethod(_addCertificate);
540
700
  removeCertificate.bindMethod(_removeCertificate);
541
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
+
542
713
  // Wrapper to pass the underlying close method to _closeAndUpdate
543
714
  closeAndUpdate?.bindMethod(async function (
544
715
  this: UAMethod,
@@ -554,12 +725,17 @@ export async function promoteTrustList(trustList: UATrustList) {
554
725
  return;
555
726
  }
556
727
  fileType.open?.bindMethod(_openCallback);
728
+ fileType.close?.bindMethod(_closeCallback);
557
729
  fileType.addCertificate.bindMethod(_addCertificate);
558
730
  fileType.removeCertificate.bindMethod(_removeCertificate);
559
731
  fileType.openWithMasks?.bindMethod(_openWithMaskCallback);
560
732
  fileType.closeAndUpdate?.bindMethod(_closeAndUpdate);
561
733
  }
562
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);
563
739
  }
564
740
 
565
741
  export function installAccessRestrictionOnTrustList(trustList: UAVariable | UAObject) {
@@ -2,7 +2,7 @@ import crypto from "node:crypto";
2
2
  import fs from "node:fs";
3
3
  import path from "node:path";
4
4
  import { CertificateManager } from "node-opcua-certificate-manager";
5
- import { convertPEMtoDER, exploreCertificate, readCertificateChain, type DirectoryName } from "node-opcua-crypto";
5
+ import { convertPEMtoDER, type DirectoryName, exploreCertificate, readCertificateChain } from "node-opcua-crypto";
6
6
  import { checkDebugFlag, make_debugLog, make_errorLog, make_warningLog } from "node-opcua-debug";
7
7
  import { NodeId, resolveNodeId, sameNodeId } from "node-opcua-nodeid";
8
8
  import type { SubjectOptions } from "node-opcua-pki";
@@ -118,20 +118,30 @@ export async function executeCreateSigningRequest(
118
118
  });
119
119
  }
120
120
 
121
- const options = {
122
- applicationUri: serverImpl.applicationUri,
123
- subject: subjectName
124
- };
125
-
126
- const activeCertificateManager = serverImpl.tmpCertificateManager || certificateManager;
127
-
128
- await activeCertificateManager.initialize();
129
- const csrFile = await activeCertificateManager.createCertificateRequest(options);
130
- const csrPEM = await fs.promises.readFile(csrFile, "utf8");
131
- const certificateSigningRequest = convertPEMtoDER(csrPEM);
132
-
133
- return {
134
- certificateSigningRequest,
135
- statusCode: StatusCodes.Good
136
- };
121
+ try {
122
+ const options = {
123
+ applicationUri: serverImpl.applicationUri,
124
+ subject: subjectName
125
+ };
126
+
127
+ const activeCertificateManager = serverImpl.tmpCertificateManager || certificateManager;
128
+
129
+ await activeCertificateManager.initialize();
130
+ const csrFile = await activeCertificateManager.createCertificateRequest(options);
131
+ const csrPEM = await fs.promises.readFile(csrFile, "utf8");
132
+ const certificateSigningRequest = convertPEMtoDER(csrPEM);
133
+
134
+ return {
135
+ certificateSigningRequest,
136
+ statusCode: StatusCodes.Good
137
+ };
138
+ } catch (err) {
139
+ errorLog(
140
+ "CreateSigningRequest failed during CSR generation:",
141
+ (err as Error).message,
142
+ "subject=", subjectName,
143
+ "applicationUri=", serverImpl.applicationUri
144
+ );
145
+ return { statusCode: StatusCodes.BadInternalError };
146
+ }
137
147
  }
@@ -3,11 +3,13 @@ import path from "node:path";
3
3
  import { assert } from "node-opcua-assert";
4
4
  import type { ByteString } from "node-opcua-basic-types";
5
5
  import type { CertificateManager, OPCUACertificateManager } from "node-opcua-certificate-manager";
6
- import { readPrivateKey, exploreCertificate } from "node-opcua-crypto";
6
+ import { exploreCertificate, readPrivateKey } from "node-opcua-crypto";
7
7
  import {
8
8
  certificateMatchesPrivateKey,
9
+ type Certificate,
9
10
  coercePEMorDerToPrivateKey,
10
11
  coercePrivateKeyPem,
12
+ combine_der,
11
13
  makeSHA1Thumbprint,
12
14
  type PrivateKey,
13
15
  toPem
@@ -19,7 +21,7 @@ import { StatusCodes } from "node-opcua-status-code";
19
21
  import type { UpdateCertificateResult } from "../../push_certificate_manager.js";
20
22
  import { validateCertificateAndChain } from "../certificate_validation.js";
21
23
  import type { PushCertificateManagerInternalContext } from "./internal_context.js";
22
- import { resolveCertificateGroupContext, validateCertificateType, findCertificateGroupName } from "./util.js";
24
+ import { findCertificateGroupName, resolveCertificateGroupContext, validateCertificateType } from "./util.js";
23
25
 
24
26
  const warningLog = make_warningLog("ServerConfiguration");
25
27
  const debugLog = make_debugLog("ServerConfiguration");
@@ -52,18 +54,33 @@ async function preInstallIssuerCertificates(
52
54
  }
53
55
 
54
56
  // Helper: Stage main certificate to temporary files
57
+ //
58
+ // The PEM file contains the full chain (leaf + issuer CAs) so that
59
+ // getCertificateChain() returns the complete chain. This allows
60
+ // OpenSecureChannel and CreateSession to present the full chain to
61
+ // connecting clients, enabling them to build and verify the trust
62
+ // path without needing all CA certificates pre-installed.
55
63
  async function preInstallCertificate(
56
64
  serverImpl: PushCertificateManagerInternalContext,
57
65
  certificateManager: CertificateManager,
58
- certificate: Buffer
66
+ certificate: Certificate,
67
+ issuerCertificates?: Certificate[]
59
68
  ): Promise<void> {
60
69
  const certFolder = certificateManager.ownCertFolder;
61
- const destDER = path.join(certFolder, "certificate.der");
62
70
  const destPEM = path.join(certFolder, "certificate.pem");
63
- const certificatePEM = toPem(certificate, "CERTIFICATE");
64
71
 
65
- await serverImpl.fileTransactionManager.stageFile(destDER, certificate);
72
+ // Build the full chain: leaf first, then issuer CAs
73
+ const certificateChain: Certificate[] = [certificate, ...(issuerCertificates ?? [])];
74
+ const certificatePEM = toPem(combine_der(certificateChain), "CERTIFICATE");
75
+
66
76
  await serverImpl.fileTransactionManager.stageFile(destPEM, certificatePEM, "utf-8");
77
+
78
+ // Clean up any leftover certificate.der from previous installations.
79
+ // Since DER is no longer written, a stale file would be misleading.
80
+ const legacyDER = path.join(certFolder, "certificate.der");
81
+ if (fs.existsSync(legacyDER)) {
82
+ serverImpl.fileTransactionManager.stageFileRemoval(legacyDER);
83
+ }
67
84
  }
68
85
 
69
86
  // Helper: Stage private key to temporary file
@@ -164,7 +181,7 @@ export async function executeUpdateCertificate(
164
181
  }
165
182
 
166
183
  await preInstallIssuerCertificates(serverImpl, certificateManager, issuerCertificates);
167
- await preInstallCertificate(serverImpl, certificateManager, certificate);
184
+ await preInstallCertificate(serverImpl, certificateManager, certificate, issuerCertificates);
168
185
  serverImpl.emit("certificateUpdated", certificateGroupId, certificate);
169
186
  return { statusCode: StatusCodes.Good, applyChangesRequired: true };
170
187
  } else {
@@ -203,7 +220,7 @@ export async function executeUpdateCertificate(
203
220
 
204
221
  await preInstallPrivateKey(serverImpl, certificateManager, privateKeyFormat, tempPrivateKey);
205
222
  await preInstallIssuerCertificates(serverImpl, certificateManager, issuerCertificates);
206
- await preInstallCertificate(serverImpl, certificateManager, certificate);
223
+ await preInstallCertificate(serverImpl, certificateManager, certificate, issuerCertificates);
207
224
 
208
225
  serverImpl.emit("certificateUpdated", certificateGroupId, certificate);
209
226
  return { statusCode: StatusCodes.Good, applyChangesRequired: true };
@@ -17,7 +17,7 @@ import {
17
17
  import { EventNotifierFlags, type UAObject, type UAVariable } from "node-opcua-address-space-base";
18
18
  import type { ByteString, UAString } from "node-opcua-basic-types";
19
19
  import { ObjectIds, ObjectTypeIds } from "node-opcua-constants";
20
- import { readCertificateChainAsync, type Certificate } from "node-opcua-crypto";
20
+ import { type Certificate, readCertificateChainAsync } from "node-opcua-crypto";
21
21
  import { AccessRestrictionsFlag, BrowseDirection, coerceQualifiedName, NodeClass } from "node-opcua-data-model";
22
22
  import { make_debugLog, make_errorLog, make_warningLog } from "node-opcua-debug";
23
23
  import { NodeId, resolveNodeId } from "node-opcua-nodeid";
@@ -2,8 +2,8 @@
2
2
  * @module node-opcua-server-configuration-server
3
3
  */
4
4
  import { EventEmitter } from "node:events";
5
- import { assert } from "node-opcua-assert";
6
5
  import type { ISessionContext } from "node-opcua-address-space-base";
6
+ import { assert } from "node-opcua-assert";
7
7
  import type { ByteString } from "node-opcua-basic-types";
8
8
  import { CertificateManager } from "node-opcua-certificate-manager";
9
9
  import { make_errorLog } from "node-opcua-debug";
@@ -70,10 +70,7 @@ export class PushCertificateManagerServerImpl extends EventEmitter implements Pu
70
70
  public applicationUri: string;
71
71
 
72
72
  // ── typed event helpers ──────────────────────────────────────────
73
- public on<K extends keyof PushCertificateManagerEvents>(
74
- event: K,
75
- listener: PushCertificateManagerEvents[K]
76
- ): this;
73
+ public on<K extends keyof PushCertificateManagerEvents>(event: K, listener: PushCertificateManagerEvents[K]): this;
77
74
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
78
75
  public on(event: string | symbol, listener: (...args: any[]) => void): this;
79
76
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
@@ -81,10 +78,7 @@ export class PushCertificateManagerServerImpl extends EventEmitter implements Pu
81
78
  return super.on(event, listener);
82
79
  }
83
80
 
84
- public once<K extends keyof PushCertificateManagerEvents>(
85
- event: K,
86
- listener: PushCertificateManagerEvents[K]
87
- ): this;
81
+ public once<K extends keyof PushCertificateManagerEvents>(event: K, listener: PushCertificateManagerEvents[K]): this;
88
82
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
89
83
  public once(event: string | symbol, listener: (...args: any[]) => void): this;
90
84
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
@@ -11,8 +11,8 @@ const errorLog = make_errorLog("TrustListServer");
11
11
 
12
12
  /**
13
13
  * Read all certificate (chains) and CRLs in a folder
14
- * @param folder
15
- * @returns
14
+ * @param folder
15
+ * @returns
16
16
  */
17
17
  async function readAll(folder: string): Promise<Buffer[]> {
18
18
  const results: Buffer[] = [];
@@ -22,6 +22,11 @@ async function readAll(folder: string): Promise<Buffer[]> {
22
22
  const ext = path.extname(file);
23
23
  if (ext === ".der" || ext === ".pem") {
24
24
  const chain = await readCertificateChainAsync(file);
25
+ // Return the full chain as a single ByteString.
26
+ // When addTrustedCertificateFromChain stores a chain-on-disk
27
+ // (leaf + issuer CAs in a single PEM), we preserve that
28
+ // chain so clients reading the TrustList can use it for
29
+ // chain-building.
25
30
  const concatenated = Buffer.concat(chain);
26
31
  results.push(concatenated);
27
32
  } else if (ext === ".crl") {
@@ -1,144 +0,0 @@
1
- /**
2
- * @module node-opcua-server-configuration-server
3
- */
4
- import fs from "node:fs";
5
- import path from "node:path";
6
- import chalk from "chalk";
7
- import { assert } from "node-opcua-assert";
8
- import { invalidateCachedSecrets } from "node-opcua-common";
9
- import { readPrivateKey } from "node-opcua-crypto";
10
- import { combine_der, convertPEMtoDER, split_der } from "node-opcua-crypto/web";
11
- import { checkDebugFlag, make_debugLog, make_errorLog } from "node-opcua-debug";
12
- import { getFullyQualifiedDomainName, getIpAddresses } from "node-opcua-hostname";
13
- import { installPushCertificateManagement } from "./push_certificate_manager_helpers.js";
14
- const debugLog = make_debugLog("ServerConfiguration");
15
- const errorLog = make_errorLog("ServerConfiguration");
16
- const doDebug = checkDebugFlag("ServerConfiguration");
17
- function getCertificateChainEP() {
18
- const certificateFile = path.join(this.certificateManager.rootDir, "own/certs/certificate.pem");
19
- const certificatePEM = fs.readFileSync(certificateFile, "utf8");
20
- return split_der(convertPEMtoDER(certificatePEM));
21
- }
22
- function getPrivateKeyEP() {
23
- return readPrivateKey(this.certificateManager.privateKey);
24
- }
25
- async function onCertificateAboutToChange(server) {
26
- doDebug && debugLog(chalk.yellow(" onCertificateAboutToChange => Suspending End points"));
27
- await server.suspendEndPoints();
28
- doDebug && debugLog(chalk.yellow(" onCertificateAboutToChange => End points suspended"));
29
- }
30
- /**
31
- * onCertificateChange is called when the serverConfiguration notifies
32
- * that the server certificate and/or private key has changed.
33
- *
34
- * This function invalidates the cached secrets so that the next
35
- * getCertificate() / getPrivateKey() call re-reads from disk,
36
- * then shuts down all channels and resumes endpoints.
37
- *
38
- * @param server
39
- */
40
- async function onCertificateChange(server) {
41
- doDebug && debugLog("on CertificateChanged");
42
- // Invalidate the cached certificate chain and private key.
43
- // The SecretHolder will re-read from disk on next access.
44
- invalidateCachedSecrets(server);
45
- setTimeout(async () => {
46
- try {
47
- doDebug && debugLog(chalk.yellow(" onCertificateChange => shutting down channels"));
48
- await server.shutdownChannels();
49
- doDebug && debugLog(chalk.yellow(" onCertificateChange => channels shut down"));
50
- doDebug && debugLog(chalk.yellow(" onCertificateChange => resuming end points"));
51
- await server.resumeEndPoints();
52
- doDebug && debugLog(chalk.yellow(" onCertificateChange => end points resumed"));
53
- debugLog(chalk.yellow("channels have been closed -> client should reconnect "));
54
- }
55
- catch (err) {
56
- errorLog("Error in CertificateChanged handler ", err.message);
57
- debugLog("err = ", err);
58
- }
59
- }, 2000);
60
- }
61
- /**
62
- * Install push certificate management on the server.
63
- *
64
- * This redirects `getCertificate`, `getCertificateChain` and
65
- * `getPrivateKey` to read from the serverCertificateManager's
66
- * PEM files, and wires up the push certificate management
67
- * address-space nodes.
68
- */
69
- async function install() {
70
- doDebug && debugLog("install push certificate management", this.serverCertificateManager.rootDir);
71
- Object.defineProperty(this, "privateKeyFile", {
72
- get: () => this.serverCertificateManager.privateKey,
73
- configurable: true
74
- });
75
- Object.defineProperty(this, "certificateFile", {
76
- get: () => path.join(this.serverCertificateManager.rootDir, "own/certs/certificate.pem"),
77
- configurable: true
78
- });
79
- const certificateFile = this.certificateFile;
80
- if (!fs.existsSync(certificateFile)) {
81
- // this is the first time server is launched
82
- // let's create a default self signed certificate with limited validity
83
- const fqdn = getFullyQualifiedDomainName();
84
- const ipAddresses = getIpAddresses();
85
- const applicationUri = (this.serverInfo ? this.serverInfo.applicationUri : null) || "uri:MISSING";
86
- const options = {
87
- applicationUri,
88
- dns: [fqdn],
89
- ip: ipAddresses,
90
- subject: `/CN=${applicationUri};/L=Paris`,
91
- startDate: new Date(),
92
- validity: 365 * 5, // five years
93
- outputFile: certificateFile
94
- };
95
- doDebug && debugLog("creating self signed certificate", options);
96
- await this.serverCertificateManager.createSelfSignedCertificate(options);
97
- }
98
- // Invalidate any previously cached secrets so that
99
- // getCertificateChain() / getPrivateKey() will re-read from disk.
100
- invalidateCachedSecrets(this);
101
- }
102
- export async function installPushCertificateManagementOnServer(server) {
103
- if (!server.engine || !server.engine.addressSpace) {
104
- throw new Error("Server must have a valid address space." +
105
- "you need to call installPushCertificateManagementOnServer after server has been initialized");
106
- }
107
- await install.call(server);
108
- for (const endpoint of server.endpoints) {
109
- const endpointPriv = endpoint;
110
- endpointPriv._certificateChain = null;
111
- endpointPriv._privateKey = null;
112
- endpoint.getCertificateChain = getCertificateChainEP;
113
- endpoint.getPrivateKey = getPrivateKeyEP;
114
- for (const e of endpoint.endpointDescriptions()) {
115
- Object.defineProperty(e, "serverCertificate", {
116
- get: () => combine_der(endpoint.getCertificateChain()),
117
- configurable: true
118
- });
119
- }
120
- }
121
- await installPushCertificateManagement(server.engine.addressSpace, {
122
- applicationGroup: server.serverCertificateManager,
123
- userTokenGroup: server.userCertificateManager,
124
- applicationUri: server.serverInfo.applicationUri || "InvalidURI"
125
- });
126
- const serverConfiguration = server.engine.addressSpace.rootFolder.objects.server.getChildByName("ServerConfiguration");
127
- const serverConfigurationPriv = serverConfiguration;
128
- assert(serverConfigurationPriv.$pushCertificateManager);
129
- serverConfigurationPriv.$pushCertificateManager.on("CertificateAboutToChange", (actionQueue) => {
130
- actionQueue.push(async () => {
131
- doDebug && debugLog("CertificateAboutToChange Event received");
132
- await onCertificateAboutToChange(server);
133
- doDebug && debugLog("CertificateAboutToChange Event processed");
134
- });
135
- });
136
- serverConfigurationPriv.$pushCertificateManager.on("CertificateChanged", (actionQueue) => {
137
- actionQueue.push(async () => {
138
- doDebug && debugLog("CertificateChanged Event received");
139
- await onCertificateChange(server);
140
- doDebug && debugLog("CertificateChanged Event processed");
141
- });
142
- });
143
- }
144
- //# sourceMappingURL=install_push_certitifate_management.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"install_push_certitifate_management.js","sourceRoot":"","sources":["../../source/server/install_push_certitifate_management.ts"],"names":[],"mappings":"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"}