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.
- package/dist/index.d.ts +1 -0
- package/dist/index.js +1 -0
- package/dist/index.js.map +1 -1
- package/dist/server/file_transaction_manager.d.ts +10 -0
- package/dist/server/file_transaction_manager.js +23 -0
- package/dist/server/file_transaction_manager.js.map +1 -1
- package/dist/server/install_push_certificate_management.d.ts +1 -1
- package/dist/server/install_push_certificate_management.js +142 -1
- package/dist/server/install_push_certificate_management.js.map +1 -1
- package/dist/server/promote_trust_list.js +153 -1
- package/dist/server/promote_trust_list.js.map +1 -1
- package/dist/server/push_certificate_manager/create_signing_request.js +19 -13
- package/dist/server/push_certificate_manager/create_signing_request.js.map +1 -1
- package/dist/server/push_certificate_manager/update_certificate.js +19 -7
- package/dist/server/push_certificate_manager/update_certificate.js.map +1 -1
- package/dist/server/trust_list_server.js +5 -0
- package/dist/server/trust_list_server.js.map +1 -1
- package/package.json +23 -23
- package/source/index.ts +1 -0
- package/source/server/file_transaction_manager.ts +25 -0
- package/source/server/install_push_certificate_management.ts +175 -3
- package/source/server/promote_trust_list.ts +175 -1
- package/source/server/push_certificate_manager/create_signing_request.ts +26 -16
- package/source/server/push_certificate_manager/update_certificate.ts +23 -6
- package/source/server/trust_list_server.ts +5 -0
|
@@ -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
|
-
|
|
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
|
-
|
|
36
|
-
|
|
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":"AAAA,OAAO,EAAE,MAAM,SAAS,CAAC;AACzB,OAAO,IAAI,MAAM,WAAW,CAAC;AAC7B,OAAO,EAAE,MAAM,EAAE,MAAM,mBAAmB,CAAC;AAG3C,OAAO,EAAE,kBAAkB,EAAE,cAAc,EAAE,MAAM,mBAAmB,CAAC;AACvE,OAAO,EACH,4BAA4B,
|
|
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.
|
|
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.
|
|
19
|
-
"node-opcua-address-space-base": "2.
|
|
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.
|
|
22
|
-
"node-opcua-binary-stream": "2.
|
|
23
|
-
"node-opcua-certificate-manager": "2.
|
|
24
|
-
"node-opcua-common": "2.
|
|
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.
|
|
27
|
-
"node-opcua-data-model": "2.
|
|
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.
|
|
29
|
+
"node-opcua-file-transfer": "2.169.0",
|
|
30
30
|
"node-opcua-hostname": "2.167.0",
|
|
31
|
-
"node-opcua-nodeid": "2.
|
|
32
|
-
"node-opcua-pki": "6.
|
|
33
|
-
"node-opcua-pseudo-session": "2.
|
|
34
|
-
"node-opcua-secure-channel": "2.
|
|
35
|
-
"node-opcua-server": "2.
|
|
36
|
-
"node-opcua-service-translate-browse-path": "2.
|
|
37
|
-
"node-opcua-status-code": "2.
|
|
38
|
-
"node-opcua-types": "2.
|
|
39
|
-
"node-opcua-variant": "2.
|
|
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.
|
|
44
|
-
"node-opcua-data-value": "2.
|
|
45
|
-
"node-opcua-leak-detector": "2.
|
|
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": "
|
|
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 {
|
|
13
|
-
import {
|
|
14
|
-
import type
|
|
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) {
|