node-opcua-server-configuration 2.177.0 → 2.178.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,7 +1,7 @@
1
1
  import type { AddressSpace } from "node-opcua-address-space";
2
2
  import { OPCUACertificateManager } from "node-opcua-certificate-manager";
3
- import { type ICertificateKeyPairProvider } from "node-opcua-common";
4
- import { type OPCUAServer } from "node-opcua-server";
3
+ import { type ICertificateChainProvider, type ICertificateKeyPairProvider } from "node-opcua-common";
4
+ import type { OPCUAServer } from "node-opcua-server";
5
5
  import { type ApplicationDescriptionOptions } from "node-opcua-types";
6
6
  export interface OPCUAServerPartial extends ICertificateKeyPairProvider {
7
7
  serverInfo?: ApplicationDescriptionOptions;
@@ -14,5 +14,37 @@ export interface OPCUAServerPartial extends ICertificateKeyPairProvider {
14
14
  createDefaultCertificate(): Promise<void>;
15
15
  setProvider(provider: ICertificateKeyPairProvider): void;
16
16
  invalidateCachedCertificates(): void;
17
+ getCertificateChainProvider(): ICertificateChainProvider;
17
18
  }
18
- export declare function installPushCertificateManagementOnServer(server: OPCUAServer): Promise<void>;
19
+ export interface InstallPushCertificateManagementOnServerOptions {
20
+ /**
21
+ * What happens to secure channels that are already open when
22
+ * `ApplyChanges` installs a new server certificate (and possibly a new
23
+ * private key).
24
+ *
25
+ * - `true` (default): every existing channel is shut down as soon as
26
+ * the ApplyChanges response has been sent. All connected clients
27
+ * reconnect immediately with the new certificate. This is the safe
28
+ * choice when the rotation is a response to a **compromised or
29
+ * revoked** key: nothing keeps running under the old one.
30
+ *
31
+ * - `false`: existing channels are left open and the rotation is
32
+ * **seamless** for them. Each server channel is bound to the
33
+ * certificate/key pair it was created with (OPC UA Part 6 §6.7.2
34
+ * defines the thumbprint check against "the Certificate it is using
35
+ * for the SecureChannel", and §6.7.4 requires the client to close the
36
+ * channel if a response is signed with a different certificate than
37
+ * the request was encrypted to — so the old pair is the only one a
38
+ * Renew on an old channel can conformantly use). Connected clients
39
+ * keep renewing their security token under the old pair, unmodified
40
+ * third-party clients included; new connections use the new pair.
41
+ * Choose this for a **planned** rotation. The old pair remains in use
42
+ * by those channels until they close naturally — which is exactly why
43
+ * `true` stays the default: when the rotation answers a **compromised
44
+ * or revoked** key, nothing must keep running under it.
45
+ *
46
+ * @defaultValue true
47
+ */
48
+ closeChannelsOnApplyChanges?: boolean;
49
+ }
50
+ export declare function installPushCertificateManagementOnServer(server: OPCUAServer, options?: InstallPushCertificateManagementOnServerOptions): Promise<void>;
@@ -5,10 +5,10 @@ import path from "node:path";
5
5
  import chalk from "chalk";
6
6
  import { assert } from "node-opcua-assert";
7
7
  import { OPCUACertificateManager } from "node-opcua-certificate-manager";
8
- import { DiskCertificateKeyPairProvider } from "node-opcua-common";
9
- import { split_der, exploreCertificateInfo } from "node-opcua-crypto/web";
8
+ import { DiskCertificateKeyPairProvider, ResolvedCertificateKeyPairProvider, resolvePrivateKeyProviderIfNeeded } from "node-opcua-common";
9
+ import { readPrivateKey } from "node-opcua-crypto";
10
+ import { exploreCertificateInfo, split_der } from "node-opcua-crypto/web";
10
11
  import { checkDebugFlag, make_debugLog, make_errorLog, make_warningLog } from "node-opcua-debug";
11
- import { invalidateServerCertificateCache } from "node-opcua-server";
12
12
  import { StatusCodes } from "node-opcua-status-code";
13
13
  import { ServerState } from "node-opcua-types";
14
14
  import { installPushCertificateManagement } from "./push_certificate_manager_helpers.js";
@@ -36,22 +36,102 @@ async function onCertificateAboutToChange(server) {
36
36
  */
37
37
  async function onCertificateChange(server) {
38
38
  doDebug && debugLog("on CertificateChanged");
39
- invalidateServerCertificateCache(server);
39
+ await reresolveServerCertificateProvider(server);
40
40
  }
41
41
  /**
42
- * Deferred channel restart: called after the ApplyChanges method
43
- * response has been sent. Shuts down all existing secure channels
44
- * (which forces clients to reconnect with the new cert) and resumes
45
- * the endpoints.
42
+ * Resolve the private key immediately after a rotation, bypassing
43
+ * `OPCUACertificateManager.getPrivateKey()`'s cache.
44
+ *
45
+ * `getPrivateKey()` decrypts the on-disk key *once* and caches it for the
46
+ * manager instance's whole lifetime (pki's documented behavior — see
47
+ * `CertificateManager.getPrivateKey`). That's exactly right for a normal
48
+ * startup resolve (`resolvePrivateKeyProviderIfNeeded`, used by
49
+ * `initializeCM()`), but wrong here: `UpdateCertificate`/`ApplyChanges` just
50
+ * replaced the on-disk key file underneath this *same, long-lived*
51
+ * `certificateManager` instance, so a cached call would silently keep
52
+ * returning the pre-rotation key — certificate and key would then belong to
53
+ * different key pairs, and every subsequent handshake fails signature
54
+ * verification.
55
+ *
56
+ * `privateKeyProvider` mode has no such cache — pki consults the injected
57
+ * provider on every call — so this only special-cases `privateKeyPassphrase`
58
+ * (reading the file directly, with the manager's own configured passphrase,
59
+ * instead of going through the memoized `getPrivateKey()`).
60
+ */
61
+ async function resolvePrivateKeyAfterRotation(certificateManager, privateKeyFile) {
62
+ const passphrase = await certificateManager.getPrivateKeyPassphrase();
63
+ if (passphrase !== undefined) {
64
+ return readPrivateKey(privateKeyFile, passphrase);
65
+ }
66
+ return certificateManager.getPrivateKey();
67
+ }
68
+ /**
69
+ * Refresh the server's certificate/private-key provider after a rotation,
70
+ * and push the result to every endpoint.
71
+ *
72
+ * When the certificate manager is passphrase-/provider-managed, a
73
+ * synchronous "invalidate, let it lazily re-read the file" (the old
74
+ * behavior) cannot decrypt the key — so the key is instead resolved here,
75
+ * eagerly and asynchronously (see `resolvePrivateKeyAfterRotation`), and the
76
+ * resulting provider handed out explicitly to the server and every
77
+ * endpoint.
78
+ *
79
+ * For a plain (unmanaged) certificate manager, this falls back to exactly
80
+ * the previous behavior: invalidate the server's and every endpoint's
81
+ * cached certificate/key so the next access re-reads the (plaintext) files
82
+ * from disk.
83
+ */
84
+ async function reresolveServerCertificateProvider(server) {
85
+ const certificateManager = server.serverCertificateManager;
86
+ const isManaged = certificateManager instanceof OPCUACertificateManager && certificateManager.isPrivateKeyManaged();
87
+ if (isManaged) {
88
+ const privateKey = await resolvePrivateKeyAfterRotation(certificateManager, server.privateKeyFile);
89
+ server.setProvider(new ResolvedCertificateKeyPairProvider(server.certificateFile, server.privateKeyFile, privateKey));
90
+ const provider = server.getCertificateChainProvider();
91
+ for (const endpoint of server.endpoints) {
92
+ endpoint.setCertificateProvider(provider);
93
+ }
94
+ }
95
+ else {
96
+ server.invalidateCachedCertificates();
97
+ for (const endpoint of server.endpoints) {
98
+ endpoint.invalidateCertificates();
99
+ }
100
+ }
101
+ }
102
+ /**
103
+ * Deferred step, called after the ApplyChanges method response has been
104
+ * sent (so the admin client gets its answer before its own channel is
105
+ * affected).
106
+ *
107
+ * With `closeChannelsOnApplyChanges` (the default) every existing secure
108
+ * channel is shut down immediately, forcing all connected clients to
109
+ * reconnect with the new certificate right away. Without it, existing
110
+ * channels are left alone and continue — including OpenSecureChannel
111
+ * *Renews* — under the certificate/key pair each channel is bound to,
112
+ * while new connections use the new pair. See
113
+ * {@link InstallPushCertificateManagementOnServerOptions.closeChannelsOnApplyChanges}
114
+ * for when each is appropriate.
115
+ *
116
+ * Either way the endpoints, suspended in `onCertificateAboutToChange`,
117
+ * are resumed here.
46
118
  */
47
- async function onApplyChangesCompleted(server) {
48
- doDebug && debugLog(chalk.yellow(" onApplyChangesCompleted => shutting down channels"));
49
- await server.shutdownChannels();
50
- doDebug && debugLog(chalk.yellow(" onApplyChangesCompleted => channels shut down"));
119
+ async function onApplyChangesCompleted(server, closeChannels) {
120
+ if (closeChannels) {
121
+ doDebug && debugLog(chalk.yellow(" onApplyChangesCompleted => shutting down channels"));
122
+ await server.shutdownChannels();
123
+ doDebug && debugLog(chalk.yellow(" onApplyChangesCompleted => channels shut down"));
124
+ }
125
+ else {
126
+ doDebug &&
127
+ debugLog(chalk.yellow(" onApplyChangesCompleted => leaving existing channels open (bound to their original pair)"));
128
+ }
51
129
  doDebug && debugLog(chalk.yellow(" onApplyChangesCompleted => resuming end points"));
52
130
  await server.resumeEndPoints();
53
131
  doDebug && debugLog(chalk.yellow(" onApplyChangesCompleted => end points resumed"));
54
- debugLog(chalk.yellow("channels have been closed -> client should reconnect "));
132
+ if (closeChannels) {
133
+ debugLog(chalk.yellow("channels have been closed -> client should reconnect "));
134
+ }
55
135
  }
56
136
  /**
57
137
  * Redirect the server's certificate provider to the cert manager's
@@ -62,20 +142,26 @@ async function install() {
62
142
  doDebug && debugLog("install push certificate management", this.serverCertificateManager.rootDir);
63
143
  const certFile = path.join(this.serverCertificateManager.rootDir, CERT_PEM_RELATIVE_PATH);
64
144
  const keyFile = this.serverCertificateManager.privateKey;
65
- // Inject a new disk provider pointing at the cert manager's
66
- // paths. The server's certificateFile/privateKeyFile getters
67
- // now automatically return the new paths.
145
+ // Inject a new disk provider pointing at the cert manager's paths. The
146
+ // server's certificateFile/privateKeyFile getters now automatically
147
+ // return the new paths. If the key turns out to be passphrase-encrypted,
148
+ // resolvePrivateKeyProviderIfNeeded() below immediately replaces this
149
+ // with a resolved, in-memory provider.
68
150
  this.setProvider(new DiskCertificateKeyPairProvider(certFile, keyFile));
69
151
  // Delegate to the base server's createDefaultCertificate() which
70
152
  // handles DNS (fqdn + hostname + configured), IPs (auto + configured),
71
153
  // proper subject via makeSubject(), mutex locking, and file checks.
72
154
  await this.createDefaultCertificate();
73
- // Invalidate any previously cached secrets so that
74
- // getCertificateChain() / getPrivateKey() will re-read from disk.
75
- this.invalidateCachedCertificates();
155
+ // Resolve the (possibly passphrase-encrypted) private key once,
156
+ // asynchronously, and install the resolved provider — a synchronous
157
+ // disk re-read (the old invalidateCachedCertificates() behavior) cannot
158
+ // decrypt an encrypted key. For a plaintext key this just replaces the
159
+ // disk provider above with an equivalent resolved one.
160
+ await resolvePrivateKeyProviderIfNeeded(this, this.serverCertificateManager, false);
76
161
  }
77
- export async function installPushCertificateManagementOnServer(server) {
78
- if (!server.engine || !server.engine.addressSpace) {
162
+ export async function installPushCertificateManagementOnServer(server, options) {
163
+ const closeChannelsOnApplyChanges = options?.closeChannelsOnApplyChanges ?? true;
164
+ if (!server.engine?.addressSpace) {
79
165
  throw new Error("Server must have a valid address space. " +
80
166
  "You need to call installPushCertificateManagementOnServer after server has been initialized");
81
167
  }
@@ -91,10 +177,14 @@ export async function installPushCertificateManagementOnServer(server) {
91
177
  " serverCertificateManager");
92
178
  }
93
179
  const cm = server.serverCertificateManager;
94
- const certFile = path.join(cm.rootDir, CERT_PEM_RELATIVE_PATH);
95
- const keyFile = cm.privateKey;
180
+ // Reuse the server's own provider (installed by install() above, and
181
+ // already resolved if the key is passphrase-encrypted) rather than
182
+ // constructing a fresh DiskCertificateKeyPairProvider from the file
183
+ // paths — a plain disk provider would re-read the file and fail
184
+ // synchronously on an encrypted key.
185
+ const provider = server.getCertificateChainProvider();
96
186
  for (const endpoint of server.endpoints) {
97
- endpoint.setCertificateProvider(new DiskCertificateKeyPairProvider(certFile, keyFile));
187
+ endpoint.setCertificateProvider(provider);
98
188
  }
99
189
  await installPushCertificateManagement(server.engine.addressSpace, {
100
190
  applicationGroup: cm,
@@ -119,11 +209,11 @@ export async function installPushCertificateManagementOnServer(server) {
119
209
  });
120
210
  });
121
211
  serverConfigurationPriv.$pushCertificateManager.on("applyChangesCompleted", () => {
122
- // Fire-and-forget: schedule channel teardown + endpoint
212
+ // Fire-and-forget: schedule (optional) channel teardown + endpoint
123
213
  // resumption AFTER the method response is sent.
124
214
  setImmediate(async () => {
125
215
  try {
126
- await onApplyChangesCompleted(server);
216
+ await onApplyChangesCompleted(server, closeChannelsOnApplyChanges);
127
217
  }
128
218
  catch (err) {
129
219
  errorLog("onApplyChangesCompleted error:", err.message);
@@ -259,7 +349,8 @@ function installCertificateRelaxationHook(server) {
259
349
  if (!isRelaxableCertificateError(statusCode)) {
260
350
  return statusCode;
261
351
  }
262
- doDebug && warningLog(`[NoConfiguration] Relaxing certificate check:`, `${statusCode.toString()} → Good`, "(server is awaiting GDS provisioning)");
352
+ doDebug &&
353
+ warningLog(`[NoConfiguration] Relaxing certificate check:`, `${statusCode.toString()} → Good`, "(server is awaiting GDS provisioning)");
263
354
  // Auto-trust the leaf certificate; issuer CAs go to issuers/
264
355
  await autoTrustCertificateChain(server, certificate);
265
356
  return StatusCodes.Good;
@@ -1 +1 @@
1
- {"version":3,"file":"install_push_certificate_management.js","sourceRoot":"","sources":["../../source/server/install_push_certificate_management.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"install_push_certificate_management.js","sourceRoot":"","sources":["../../source/server/install_push_certificate_management.ts"],"names":[],"mappings":"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"}
@@ -1,7 +1,7 @@
1
1
  import fs from "node:fs";
2
2
  import path from "node:path";
3
3
  import { assert } from "node-opcua-assert";
4
- import { exploreCertificate, readPrivateKey } from "node-opcua-crypto";
4
+ import { _toExportableKeyObject, exploreCertificate } from "node-opcua-crypto";
5
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";
@@ -48,12 +48,31 @@ async function preInstallCertificate(serverImpl, certificateManager, certificate
48
48
  serverImpl.fileTransactionManager.stageFileRemoval(legacyDER);
49
49
  }
50
50
  }
51
+ /**
52
+ * Encode `privateKeyObj` as a PKCS#8 PEM string, encrypted with `passphrase`
53
+ * (aes-256-cbc — same cipher `writePrivateKeyFile` uses) when given, plaintext
54
+ * otherwise. Never touches disk — the caller stages the resulting string via
55
+ * `FileTransactionManager.stageFile()`, so an encrypted key is the only form
56
+ * that is ever written, whether to the staging temp file or the final
57
+ * destination.
58
+ */
59
+ function encodePrivateKeyPem(privateKeyObj, passphrase) {
60
+ if (!passphrase) {
61
+ return coercePrivateKeyPem(privateKeyObj);
62
+ }
63
+ const keyObject = _toExportableKeyObject(privateKeyObj);
64
+ return keyObject.export({ type: "pkcs8", format: "pem", cipher: "aes-256-cbc", passphrase });
65
+ }
51
66
  // Helper: Stage private key to temporary file
52
67
  async function preInstallPrivateKey(serverImpl, certificateManager, privateKeyFormat, privateKey) {
53
68
  assert(privateKeyFormat.toUpperCase() === "PEM");
54
69
  if (privateKey) {
55
70
  const privateKeyObj = coercePEMorDerToPrivateKey(privateKey);
56
- const privateKeyPEM = coercePrivateKeyPem(privateKeyObj);
71
+ // If the certificate manager is configured with a privateKeyPassphrase,
72
+ // the staged (and later applied) key must be encrypted with it — never
73
+ // written back to disk in clear.
74
+ const passphrase = await certificateManager.getPrivateKeyPassphrase?.();
75
+ const privateKeyPEM = encodePrivateKeyPem(privateKeyObj, passphrase);
57
76
  await serverImpl.fileTransactionManager.stageFile(certificateManager.privateKey, privateKeyPEM, "utf-8");
58
77
  }
59
78
  }
@@ -101,7 +120,17 @@ export async function executeUpdateCertificate(serverImpl, certificateGroupId, c
101
120
  return { statusCode: StatusCodes.BadInvalidArgument, applyChangesRequired: false };
102
121
  }
103
122
  if (!hasPrivateKeyFormat && !hasPrivateKey) {
104
- const privateKeyObj = readPrivateKey(serverImpl.tmpCertificateManager ? serverImpl.tmpCertificateManager.privateKey : certificateManager.privateKey);
123
+ // Resolve via getPrivateKey(), for two independent reasons:
124
+ // - CertificateManager.getPrivateKey() caches the decoded key for the lifetime of the
125
+ // instance (see node-opcua-pki). createCertificateRequest()/createSelfSignedCertificate()
126
+ // both go through that cache, so the CSR that produced `certificate` was necessarily
127
+ // signed with the *cached* key. Comparing against a fresh disk read (readPrivateKey
128
+ // on the file path) would compare against whatever is currently on disk instead — which
129
+ // can differ right after a previous updateCertificate() call replaced the private key
130
+ // file directly (bypassing the cache), causing a spurious BadSecurityChecksFailed.
131
+ // - the on-disk file may be an encrypted PKCS#8 key (privateKeyPassphrase); a raw
132
+ // readPrivateKey without the manager's passphrase would fail on it.
133
+ const privateKeyObj = await (serverImpl.tmpCertificateManager ?? certificateManager).getPrivateKey();
105
134
  if (!certificateMatchesPrivateKey(certificate, privateKeyObj)) {
106
135
  warningLog("certificate doesn't match privateKey");
107
136
  await serverImpl.fileTransactionManager.abortTransaction();
@@ -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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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "node-opcua-server-configuration",
3
- "version": "2.177.0",
3
+ "version": "2.178.0",
4
4
  "description": "pure nodejs OPCUA SDK - module server-configuration",
5
5
  "scripts": {
6
6
  "build": "tsc -b",
@@ -15,24 +15,24 @@
15
15
  "dependencies": {
16
16
  "chalk": "4.1.2",
17
17
  "memfs": "^4.68.1",
18
- "node-opcua-address-space": "2.177.0",
19
- "node-opcua-address-space-base": "2.177.0",
18
+ "node-opcua-address-space": "2.178.0",
19
+ "node-opcua-address-space-base": "2.178.0",
20
20
  "node-opcua-assert": "2.175.6",
21
21
  "node-opcua-basic-types": "2.177.0",
22
22
  "node-opcua-binary-stream": "2.176.0",
23
- "node-opcua-certificate-manager": "2.177.0",
24
- "node-opcua-common": "2.177.0",
23
+ "node-opcua-certificate-manager": "2.178.0",
24
+ "node-opcua-common": "2.178.0",
25
25
  "node-opcua-constants": "2.176.0",
26
- "node-opcua-crypto": "5.5.0",
26
+ "node-opcua-crypto": "5.6.0",
27
27
  "node-opcua-data-model": "2.177.0",
28
28
  "node-opcua-debug": "2.175.6",
29
- "node-opcua-file-transfer": "2.177.0",
29
+ "node-opcua-file-transfer": "2.178.0",
30
30
  "node-opcua-hostname": "2.175.1",
31
31
  "node-opcua-nodeid": "2.177.0",
32
- "node-opcua-pki": "6.19.1",
33
- "node-opcua-pseudo-session": "2.177.0",
34
- "node-opcua-secure-channel": "2.177.0",
35
- "node-opcua-server": "2.177.0",
32
+ "node-opcua-pki": "6.20.0",
33
+ "node-opcua-pseudo-session": "2.178.0",
34
+ "node-opcua-secure-channel": "2.178.0",
35
+ "node-opcua-server": "2.178.0",
36
36
  "node-opcua-service-translate-browse-path": "2.177.0",
37
37
  "node-opcua-status-code": "2.176.0",
38
38
  "node-opcua-types": "2.177.0",
@@ -41,7 +41,7 @@
41
41
  },
42
42
  "devDependencies": {
43
43
  "bcryptjs": "3.0.3",
44
- "node-opcua-client": "2.177.0",
44
+ "node-opcua-client": "2.178.0",
45
45
  "node-opcua-data-value": "2.177.0",
46
46
  "node-opcua-leak-detector": "2.175.6",
47
47
  "node-opcua-nodesets": "2.175.3"
@@ -61,7 +61,7 @@
61
61
  "internet of things"
62
62
  ],
63
63
  "homepage": "http://node-opcua.github.io/",
64
- "gitHead": "a00d1d3f4db4522ed6bb2c6104d86c57df79946a",
64
+ "gitHead": "d0e850e5f7df80e14ceb6d8e092e793472965301",
65
65
  "files": [
66
66
  "dist",
67
67
  "source"
@@ -8,10 +8,17 @@ import chalk from "chalk";
8
8
  import type { AddressSpace, UAServerConfiguration } from "node-opcua-address-space";
9
9
  import { assert } from "node-opcua-assert";
10
10
  import { OPCUACertificateManager } from "node-opcua-certificate-manager";
11
- import { DiskCertificateKeyPairProvider, type ICertificateKeyPairProvider } from "node-opcua-common";
12
- import { type Certificate, split_der, exploreCertificateInfo } from "node-opcua-crypto/web";
11
+ import {
12
+ DiskCertificateKeyPairProvider,
13
+ type ICertificateChainProvider,
14
+ type ICertificateKeyPairProvider,
15
+ ResolvedCertificateKeyPairProvider,
16
+ resolvePrivateKeyProviderIfNeeded
17
+ } from "node-opcua-common";
18
+ import { readPrivateKey } from "node-opcua-crypto";
19
+ import { type Certificate, exploreCertificateInfo, split_der } from "node-opcua-crypto/web";
13
20
  import { checkDebugFlag, make_debugLog, make_errorLog, make_warningLog } from "node-opcua-debug";
14
- import { invalidateServerCertificateCache, type OPCUAServer, type OPCUAServerEndPoint } from "node-opcua-server";
21
+ import type { OPCUAServer, OPCUAServerEndPoint } from "node-opcua-server";
15
22
  import { type StatusCode, StatusCodes } from "node-opcua-status-code";
16
23
  import { type ApplicationDescriptionOptions, ServerState } from "node-opcua-types";
17
24
 
@@ -35,6 +42,7 @@ export interface OPCUAServerPartial extends ICertificateKeyPairProvider {
35
42
  createDefaultCertificate(): Promise<void>;
36
43
  setProvider(provider: ICertificateKeyPairProvider): void;
37
44
  invalidateCachedCertificates(): void;
45
+ getCertificateChainProvider(): ICertificateChainProvider;
38
46
  }
39
47
 
40
48
  async function onCertificateAboutToChange(server: OPCUAServer) {
@@ -56,25 +64,105 @@ async function onCertificateAboutToChange(server: OPCUAServer) {
56
64
  */
57
65
  async function onCertificateChange(server: OPCUAServer) {
58
66
  doDebug && debugLog("on CertificateChanged");
59
- invalidateServerCertificateCache(server);
67
+ await reresolveServerCertificateProvider(server);
60
68
  }
61
69
 
62
70
  /**
63
- * Deferred channel restart: called after the ApplyChanges method
64
- * response has been sent. Shuts down all existing secure channels
65
- * (which forces clients to reconnect with the new cert) and resumes
66
- * the endpoints.
71
+ * Resolve the private key immediately after a rotation, bypassing
72
+ * `OPCUACertificateManager.getPrivateKey()`'s cache.
73
+ *
74
+ * `getPrivateKey()` decrypts the on-disk key *once* and caches it for the
75
+ * manager instance's whole lifetime (pki's documented behavior — see
76
+ * `CertificateManager.getPrivateKey`). That's exactly right for a normal
77
+ * startup resolve (`resolvePrivateKeyProviderIfNeeded`, used by
78
+ * `initializeCM()`), but wrong here: `UpdateCertificate`/`ApplyChanges` just
79
+ * replaced the on-disk key file underneath this *same, long-lived*
80
+ * `certificateManager` instance, so a cached call would silently keep
81
+ * returning the pre-rotation key — certificate and key would then belong to
82
+ * different key pairs, and every subsequent handshake fails signature
83
+ * verification.
84
+ *
85
+ * `privateKeyProvider` mode has no such cache — pki consults the injected
86
+ * provider on every call — so this only special-cases `privateKeyPassphrase`
87
+ * (reading the file directly, with the manager's own configured passphrase,
88
+ * instead of going through the memoized `getPrivateKey()`).
89
+ */
90
+ async function resolvePrivateKeyAfterRotation(certificateManager: OPCUACertificateManager, privateKeyFile: string) {
91
+ const passphrase = await certificateManager.getPrivateKeyPassphrase();
92
+ if (passphrase !== undefined) {
93
+ return readPrivateKey(privateKeyFile, passphrase);
94
+ }
95
+ return certificateManager.getPrivateKey();
96
+ }
97
+
98
+ /**
99
+ * Refresh the server's certificate/private-key provider after a rotation,
100
+ * and push the result to every endpoint.
101
+ *
102
+ * When the certificate manager is passphrase-/provider-managed, a
103
+ * synchronous "invalidate, let it lazily re-read the file" (the old
104
+ * behavior) cannot decrypt the key — so the key is instead resolved here,
105
+ * eagerly and asynchronously (see `resolvePrivateKeyAfterRotation`), and the
106
+ * resulting provider handed out explicitly to the server and every
107
+ * endpoint.
108
+ *
109
+ * For a plain (unmanaged) certificate manager, this falls back to exactly
110
+ * the previous behavior: invalidate the server's and every endpoint's
111
+ * cached certificate/key so the next access re-reads the (plaintext) files
112
+ * from disk.
113
+ */
114
+ async function reresolveServerCertificateProvider(server: OPCUAServer): Promise<void> {
115
+ const certificateManager = server.serverCertificateManager;
116
+ const isManaged = certificateManager instanceof OPCUACertificateManager && certificateManager.isPrivateKeyManaged();
117
+ if (isManaged) {
118
+ const privateKey = await resolvePrivateKeyAfterRotation(certificateManager, server.privateKeyFile);
119
+ server.setProvider(new ResolvedCertificateKeyPairProvider(server.certificateFile, server.privateKeyFile, privateKey));
120
+ const provider = server.getCertificateChainProvider();
121
+ for (const endpoint of server.endpoints) {
122
+ endpoint.setCertificateProvider(provider);
123
+ }
124
+ } else {
125
+ server.invalidateCachedCertificates();
126
+ for (const endpoint of server.endpoints) {
127
+ endpoint.invalidateCertificates();
128
+ }
129
+ }
130
+ }
131
+
132
+ /**
133
+ * Deferred step, called after the ApplyChanges method response has been
134
+ * sent (so the admin client gets its answer before its own channel is
135
+ * affected).
136
+ *
137
+ * With `closeChannelsOnApplyChanges` (the default) every existing secure
138
+ * channel is shut down immediately, forcing all connected clients to
139
+ * reconnect with the new certificate right away. Without it, existing
140
+ * channels are left alone and continue — including OpenSecureChannel
141
+ * *Renews* — under the certificate/key pair each channel is bound to,
142
+ * while new connections use the new pair. See
143
+ * {@link InstallPushCertificateManagementOnServerOptions.closeChannelsOnApplyChanges}
144
+ * for when each is appropriate.
145
+ *
146
+ * Either way the endpoints, suspended in `onCertificateAboutToChange`,
147
+ * are resumed here.
67
148
  */
68
- async function onApplyChangesCompleted(server: OPCUAServer) {
69
- doDebug && debugLog(chalk.yellow(" onApplyChangesCompleted => shutting down channels"));
70
- await server.shutdownChannels();
71
- doDebug && debugLog(chalk.yellow(" onApplyChangesCompleted => channels shut down"));
149
+ async function onApplyChangesCompleted(server: OPCUAServer, closeChannels: boolean) {
150
+ if (closeChannels) {
151
+ doDebug && debugLog(chalk.yellow(" onApplyChangesCompleted => shutting down channels"));
152
+ await server.shutdownChannels();
153
+ doDebug && debugLog(chalk.yellow(" onApplyChangesCompleted => channels shut down"));
154
+ } else {
155
+ doDebug &&
156
+ debugLog(chalk.yellow(" onApplyChangesCompleted => leaving existing channels open (bound to their original pair)"));
157
+ }
72
158
 
73
159
  doDebug && debugLog(chalk.yellow(" onApplyChangesCompleted => resuming end points"));
74
160
  await server.resumeEndPoints();
75
161
  doDebug && debugLog(chalk.yellow(" onApplyChangesCompleted => end points resumed"));
76
162
 
77
- debugLog(chalk.yellow("channels have been closed -> client should reconnect "));
163
+ if (closeChannels) {
164
+ debugLog(chalk.yellow("channels have been closed -> client should reconnect "));
165
+ }
78
166
  }
79
167
 
80
168
  /**
@@ -88,9 +176,11 @@ async function install(this: OPCUAServerPartial): Promise<void> {
88
176
  const certFile = path.join(this.serverCertificateManager.rootDir, CERT_PEM_RELATIVE_PATH);
89
177
  const keyFile = this.serverCertificateManager.privateKey;
90
178
 
91
- // Inject a new disk provider pointing at the cert manager's
92
- // paths. The server's certificateFile/privateKeyFile getters
93
- // now automatically return the new paths.
179
+ // Inject a new disk provider pointing at the cert manager's paths. The
180
+ // server's certificateFile/privateKeyFile getters now automatically
181
+ // return the new paths. If the key turns out to be passphrase-encrypted,
182
+ // resolvePrivateKeyProviderIfNeeded() below immediately replaces this
183
+ // with a resolved, in-memory provider.
94
184
  this.setProvider(new DiskCertificateKeyPairProvider(certFile, keyFile));
95
185
 
96
186
  // Delegate to the base server's createDefaultCertificate() which
@@ -98,20 +188,59 @@ async function install(this: OPCUAServerPartial): Promise<void> {
98
188
  // proper subject via makeSubject(), mutex locking, and file checks.
99
189
  await this.createDefaultCertificate();
100
190
 
101
- // Invalidate any previously cached secrets so that
102
- // getCertificateChain() / getPrivateKey() will re-read from disk.
103
- this.invalidateCachedCertificates();
191
+ // Resolve the (possibly passphrase-encrypted) private key once,
192
+ // asynchronously, and install the resolved provider — a synchronous
193
+ // disk re-read (the old invalidateCachedCertificates() behavior) cannot
194
+ // decrypt an encrypted key. For a plaintext key this just replaces the
195
+ // disk provider above with an equivalent resolved one.
196
+ await resolvePrivateKeyProviderIfNeeded(this, this.serverCertificateManager, false);
104
197
  }
105
198
 
106
199
  interface UAServerConfigurationEx extends UAServerConfiguration {
107
200
  $pushCertificateManager: PushCertificateManagerServerImpl;
108
201
  }
109
202
 
110
- export async function installPushCertificateManagementOnServer(server: OPCUAServer): Promise<void> {
111
- if (!server.engine || !server.engine.addressSpace) {
203
+ export interface InstallPushCertificateManagementOnServerOptions {
204
+ /**
205
+ * What happens to secure channels that are already open when
206
+ * `ApplyChanges` installs a new server certificate (and possibly a new
207
+ * private key).
208
+ *
209
+ * - `true` (default): every existing channel is shut down as soon as
210
+ * the ApplyChanges response has been sent. All connected clients
211
+ * reconnect immediately with the new certificate. This is the safe
212
+ * choice when the rotation is a response to a **compromised or
213
+ * revoked** key: nothing keeps running under the old one.
214
+ *
215
+ * - `false`: existing channels are left open and the rotation is
216
+ * **seamless** for them. Each server channel is bound to the
217
+ * certificate/key pair it was created with (OPC UA Part 6 §6.7.2
218
+ * defines the thumbprint check against "the Certificate it is using
219
+ * for the SecureChannel", and §6.7.4 requires the client to close the
220
+ * channel if a response is signed with a different certificate than
221
+ * the request was encrypted to — so the old pair is the only one a
222
+ * Renew on an old channel can conformantly use). Connected clients
223
+ * keep renewing their security token under the old pair, unmodified
224
+ * third-party clients included; new connections use the new pair.
225
+ * Choose this for a **planned** rotation. The old pair remains in use
226
+ * by those channels until they close naturally — which is exactly why
227
+ * `true` stays the default: when the rotation answers a **compromised
228
+ * or revoked** key, nothing must keep running under it.
229
+ *
230
+ * @defaultValue true
231
+ */
232
+ closeChannelsOnApplyChanges?: boolean;
233
+ }
234
+
235
+ export async function installPushCertificateManagementOnServer(
236
+ server: OPCUAServer,
237
+ options?: InstallPushCertificateManagementOnServerOptions
238
+ ): Promise<void> {
239
+ const closeChannelsOnApplyChanges = options?.closeChannelsOnApplyChanges ?? true;
240
+ if (!server.engine?.addressSpace) {
112
241
  throw new Error(
113
242
  "Server must have a valid address space. " +
114
- "You need to call installPushCertificateManagementOnServer after server has been initialized"
243
+ "You need to call installPushCertificateManagementOnServer after server has been initialized"
115
244
  );
116
245
  }
117
246
  await install.call(server as unknown as OPCUAServerPartial);
@@ -124,15 +253,19 @@ export async function installPushCertificateManagementOnServer(server: OPCUAServ
124
253
  if (!(server.serverCertificateManager instanceof OPCUACertificateManager)) {
125
254
  throw new Error(
126
255
  "installPushCertificateManagementOnServer requires a" +
127
- " disk-based OPCUACertificateManager as" +
128
- " serverCertificateManager"
256
+ " disk-based OPCUACertificateManager as" +
257
+ " serverCertificateManager"
129
258
  );
130
259
  }
131
260
  const cm = server.serverCertificateManager;
132
- const certFile = path.join(cm.rootDir, CERT_PEM_RELATIVE_PATH);
133
- const keyFile = cm.privateKey;
261
+ // Reuse the server's own provider (installed by install() above, and
262
+ // already resolved if the key is passphrase-encrypted) rather than
263
+ // constructing a fresh DiskCertificateKeyPairProvider from the file
264
+ // paths — a plain disk provider would re-read the file and fail
265
+ // synchronously on an encrypted key.
266
+ const provider = server.getCertificateChainProvider();
134
267
  for (const endpoint of server.endpoints) {
135
- endpoint.setCertificateProvider(new DiskCertificateKeyPairProvider(certFile, keyFile));
268
+ endpoint.setCertificateProvider(provider);
136
269
  }
137
270
 
138
271
  await installPushCertificateManagement(server.engine.addressSpace, {
@@ -162,11 +295,11 @@ export async function installPushCertificateManagementOnServer(server: OPCUAServ
162
295
  });
163
296
 
164
297
  serverConfigurationPriv.$pushCertificateManager.on("applyChangesCompleted", () => {
165
- // Fire-and-forget: schedule channel teardown + endpoint
298
+ // Fire-and-forget: schedule (optional) channel teardown + endpoint
166
299
  // resumption AFTER the method response is sent.
167
300
  setImmediate(async () => {
168
301
  try {
169
- await onApplyChangesCompleted(server);
302
+ await onApplyChangesCompleted(server, closeChannelsOnApplyChanges);
170
303
  } catch (err) {
171
304
  errorLog("onApplyChangesCompleted error:", (err as Error).message);
172
305
  }
@@ -238,18 +371,12 @@ function isRelaxableCertificateError(statusCode: StatusCode): boolean {
238
371
  * connection from unintentionally granting trust to every
239
372
  * certificate signed by the same CA.
240
373
  */
241
- async function autoTrustCertificateChain(
242
- server: OPCUAServer,
243
- certificate: Certificate
244
- ): Promise<void> {
374
+ async function autoTrustCertificateChain(server: OPCUAServer, certificate: Certificate): Promise<void> {
245
375
  let chain: Certificate[];
246
376
  try {
247
377
  chain = split_der(certificate);
248
378
  } catch (err) {
249
- warningLog(
250
- "[NoConfiguration] Cannot parse certificate chain for auto-trust:",
251
- (err as Error).message
252
- );
379
+ warningLog("[NoConfiguration] Cannot parse certificate chain for auto-trust:", (err as Error).message);
253
380
  return;
254
381
  }
255
382
 
@@ -263,10 +390,7 @@ async function autoTrustCertificateChain(
263
390
  try {
264
391
  exploreCertificateInfo(cert);
265
392
  } catch (err) {
266
- warningLog(
267
- "[NoConfiguration] Skipping invalid certificate in chain for auto-trust:",
268
- (err as Error).message
269
- );
393
+ warningLog("[NoConfiguration] Skipping invalid certificate in chain for auto-trust:", (err as Error).message);
270
394
  continue;
271
395
  }
272
396
 
@@ -278,10 +402,7 @@ async function autoTrustCertificateChain(
278
402
  // ENOENT can happen if another concurrent call already
279
403
  // moved the cert from rejected to trusted.
280
404
  if ((err as Error & { code?: string }).code !== "ENOENT") {
281
- warningLog(
282
- "[NoConfiguration] Failed to auto-trust leaf certificate:",
283
- (err as Error).message
284
- );
405
+ warningLog("[NoConfiguration] Failed to auto-trust leaf certificate:", (err as Error).message);
285
406
  }
286
407
  }
287
408
  } else {
@@ -290,10 +411,7 @@ async function autoTrustCertificateChain(
290
411
  try {
291
412
  await cm.addIssuer(cert);
292
413
  } catch (err) {
293
- warningLog(
294
- "[NoConfiguration] Failed to add issuer certificate:",
295
- (err as Error).message
296
- );
414
+ warningLog("[NoConfiguration] Failed to add issuer certificate:", (err as Error).message);
297
415
  }
298
416
  }
299
417
  }
@@ -312,10 +430,7 @@ async function autoTrustCertificateChain(
312
430
  * building but do not become trust anchors.
313
431
  */
314
432
  function installCertificateRelaxationHook(server: OPCUAServer): void {
315
- const adjustCertificateStatus = async (
316
- statusCode: StatusCode,
317
- certificate: Certificate
318
- ): Promise<StatusCode> => {
433
+ const adjustCertificateStatus = async (statusCode: StatusCode, certificate: Certificate): Promise<StatusCode> => {
319
434
  // Only relax in NoConfiguration state
320
435
  if (server.engine.getServerState() !== ServerState.NoConfiguration) {
321
436
  return statusCode;
@@ -326,11 +441,12 @@ function installCertificateRelaxationHook(server: OPCUAServer): void {
326
441
  return statusCode;
327
442
  }
328
443
 
329
- doDebug && warningLog(
330
- `[NoConfiguration] Relaxing certificate check:`,
331
- `${statusCode.toString()} → Good`,
332
- "(server is awaiting GDS provisioning)"
333
- );
444
+ doDebug &&
445
+ warningLog(
446
+ `[NoConfiguration] Relaxing certificate check:`,
447
+ `${statusCode.toString()} → Good`,
448
+ "(server is awaiting GDS provisioning)"
449
+ );
334
450
 
335
451
  // Auto-trust the leaf certificate; issuer CAs go to issuers/
336
452
  await autoTrustCertificateChain(server, certificate);
@@ -3,10 +3,10 @@ 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 { exploreCertificate, readPrivateKey } from "node-opcua-crypto";
6
+ import { _toExportableKeyObject, exploreCertificate } from "node-opcua-crypto";
7
7
  import {
8
- certificateMatchesPrivateKey,
9
8
  type Certificate,
9
+ certificateMatchesPrivateKey,
10
10
  coercePEMorDerToPrivateKey,
11
11
  coercePrivateKeyPem,
12
12
  combine_der,
@@ -83,6 +83,22 @@ async function preInstallCertificate(
83
83
  }
84
84
  }
85
85
 
86
+ /**
87
+ * Encode `privateKeyObj` as a PKCS#8 PEM string, encrypted with `passphrase`
88
+ * (aes-256-cbc — same cipher `writePrivateKeyFile` uses) when given, plaintext
89
+ * otherwise. Never touches disk — the caller stages the resulting string via
90
+ * `FileTransactionManager.stageFile()`, so an encrypted key is the only form
91
+ * that is ever written, whether to the staging temp file or the final
92
+ * destination.
93
+ */
94
+ function encodePrivateKeyPem(privateKeyObj: PrivateKey, passphrase: string | undefined): string {
95
+ if (!passphrase) {
96
+ return coercePrivateKeyPem(privateKeyObj);
97
+ }
98
+ const keyObject = _toExportableKeyObject(privateKeyObj);
99
+ return keyObject.export({ type: "pkcs8", format: "pem", cipher: "aes-256-cbc", passphrase }) as string;
100
+ }
101
+
86
102
  // Helper: Stage private key to temporary file
87
103
  async function preInstallPrivateKey(
88
104
  serverImpl: PushCertificateManagerInternalContext,
@@ -94,7 +110,11 @@ async function preInstallPrivateKey(
94
110
 
95
111
  if (privateKey) {
96
112
  const privateKeyObj = coercePEMorDerToPrivateKey(privateKey as string | Buffer);
97
- const privateKeyPEM = coercePrivateKeyPem(privateKeyObj);
113
+ // If the certificate manager is configured with a privateKeyPassphrase,
114
+ // the staged (and later applied) key must be encrypted with it — never
115
+ // written back to disk in clear.
116
+ const passphrase = await (certificateManager as OPCUACertificateManager).getPrivateKeyPassphrase?.();
117
+ const privateKeyPEM = encodePrivateKeyPem(privateKeyObj, passphrase);
98
118
  await serverImpl.fileTransactionManager.stageFile(certificateManager.privateKey, privateKeyPEM, "utf-8");
99
119
  }
100
120
  }
@@ -170,9 +190,17 @@ export async function executeUpdateCertificate(
170
190
  }
171
191
 
172
192
  if (!hasPrivateKeyFormat && !hasPrivateKey) {
173
- const privateKeyObj = readPrivateKey(
174
- serverImpl.tmpCertificateManager ? serverImpl.tmpCertificateManager.privateKey : certificateManager.privateKey
175
- );
193
+ // Resolve via getPrivateKey(), for two independent reasons:
194
+ // - CertificateManager.getPrivateKey() caches the decoded key for the lifetime of the
195
+ // instance (see node-opcua-pki). createCertificateRequest()/createSelfSignedCertificate()
196
+ // both go through that cache, so the CSR that produced `certificate` was necessarily
197
+ // signed with the *cached* key. Comparing against a fresh disk read (readPrivateKey
198
+ // on the file path) would compare against whatever is currently on disk instead — which
199
+ // can differ right after a previous updateCertificate() call replaced the private key
200
+ // file directly (bypassing the cache), causing a spurious BadSecurityChecksFailed.
201
+ // - the on-disk file may be an encrypted PKCS#8 key (privateKeyPassphrase); a raw
202
+ // readPrivateKey without the manager's passphrase would fail on it.
203
+ const privateKeyObj = await (serverImpl.tmpCertificateManager ?? certificateManager).getPrivateKey();
176
204
 
177
205
  if (!certificateMatchesPrivateKey(certificate, privateKeyObj)) {
178
206
  warningLog("certificate doesn't match privateKey");