node-opcua-server-configuration 2.177.0 → 2.179.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/clientTools/push_certificate_management_client.js +1 -1
- package/dist/clientTools/push_certificate_management_client.js.map +1 -1
- package/dist/server/file_transaction_manager.js +26 -13
- package/dist/server/file_transaction_manager.js.map +1 -1
- package/dist/server/install_certificate_file_watcher.js +4 -2
- package/dist/server/install_certificate_file_watcher.js.map +1 -1
- package/dist/server/install_push_certificate_management.d.ts +35 -3
- package/dist/server/install_push_certificate_management.js +119 -27
- package/dist/server/install_push_certificate_management.js.map +1 -1
- package/dist/server/promote_trust_list.js +6 -6
- package/dist/server/promote_trust_list.js.map +1 -1
- package/dist/server/push_certificate_manager/create_signing_request.js.map +1 -1
- package/dist/server/push_certificate_manager/update_certificate.js +34 -4
- package/dist/server/push_certificate_manager/update_certificate.js.map +1 -1
- package/dist/server/push_certificate_manager_helpers.js +8 -4
- package/dist/server/push_certificate_manager_helpers.js.map +1 -1
- package/dist/server/push_certificate_manager_server_impl.d.ts +4 -2
- package/dist/server/push_certificate_manager_server_impl.js +0 -3
- package/dist/server/push_certificate_manager_server_impl.js.map +1 -1
- package/dist/server/tools.js +1 -1
- package/dist/server/tools.js.map +1 -1
- package/package.json +30 -30
- package/source/clientTools/push_certificate_management_client.ts +1 -1
- package/source/server/file_transaction_manager.ts +26 -13
- package/source/server/install_certificate_file_watcher.ts +5 -4
- package/source/server/install_push_certificate_management.ts +175 -58
- package/source/server/promote_trust_list.ts +16 -15
- package/source/server/push_certificate_manager/create_signing_request.ts +4 -2
- package/source/server/push_certificate_manager/update_certificate.ts +36 -7
- package/source/server/push_certificate_manager_helpers.ts +9 -5
- package/source/server/push_certificate_manager_server_impl.ts +6 -9
- package/source/server/tools.ts +1 -1
|
@@ -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 {
|
|
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
|
-
|
|
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
|
}
|
|
@@ -117,7 +137,8 @@ export async function executeUpdateCertificate(
|
|
|
117
137
|
try {
|
|
118
138
|
const context = resolveCertificateGroupContext(serverImpl, certificateGroupId);
|
|
119
139
|
if (context.statusCode.isNotGood() || !context.certificateManager) {
|
|
120
|
-
|
|
140
|
+
// c8 ignore next
|
|
141
|
+
doDebug && debugLog(" cannot find group ", certificateGroupId);
|
|
121
142
|
return { statusCode: StatusCodes.BadInvalidArgument, applyChangesRequired: false };
|
|
122
143
|
}
|
|
123
144
|
|
|
@@ -170,9 +191,17 @@ export async function executeUpdateCertificate(
|
|
|
170
191
|
}
|
|
171
192
|
|
|
172
193
|
if (!hasPrivateKeyFormat && !hasPrivateKey) {
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
)
|
|
194
|
+
// Resolve via getPrivateKey(), for two independent reasons:
|
|
195
|
+
// - CertificateManager.getPrivateKey() caches the decoded key for the lifetime of the
|
|
196
|
+
// instance (see node-opcua-pki). createCertificateRequest()/createSelfSignedCertificate()
|
|
197
|
+
// both go through that cache, so the CSR that produced `certificate` was necessarily
|
|
198
|
+
// signed with the *cached* key. Comparing against a fresh disk read (readPrivateKey
|
|
199
|
+
// on the file path) would compare against whatever is currently on disk instead — which
|
|
200
|
+
// can differ right after a previous updateCertificate() call replaced the private key
|
|
201
|
+
// file directly (bypassing the cache), causing a spurious BadSecurityChecksFailed.
|
|
202
|
+
// - the on-disk file may be an encrypted PKCS#8 key (privateKeyPassphrase); a raw
|
|
203
|
+
// readPrivateKey without the manager's passphrase would fail on it.
|
|
204
|
+
const privateKeyObj = await (serverImpl.tmpCertificateManager ?? certificateManager).getPrivateKey();
|
|
176
205
|
|
|
177
206
|
if (!certificateMatchesPrivateKey(certificate, privateKeyObj)) {
|
|
178
207
|
warningLog("certificate doesn't match privateKey");
|
|
@@ -16,12 +16,12 @@ import {
|
|
|
16
16
|
} from "node-opcua-address-space";
|
|
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
|
+
import type { CertificateManager } from "node-opcua-certificate-manager";
|
|
19
20
|
import { ObjectIds, ObjectTypeIds } from "node-opcua-constants";
|
|
20
21
|
import { type Certificate, readCertificateChainAsync } from "node-opcua-crypto";
|
|
21
22
|
import { AccessRestrictionsFlag, BrowseDirection, coerceQualifiedName, NodeClass } from "node-opcua-data-model";
|
|
22
|
-
import { make_debugLog, make_errorLog, make_warningLog } from "node-opcua-debug";
|
|
23
|
+
import { checkDebugFlag, make_debugLog, make_errorLog, make_warningLog } from "node-opcua-debug";
|
|
23
24
|
import { NodeId, resolveNodeId } from "node-opcua-nodeid";
|
|
24
|
-
import type { CertificateManager } from "node-opcua-certificate-manager";
|
|
25
25
|
import { StatusCodes } from "node-opcua-status-code";
|
|
26
26
|
import type { CallMethodResultOptions } from "node-opcua-types";
|
|
27
27
|
import { DataType, type Variant, VariantArrayType } from "node-opcua-variant";
|
|
@@ -37,6 +37,7 @@ import { rolePermissionAdminOnly, rolePermissionRestricted } from "./roles_and_p
|
|
|
37
37
|
import { hasEncryptedChannel, hasExpectedUserAccess } from "./tools.js";
|
|
38
38
|
|
|
39
39
|
const debugLog = make_debugLog("ServerConfiguration");
|
|
40
|
+
const doDebug = checkDebugFlag("ServerConfiguration");
|
|
40
41
|
const warningLog = make_warningLog("ServerConfiguration");
|
|
41
42
|
const errorLog = make_errorLog("ServerConfiguration");
|
|
42
43
|
|
|
@@ -273,14 +274,16 @@ function bindCertificateGroup(certificateGroup: UACertificateGroup, certificateM
|
|
|
273
274
|
const certificateFile = getCertificateFilename(certificateManager);
|
|
274
275
|
const changeDetector = installCertificateFileWatcher(certificateGroup, certificateFile);
|
|
275
276
|
changeDetector.on("certificateChange", () => {
|
|
276
|
-
|
|
277
|
+
// c8 ignore next
|
|
278
|
+
doDebug && debugLog("detecting certificate change", certificateFile);
|
|
277
279
|
updateCertificateAlarm();
|
|
278
280
|
});
|
|
279
281
|
}
|
|
280
282
|
|
|
281
283
|
async function updateCertificateAlarm() {
|
|
282
284
|
try {
|
|
283
|
-
|
|
285
|
+
// c8 ignore next
|
|
286
|
+
doDebug && debugLog("updateCertificateAlarm", certificateGroup.browseName.toString());
|
|
284
287
|
const certificateExpired = certificateGroup.getComponentByName("CertificateExpired");
|
|
285
288
|
if (certificateExpired && certificateManager) {
|
|
286
289
|
const certificateExpiredEx = certificateExpired as unknown as UACertificateExpirationAlarmEx;
|
|
@@ -393,7 +396,8 @@ export async function installPushCertificateManagement(
|
|
|
393
396
|
|
|
394
397
|
const serverConfigurationPriv = serverConfiguration as UAServerConfigurationPriv;
|
|
395
398
|
if (serverConfigurationPriv.$pushCertificateManager) {
|
|
396
|
-
|
|
399
|
+
// c8 ignore next
|
|
400
|
+
doDebug && debugLog("PushCertificateManagement has already been installed");
|
|
397
401
|
return;
|
|
398
402
|
}
|
|
399
403
|
|
|
@@ -39,6 +39,8 @@ export interface PushCertificateManagerEvents {
|
|
|
39
39
|
applyChangesCompleted: (context?: ISessionContext) => void;
|
|
40
40
|
certificateUpdated: (certificateGroupId: NodeId | string, certificate: Buffer) => void;
|
|
41
41
|
trustListUpdated: (certificateGroupId: string) => void;
|
|
42
|
+
CertificateAboutToChange: (actionQueue: ActionQueue) => void;
|
|
43
|
+
CertificateChanged: (actionQueue: ActionQueue) => void;
|
|
42
44
|
}
|
|
43
45
|
|
|
44
46
|
export interface PushCertificateManagerServerOptions {
|
|
@@ -71,18 +73,14 @@ export class PushCertificateManagerServerImpl extends EventEmitter implements Pu
|
|
|
71
73
|
|
|
72
74
|
// ── typed event helpers ──────────────────────────────────────────
|
|
73
75
|
public on<K extends keyof PushCertificateManagerEvents>(event: K, listener: PushCertificateManagerEvents[K]): this;
|
|
74
|
-
|
|
75
|
-
public on(event: string | symbol, listener: (...args:
|
|
76
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
77
|
-
public on(event: string | symbol, listener: (...args: any[]) => void): this {
|
|
76
|
+
public on(event: string | symbol, listener: (...args: unknown[]) => void): this;
|
|
77
|
+
public on(event: string | symbol, listener: (...args: unknown[]) => void): this {
|
|
78
78
|
return super.on(event, listener);
|
|
79
79
|
}
|
|
80
80
|
|
|
81
81
|
public once<K extends keyof PushCertificateManagerEvents>(event: K, listener: PushCertificateManagerEvents[K]): this;
|
|
82
|
-
|
|
83
|
-
public once(event: string | symbol, listener: (...args:
|
|
84
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
85
|
-
public once(event: string | symbol, listener: (...args: any[]) => void): this {
|
|
82
|
+
public once(event: string | symbol, listener: (...args: unknown[]) => void): this;
|
|
83
|
+
public once(event: string | symbol, listener: (...args: unknown[]) => void): this {
|
|
86
84
|
return super.once(event, listener);
|
|
87
85
|
}
|
|
88
86
|
|
|
@@ -175,7 +173,6 @@ export class PushCertificateManagerServerImpl extends EventEmitter implements Pu
|
|
|
175
173
|
return await executeGetRejectedList(this._context);
|
|
176
174
|
}
|
|
177
175
|
|
|
178
|
-
// eslint-disable-next-line max-statements
|
|
179
176
|
public async updateCertificate(
|
|
180
177
|
certificateGroupId: NodeId | string,
|
|
181
178
|
certificateTypeId: NodeId | string,
|
package/source/server/tools.ts
CHANGED
|
@@ -2,7 +2,7 @@ import { type ISessionContext, WellKnownRoles } from "node-opcua-address-space";
|
|
|
2
2
|
import { MessageSecurityMode } from "node-opcua-secure-channel";
|
|
3
3
|
|
|
4
4
|
export function hasExpectedUserAccess(context: ISessionContext) {
|
|
5
|
-
if (!context
|
|
5
|
+
if (!context?.session?.userIdentityToken) {
|
|
6
6
|
return false;
|
|
7
7
|
}
|
|
8
8
|
return context.currentUserHasRole(WellKnownRoles.SecurityAdmin);
|