node-opcua-server-configuration 2.163.0 → 2.164.2

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 (100) hide show
  1. package/dist/clientTools/certificate_types.d.ts +17 -0
  2. package/dist/clientTools/certificate_types.js +20 -0
  3. package/dist/clientTools/certificate_types.js.map +1 -0
  4. package/dist/clientTools/get_certificate_key_type.d.ts +6 -0
  5. package/dist/clientTools/get_certificate_key_type.js +55 -0
  6. package/dist/clientTools/get_certificate_key_type.js.map +1 -0
  7. package/dist/clientTools/index.d.ts +2 -1
  8. package/dist/clientTools/index.js +2 -17
  9. package/dist/clientTools/index.js.map +1 -1
  10. package/dist/clientTools/push_certificate_management_client.d.ts +10 -10
  11. package/dist/clientTools/push_certificate_management_client.js +85 -89
  12. package/dist/clientTools/push_certificate_management_client.js.map +1 -1
  13. package/dist/index.d.ts +9 -7
  14. package/dist/index.js +9 -23
  15. package/dist/index.js.map +1 -1
  16. package/dist/push_certificate_manager.d.ts +4 -4
  17. package/dist/push_certificate_manager.js +1 -2
  18. package/dist/server/certificate_validation.d.ts +15 -0
  19. package/dist/server/certificate_validation.js +76 -0
  20. package/dist/server/certificate_validation.js.map +1 -0
  21. package/dist/server/file_transaction_manager.d.ts +30 -0
  22. package/dist/server/file_transaction_manager.js +223 -0
  23. package/dist/server/file_transaction_manager.js.map +1 -0
  24. package/dist/server/install_certificate_file_watcher.d.ts +1 -1
  25. package/dist/server/install_certificate_file_watcher.js +8 -14
  26. package/dist/server/install_certificate_file_watcher.js.map +1 -1
  27. package/dist/server/install_push_certitifate_management.d.ts +6 -6
  28. package/dist/server/install_push_certitifate_management.js +61 -65
  29. package/dist/server/install_push_certitifate_management.js.map +1 -1
  30. package/dist/server/promote_trust_list.d.ts +1 -1
  31. package/dist/server/promote_trust_list.js +323 -82
  32. package/dist/server/promote_trust_list.js.map +1 -1
  33. package/dist/server/push_certificate_manager/apply_changes.d.ts +3 -0
  34. package/dist/server/push_certificate_manager/apply_changes.js +59 -0
  35. package/dist/server/push_certificate_manager/apply_changes.js.map +1 -0
  36. package/dist/server/push_certificate_manager/create_signing_request.d.ts +5 -0
  37. package/dist/server/push_certificate_manager/create_signing_request.js +108 -0
  38. package/dist/server/push_certificate_manager/create_signing_request.js.map +1 -0
  39. package/dist/server/push_certificate_manager/get_rejected_list.d.ts +3 -0
  40. package/dist/server/push_certificate_manager/get_rejected_list.js +46 -0
  41. package/dist/server/push_certificate_manager/get_rejected_list.js.map +1 -0
  42. package/dist/server/push_certificate_manager/internal_context.d.ts +35 -0
  43. package/dist/server/push_certificate_manager/internal_context.js +45 -0
  44. package/dist/server/push_certificate_manager/internal_context.js.map +1 -0
  45. package/dist/server/push_certificate_manager/subject_to_string.d.ts +3 -0
  46. package/dist/server/push_certificate_manager/subject_to_string.js +27 -0
  47. package/dist/server/push_certificate_manager/subject_to_string.js.map +1 -0
  48. package/dist/server/push_certificate_manager/update_certificate.d.ts +5 -0
  49. package/dist/server/push_certificate_manager/update_certificate.js +132 -0
  50. package/dist/server/push_certificate_manager/update_certificate.js.map +1 -0
  51. package/dist/server/push_certificate_manager/util.d.ts +29 -0
  52. package/dist/server/push_certificate_manager/util.js +117 -0
  53. package/dist/server/push_certificate_manager/util.js.map +1 -0
  54. package/dist/server/push_certificate_manager_helpers.d.ts +5 -2
  55. package/dist/server/push_certificate_manager_helpers.js +109 -112
  56. package/dist/server/push_certificate_manager_helpers.js.map +1 -1
  57. package/dist/server/push_certificate_manager_server_impl.d.ts +16 -29
  58. package/dist/server/push_certificate_manager_server_impl.js +49 -437
  59. package/dist/server/push_certificate_manager_server_impl.js.map +1 -1
  60. package/dist/server/roles_and_permissions.d.ts +1 -1
  61. package/dist/server/roles_and_permissions.js +24 -27
  62. package/dist/server/roles_and_permissions.js.map +1 -1
  63. package/dist/server/tools.d.ts +1 -1
  64. package/dist/server/tools.js +7 -13
  65. package/dist/server/tools.js.map +1 -1
  66. package/dist/server/trust_list_server.d.ts +2 -2
  67. package/dist/server/trust_list_server.js +40 -29
  68. package/dist/server/trust_list_server.js.map +1 -1
  69. package/dist/standard_certificate_types.js +6 -9
  70. package/dist/standard_certificate_types.js.map +1 -1
  71. package/dist/trust_list.d.ts +2 -2
  72. package/dist/trust_list.js +1 -2
  73. package/dist/trust_list_impl.js +1 -2
  74. package/dist/trust_list_impl.js.map +1 -1
  75. package/package.json +29 -30
  76. package/source/clientTools/certificate_types.ts +21 -0
  77. package/source/clientTools/get_certificate_key_type.ts +73 -0
  78. package/source/clientTools/index.ts +2 -1
  79. package/source/clientTools/push_certificate_management_client.ts +49 -44
  80. package/source/index.ts +9 -7
  81. package/source/push_certificate_manager.ts +15 -17
  82. package/source/server/certificate_validation.ts +103 -0
  83. package/source/server/file_transaction_manager.ts +253 -0
  84. package/source/server/install_certificate_file_watcher.ts +15 -11
  85. package/source/server/install_push_certitifate_management.ts +52 -51
  86. package/source/server/promote_trust_list.ts +362 -73
  87. package/source/server/push_certificate_manager/apply_changes.ts +63 -0
  88. package/source/server/push_certificate_manager/create_signing_request.ts +137 -0
  89. package/source/server/push_certificate_manager/get_rejected_list.ts +63 -0
  90. package/source/server/push_certificate_manager/internal_context.ts +63 -0
  91. package/source/server/push_certificate_manager/subject_to_string.ts +25 -0
  92. package/source/server/push_certificate_manager/update_certificate.ts +201 -0
  93. package/source/server/push_certificate_manager/util.ts +145 -0
  94. package/source/server/push_certificate_manager_helpers.ts +61 -51
  95. package/source/server/push_certificate_manager_server_impl.ts +94 -553
  96. package/source/server/roles_and_permissions.ts +7 -8
  97. package/source/server/tools.ts +2 -5
  98. package/source/server/trust_list_server.ts +24 -9
  99. package/source/standard_certificate_types.ts +2 -3
  100. package/source/trust_list.ts +26 -33
@@ -1,78 +1,28 @@
1
1
  /**
2
2
  * @module node-opcua-server-configuration-server
3
3
  */
4
- import { EventEmitter } from "events";
5
- import fs from "fs";
6
- import path from "path";
7
- import { rimraf } from "rimraf";
8
- import { SubjectOptions } from "node-opcua-pki";
4
+ import { EventEmitter } from "node:events";
9
5
  import { assert } from "node-opcua-assert";
10
- import { ByteString, StatusCodes } from "node-opcua-basic-types";
11
- import {
12
- convertPEMtoDER,
13
- exploreCertificate,
14
- makeSHA1Thumbprint,
15
- toPem,
16
- PrivateKey,
17
- certificateMatchesPrivateKey,
18
- coercePEMorDerToPrivateKey,
19
- coercePrivateKeyPem,
20
- } from "node-opcua-crypto/web";
21
-
22
- import { readPrivateKey, readCertificate } from "node-opcua-crypto";
23
-
24
- // fixme: use the one from node-opcua-crypto
25
- export interface DirectoryName {
26
- stateOrProvinceName?: string;
27
- localityName?: string;
28
- organizationName?: string;
29
- organizationUnitName?: string;
30
- commonName?: string;
31
- countryName?: string;
32
- }
33
-
34
- import { checkDebugFlag, make_debugLog, make_errorLog, make_warningLog } from "node-opcua-debug";
35
- import { NodeId, resolveNodeId, sameNodeId } from "node-opcua-nodeid";
6
+ import type { ByteString } from "node-opcua-basic-types";
36
7
  import { CertificateManager } from "node-opcua-certificate-manager";
37
- import { StatusCode } from "node-opcua-status-code";
38
-
39
- import {
8
+ import { make_errorLog } from "node-opcua-debug";
9
+ import type { NodeId } from "node-opcua-nodeid";
10
+ import type { SubjectOptions } from "node-opcua-pki";
11
+ import type { StatusCode } from "node-opcua-status-code";
12
+ import { rsaCertificateTypesArray } from "../clientTools/certificate_types.js";
13
+ import type {
40
14
  CreateSigningRequestResult,
41
15
  GetRejectedListResult,
42
16
  PushCertificateManager,
43
17
  UpdateCertificateResult
44
- } from "../push_certificate_manager";
45
-
46
- // node 14 onward : import { readFile, writeFile, readdir } from "fs/promises";
47
- const { readFile, writeFile, readdir } = fs.promises;
18
+ } from "../push_certificate_manager.js";
19
+ import { executeApplyChanges } from "./push_certificate_manager/apply_changes.js";
20
+ import { executeCreateSigningRequest } from "./push_certificate_manager/create_signing_request.js";
21
+ import { executeGetRejectedList } from "./push_certificate_manager/get_rejected_list.js";
22
+ import { PushCertificateManagerInternalContext } from "./push_certificate_manager/internal_context.js";
23
+ import { executeUpdateCertificate } from "./push_certificate_manager/update_certificate.js";
48
24
 
49
- const debugLog = make_debugLog("ServerConfiguration");
50
25
  const errorLog = make_errorLog("ServerConfiguration");
51
- const warningLog = make_warningLog("ServerConfiguration");
52
- const doDebug = checkDebugFlag("ServerConfiguration");
53
- doDebug;
54
-
55
- const defaultApplicationGroup = resolveNodeId("ServerConfiguration_CertificateGroups_DefaultApplicationGroup");
56
- const defaultHttpsGroup = resolveNodeId("ServerConfiguration_CertificateGroups_DefaultHttpsGroup");
57
- const defaultUserTokenGroup = resolveNodeId("ServerConfiguration_CertificateGroups_DefaultUserTokenGroup");
58
-
59
-
60
-
61
- function findCertificateGroupName(certificateGroupNodeId: NodeId | string): string {
62
- if (typeof certificateGroupNodeId === "string") {
63
- return certificateGroupNodeId;
64
- }
65
- if (sameNodeId(certificateGroupNodeId, NodeId.nullNodeId) || sameNodeId(certificateGroupNodeId, defaultApplicationGroup)) {
66
- return "DefaultApplicationGroup";
67
- }
68
- if (sameNodeId(certificateGroupNodeId, defaultHttpsGroup)) {
69
- return "DefaultHttpsGroup";
70
- }
71
- if (sameNodeId(certificateGroupNodeId, defaultUserTokenGroup)) {
72
- return "DefaultUserTokenGroup";
73
- }
74
- return "";
75
- }
76
26
 
77
27
  export interface PushCertificateManagerServerOptions {
78
28
  applicationGroup?: CertificateManager;
@@ -80,76 +30,15 @@ export interface PushCertificateManagerServerOptions {
80
30
  httpsGroup?: CertificateManager;
81
31
 
82
32
  applicationUri: string;
83
- }
84
33
 
85
- type Functor = () => Promise<void>;
86
-
87
- export async function copyFile(source: string, dest: string): Promise<void> {
88
- try {
89
- debugLog("copying file \n source ", source, "\n =>\n dest ", dest);
90
- const sourceExist = fs.existsSync(source);
91
- if (sourceExist) {
92
- await fs.promises.copyFile(source, dest);
93
- }
94
- } catch (err) {
95
- errorLog(err);
96
- }
97
- }
98
-
99
- export async function deleteFile(file: string): Promise<void> {
100
- try {
101
- const exists = await fs.existsSync(file);
102
- if (exists) {
103
- debugLog("deleting file ", file);
104
- await fs.promises.unlink(file);
105
- }
106
- } catch (err) {
107
- errorLog(err);
108
- }
109
- }
110
-
111
- export async function moveFile(source: string, dest: string): Promise<void> {
112
- debugLog("moving file file \n source ", source, "\n =>\n dest ", dest);
113
- try {
114
- await copyFile(source, dest);
115
- await deleteFile(source);
116
- } catch (err) {
117
- errorLog(err);
118
- }
119
- }
120
-
121
- export async function moveFileWithBackup(source: string, dest: string): Promise<void> {
122
- // let make a copy of the destination file
123
- debugLog("moveFileWithBackup file \n source ", source, "\n =>\n dest ", dest);
124
- await copyFile(dest, dest + "_old");
125
- await moveFile(source, dest);
34
+ // Optional: Allowed certificate types for each group
35
+ // These should be read from the CertificateTypes Property of the CertificateGroup objects in the AddressSpace
36
+ // If not provided, defaults to all known OPC UA certificate types (backward compatibility)
37
+ applicationGroupCertificateTypes?: NodeId[];
38
+ userTokenGroupCertificateTypes?: NodeId[];
39
+ httpsGroupCertificateTypes?: NodeId[];
126
40
  }
127
41
 
128
-
129
- export function subjectToString(subject: SubjectOptions & DirectoryName): string {
130
- let s = "";
131
- subject.commonName && (s += `/CN=${subject.commonName}`);
132
-
133
- subject.country && (s += `/C=${subject.country}`);
134
- subject.countryName && (s += `/C=${subject.countryName}`);
135
-
136
- subject.domainComponent && (s += `/DC=${subject.domainComponent}`);
137
-
138
- subject.locality && (s += `/L=${subject.locality}`);
139
- subject.localityName && (s += `/L=${subject.localityName}`);
140
-
141
- subject.organization && (s += `/O=${subject.organization}`);
142
- subject.organizationName && (s += `/O=${subject.organizationName}`);
143
-
144
- subject.organizationUnitName && (s += `/OU=${subject.organizationUnitName}`);
145
-
146
- subject.state && (s += `/ST=${subject.state}`);
147
- subject.stateOrProvinceName && (s += `/ST=${subject.stateOrProvinceName}`);
148
-
149
- return s;
150
- }
151
- let fileCounter = 0;
152
-
153
42
  export type ActionQueue = (() => Promise<void>)[];
154
43
 
155
44
  export class PushCertificateManagerServerImpl extends EventEmitter implements PushCertificateManager {
@@ -157,15 +46,15 @@ export class PushCertificateManagerServerImpl extends EventEmitter implements Pu
157
46
  public userTokenGroup?: CertificateManager;
158
47
  public httpsGroup?: CertificateManager;
159
48
 
160
- private readonly _map: { [key: string]: CertificateManager } = {};
161
- private readonly _pendingTasks: Functor[] = [];
162
- private _tmpCertificateManager?: CertificateManager;
163
- private $$actionQueue: ActionQueue = [];
49
+ // Use a true private reference (could be upgraded to #context in recent ES)
50
+ private readonly _context: PushCertificateManagerInternalContext;
164
51
 
165
- private applicationUri: string;
52
+ /** @hidden */
53
+ public applicationUri: string;
166
54
 
167
55
  constructor(options: PushCertificateManagerServerOptions) {
168
56
  super();
57
+ this._context = new PushCertificateManagerInternalContext(this);
169
58
 
170
59
  this.applicationUri = options ? options.applicationUri : "";
171
60
 
@@ -174,23 +63,38 @@ export class PushCertificateManagerServerImpl extends EventEmitter implements Pu
174
63
  this.userTokenGroup = options.userTokenGroup;
175
64
  this.httpsGroup = options.httpsGroup;
176
65
  if (this.userTokenGroup) {
177
- this._map.DefaultUserTokenGroup = this.userTokenGroup;
178
-
179
- // istanbul ignore next
66
+ this._context.map.DefaultUserTokenGroup = this.userTokenGroup;
67
+ // Store allowed certificate types, or use all known types as default
68
+ this._context.certificateTypes.DefaultUserTokenGroup = options.userTokenGroupCertificateTypes || [
69
+ // [...rsaCertificateTypes, ...eccCertificateTypes];
70
+ ...rsaCertificateTypesArray
71
+ ]; // FIXME: ECC is not yet supported
72
+
73
+ // c8 ignore next
180
74
  if (!(this.userTokenGroup instanceof CertificateManager)) {
181
75
  errorLog(
182
76
  "Expecting this.userTokenGroup to be instanceof CertificateManager :",
183
- (this.userTokenGroup as any).constructor.name
77
+ (this.userTokenGroup as unknown as { constructor: { name: string } }).constructor.name
184
78
  );
185
79
  throw new Error("Expecting this.userTokenGroup to be instanceof CertificateManager ");
186
80
  }
187
81
  }
188
82
  if (this.applicationGroup) {
189
- this._map.DefaultApplicationGroup = this.applicationGroup;
83
+ this._context.map.DefaultApplicationGroup = this.applicationGroup;
84
+ // Store allowed certificate types, or use all known types as default
85
+ this._context.certificateTypes.DefaultApplicationGroup = options.applicationGroupCertificateTypes || [
86
+ // [...rsaCertificateTypes, ...eccCertificateTypes];
87
+ ...rsaCertificateTypesArray
88
+ ]; // FIXME: ECC is not yet supported
190
89
  assert(this.applicationGroup instanceof CertificateManager);
191
90
  }
192
91
  if (this.httpsGroup) {
193
- this._map.DefaultHttpsGroup = this.httpsGroup;
92
+ this._context.map.DefaultHttpsGroup = this.httpsGroup;
93
+ // Store allowed certificate types, or use all known types as default
94
+ this._context.certificateTypes.DefaultHttpsGroup = options.httpsGroupCertificateTypes || [
95
+ // [...rsaCertificateTypes, ...eccCertificateTypes];
96
+ ...rsaCertificateTypesArray
97
+ ]; // FIXME: ECC is not yet supported
194
98
  assert(this.httpsGroup instanceof CertificateManager);
195
99
  }
196
100
  }
@@ -213,7 +117,7 @@ export class PushCertificateManagerServerImpl extends EventEmitter implements Pu
213
117
  }
214
118
 
215
119
  public async getSupportedPrivateKeyFormats(): Promise<string[]> {
216
- return this.supportedPrivateKeyFormats;
120
+ return ["PEM"];
217
121
  }
218
122
 
219
123
  public async createSigningRequest(
@@ -223,157 +127,20 @@ export class PushCertificateManagerServerImpl extends EventEmitter implements Pu
223
127
  regeneratePrivateKey?: boolean,
224
128
  nonce?: Buffer
225
129
  ): Promise<CreateSigningRequestResult> {
226
- let certificateManager = this.getCertificateManager(certificateGroupId);
227
-
228
- if (!certificateManager) {
229
- debugLog(" cannot find group ", certificateGroupId);
230
- return {
231
- statusCode: StatusCodes.BadInvalidArgument
232
- };
233
- }
234
-
235
- if (!subjectName) {
236
- // reuse existing subjectName
237
- const currentCertificateFilename = path.join(certificateManager.rootDir, "own/certs/certificate.pem");
238
- if (!fs.existsSync(currentCertificateFilename)) {
239
- errorLog("Cannot find existing certificate to extract subjectName", currentCertificateFilename);
240
- return {
241
- statusCode: StatusCodes.BadInvalidState
242
- };
243
- }
244
- const certificate = readCertificate(currentCertificateFilename);
245
- const e = exploreCertificate(certificate);
246
- subjectName = subjectToString(e.tbsCertificate.subject);
247
- warningLog("reusing existing certificate subjectAltName = ", subjectName);
248
- }
249
-
250
- // todo : at this time regenerate private key is not supported
251
- if (regeneratePrivateKey) {
252
- // The Server shall create a new Private Key which it stores until the
253
- // matching signed Certificate is uploaded with the UpdateCertificate Method.
254
- // Previously created Private Keys may be discarded if UpdateCertificate was not
255
- // called before calling this method again.
256
-
257
- // Additional entropy which the caller shall provide if regeneratePrivateKey is TRUE.
258
- // It shall be at least 32 bytes long
259
- if (!nonce || nonce.length < 32) {
260
- make_warningLog(
261
- " nonce should be provided when regeneratePrivateKey is set, and length shall be greater than 32 bytes"
262
- );
263
- return {
264
- statusCode: StatusCodes.BadInvalidArgument
265
- };
266
- }
267
-
268
- const location = path.join(certificateManager.rootDir, "tmp");
269
- if (fs.existsSync(location)) {
270
- await rimraf.rimraf(path.join(location));
271
- }
272
- if (!fs.existsSync(location)) {
273
- await fs.promises.mkdir(location);
274
- }
275
-
276
- const destCertificateManager = certificateManager;
277
- const keySize = (certificateManager as any).keySize; // because keySize is private !
278
- certificateManager = new CertificateManager({
279
- keySize,
280
- location
281
- });
282
- debugLog("generating a new private key ...");
283
- await certificateManager.initialize();
284
-
285
- this._tmpCertificateManager = certificateManager;
286
-
287
- this.addPendingTask(async () => {
288
- await moveFileWithBackup(certificateManager!.privateKey, destCertificateManager.privateKey);
289
- });
290
- this.addPendingTask(async () => {
291
- await rimraf.rimraf(path.join(location));
292
- });
293
- } else {
294
- // The Server uses its existing Private Key
295
- }
296
-
297
- if (typeof subjectName !== "string") {
298
- return { statusCode: StatusCodes.BadInternalError };
299
- }
300
- const options = {
301
- applicationUri: this.applicationUri,
302
- subject: subjectName!
303
- };
304
- await certificateManager.initialize();
305
- const csrFile = await certificateManager.createCertificateRequest(options);
306
- const csrPEM = await readFile(csrFile, "utf8");
307
- const certificateSigningRequest = convertPEMtoDER(csrPEM);
308
-
309
- this.addPendingTask(() => deleteFile(csrFile));
310
-
311
- return {
312
- certificateSigningRequest,
313
- statusCode: StatusCodes.Good
314
- };
130
+ return await executeCreateSigningRequest(
131
+ this._context,
132
+ certificateGroupId,
133
+ certificateTypeId,
134
+ subjectName,
135
+ regeneratePrivateKey,
136
+ nonce
137
+ );
315
138
  }
316
139
 
317
140
  public async getRejectedList(): Promise<GetRejectedListResult> {
318
- interface FileData {
319
- filename: string;
320
- stat: {
321
- mtime: Date;
322
- };
323
- }
324
-
325
- // rejectedList comes from each group
326
- async function extractRejectedList(group: CertificateManager | undefined, certificateList: FileData[]): Promise<void> {
327
- if (!group) {
328
- return;
329
- }
330
- const rejectedFolder = path.join(group.rootDir, "rejected");
331
- const files = await readdir(rejectedFolder);
332
-
333
- const stat = fs.promises.stat;
334
-
335
- const promises1: Promise<fs.Stats>[] = [];
336
- for (const certFile of files) {
337
- // read date
338
- promises1.push(stat(path.join(rejectedFolder, certFile)));
339
- }
340
- const stats = await Promise.all(promises1);
341
-
342
- for (let i = 0; i < stats.length; i++) {
343
- certificateList.push({
344
- filename: path.join(rejectedFolder, files[i]),
345
- stat: stats[i]
346
- });
347
- }
348
- }
349
-
350
- const list: FileData[] = [];
351
- await extractRejectedList(this.applicationGroup, list);
352
- await extractRejectedList(this.userTokenGroup, list);
353
- await extractRejectedList(this.httpsGroup, list);
354
-
355
- // now sort list from newer file to older file
356
- list.sort((a: FileData, b: FileData) => b.stat.mtime.getTime() - a.stat.mtime.getTime());
357
-
358
- const promises: Promise<string>[] = [];
359
- for (const item of list) {
360
- promises.push(readFile(item.filename, "utf8"));
361
- }
362
- const certificatesPEM: string[] = await Promise.all(promises);
363
-
364
- const certificates: Buffer[] = certificatesPEM.map(convertPEMtoDER);
365
- return {
366
- certificates,
367
- statusCode: StatusCodes.Good
368
- };
141
+ return await executeGetRejectedList(this._context);
369
142
  }
370
143
 
371
- public async updateCertificate(
372
- certificateGroupId: NodeId | string,
373
- certificateTypeId: NodeId | string,
374
- certificate: Buffer,
375
- issuerCertificates: ByteString[]
376
- ): Promise<UpdateCertificateResult>;
377
144
  // eslint-disable-next-line max-statements
378
145
  public async updateCertificate(
379
146
  certificateGroupId: NodeId | string,
@@ -383,292 +150,66 @@ export class PushCertificateManagerServerImpl extends EventEmitter implements Pu
383
150
  privateKeyFormat?: string,
384
151
  privateKey?: Buffer | string
385
152
  ): Promise<UpdateCertificateResult> {
386
- // Result Code Description
387
- // BadInvalidArgument The certificateTypeId or certificateGroupId is not valid.
388
- // BadCertificateInvalid The Certificate is invalid or the format is not supported.
389
- // BadNotSupported The Private Key is invalid or the format is not supported.
390
- // BadUserAccessDenied The current user does not have the rights required.
391
- // BadSecurityChecksFailed Some failure occurred verifying the integrity of the Certificate.
392
- const certificateManager = this.getCertificateManager(certificateGroupId)!;
393
-
394
- if (!certificateManager) {
395
- debugLog(" cannot find group ", certificateGroupId);
396
- return {
397
- statusCode: StatusCodes.BadInvalidArgument,
398
- applyChangesRequired: false
399
- };
400
- }
401
-
402
- async function preInstallCertificate(self: PushCertificateManagerServerImpl) {
403
- const certFolder = path.join(certificateManager.rootDir, "own/certs");
404
- const certificateFileDER = path.join(certFolder, `_pending_certificate${fileCounter++}.der`);
405
- const certificateFilePEM = path.join(certFolder, `_pending_certificate${fileCounter++}.pem`);
406
-
407
- await writeFile(certificateFileDER, certificate, "binary");
408
- await writeFile(certificateFilePEM, toPem(certificate, "CERTIFICATE"));
409
-
410
- const destDER = path.join(certFolder, "certificate.der");
411
- const destPEM = path.join(certFolder, "certificate.pem");
412
-
413
- // put existing file in security by backing them up
414
- self.addPendingTask(() => moveFileWithBackup(certificateFileDER, destDER));
415
- self.addPendingTask(() => moveFileWithBackup(certificateFilePEM, destPEM));
416
- }
417
-
418
- async function preInstallPrivateKey(self: PushCertificateManagerServerImpl) {
419
- assert(privateKeyFormat!.toUpperCase() === "PEM");
420
-
421
- const ownPrivateFolder = path.join(certificateManager.rootDir, "own/private");
422
- const privateKeyFilePEM = path.join(ownPrivateFolder, `_pending_private_key${fileCounter++}.pem`);
423
-
424
- if (privateKey) {
425
- const privateKey1 = coercePEMorDerToPrivateKey(privateKey);
426
- const privateKeyPEM = await coercePrivateKeyPem(privateKey1);
427
- await writeFile(privateKeyFilePEM, privateKeyPEM, "utf-8");
428
- self.addPendingTask(() => moveFileWithBackup(privateKeyFilePEM, certificateManager.privateKey));
429
- }
430
- }
431
-
432
- // OPC Unified Architecture, Part 12 42 Release 1.04:
433
- //
434
- // UpdateCertificate is used to update a Certificate for a Server.
435
- // There are the following three use cases for this Method:
436
- //
437
- // - The new Certificate was created based on a signing request created with the Method
438
- // In this case there is no privateKey provided.
439
- // - A new privateKey and Certificate was created outside the Server and both are updated
440
- // with this Method.
441
- // - A new Certificate was created and signed with the information from the old Certificate.
442
- // In this case there is no privateKey provided.
443
-
444
- // The Server shall do all normal integrity checks on the Certificate and all of the issuer
445
- // Certificates. If errors occur the BadSecurityChecksFailed error is returned.
446
- // todo : all normal integrity check on the certificate
447
- const certInfo = exploreCertificate(certificate);
448
-
449
- const now = new Date();
450
- if (certInfo.tbsCertificate.validity.notBefore.getTime() > now.getTime()) {
451
- // certificate is not yet valid
452
- warningLog(
453
- "Certificate is not yet valid : not before ",
454
- certInfo.tbsCertificate.validity.notBefore.toISOString(),
455
- "now = ",
456
- now.toISOString()
457
- );
458
- return {
459
- statusCode: StatusCodes.BadSecurityChecksFailed,
460
- applyChangesRequired: false
461
- };
462
- }
463
- if (certInfo.tbsCertificate.validity.notAfter.getTime() < now.getTime()) {
464
- // certificate is already out of date
465
- warningLog(
466
- "Certificate is already out of date : not after ",
467
- certInfo.tbsCertificate.validity.notAfter.toISOString(),
468
- "now = ",
469
- now.toISOString()
470
- );
471
- return {
472
- statusCode: StatusCodes.BadSecurityChecksFailed,
473
- applyChangesRequired: false
474
- };
475
- }
476
-
477
- // If the Server returns applyChangesRequired=FALSE then it is indicating that it is able to
478
- // satisfy the requirements specified for the ApplyChanges Method.
479
-
480
- debugLog(" updateCertificate ", makeSHA1Thumbprint(certificate).toString("hex"));
481
-
482
- if (!privateKeyFormat || !privateKey) {
483
- // first of all we need to find the future private key;
484
- // this one may have been created during the creation of the certificate signing request
485
- // but is not active yet
486
- const privateKey1 = readPrivateKey(
487
- this._tmpCertificateManager ? this._tmpCertificateManager.privateKey : certificateManager.privateKey
488
- );
489
-
490
- // The Server shall report an error if the public key does not match the existing Certificate and
491
- // the privateKey was not provided.
492
- // privateKey is not provided, so check that the public key matches the existing certificate
493
- if (!certificateMatchesPrivateKey(certificate, privateKey1)) {
494
- // certificate doesn't match privateKey
495
- warningLog("certificate doesn't match privateKey");
496
- /* debug code */
497
- const certificatePEM = toPem(certificate, "CERTIFICATE");
498
- certificatePEM;
499
- //xx const privateKeyPEM = toPem(privateKeyDER, "RSA PRIVATE KEY");
500
- //xx const initialBuffer = Buffer.from("Lorem Ipsum");
501
- //xx const encryptedBuffer = publicEncrypt_long(initialBuffer, certificatePEM, 256, 11);
502
- //xx const decryptedBuffer = privateDecrypt_long(encryptedBuffer, privateKeyPEM, 256);
503
- return {
504
- statusCode: StatusCodes.BadSecurityChecksFailed,
505
- applyChangesRequired: false,
506
- };
507
- }
508
- // a new certificate is provided for us,
509
- // we keep our private key
510
- // we do this in two stages
511
- await preInstallCertificate(this);
512
-
513
- return {
514
- statusCode: StatusCodes.Good,
515
- applyChangesRequired: true,
516
- };
517
- } else if (privateKey) {
518
- // a private key has been provided by the caller !
519
- if (!privateKeyFormat) {
520
- warningLog("the privateKeyFormat must be specified " + privateKeyFormat);
521
- return { statusCode: StatusCodes.BadNotSupported, applyChangesRequired: false};
522
- }
523
- if (privateKeyFormat !== "PEM" && privateKeyFormat !== "PFX") {
524
- warningLog(" the private key format is invalid privateKeyFormat =" + privateKeyFormat);
525
- return { statusCode: StatusCodes.BadNotSupported, applyChangesRequired: false };
526
- }
527
- if (privateKeyFormat !== "PEM") {
528
- warningLog("in NodeOPCUA we only support PEM for the moment privateKeyFormat =" + privateKeyFormat);
529
- return { statusCode: StatusCodes.BadNotSupported , applyChangesRequired: false };
530
- }
531
-
532
- let privateKey1: PrivateKey | undefined;
533
- if (privateKey && (privateKey instanceof Buffer || typeof privateKey === "string")) {
534
- if (privateKey instanceof Buffer) {
535
- assert(privateKeyFormat === "PEM");
536
- privateKey = privateKey.toString("utf-8");
537
- }
538
- privateKey1 = coercePEMorDerToPrivateKey(privateKey);
539
- }
540
- if (!privateKey1) {
541
- return { statusCode: StatusCodes.BadNotSupported, applyChangesRequired: false };
542
- }
543
- // privateKey is provided, so check that the public key matches provided private key
544
- if (!certificateMatchesPrivateKey(certificate, privateKey1!)) {
545
- // certificate doesn't match privateKey
546
- warningLog("certificate doesn't match privateKey");
547
- return { statusCode: StatusCodes.BadSecurityChecksFailed, applyChangesRequired: false };
548
- }
549
-
550
- await preInstallPrivateKey(this);
551
-
552
- await preInstallCertificate(this);
553
-
554
-
555
-
556
- return {
557
- statusCode: StatusCodes.Good,
558
- applyChangesRequired: true
559
- };
560
- } else {
561
- // todo !
562
- return {
563
- statusCode: StatusCodes.BadNotSupported,
564
- applyChangesRequired: true
565
- };
566
- }
153
+ return await executeUpdateCertificate(
154
+ this._context,
155
+ certificateGroupId,
156
+ certificateTypeId,
157
+ certificate,
158
+ issuerCertificates,
159
+ privateKeyFormat,
160
+ privateKey
161
+ );
567
162
  }
568
163
 
569
164
  /**
570
- *
571
- * ApplyChanges is used to apply pending Certificate and TrustList updates
165
+ *
166
+ * ApplyChanges is used to apply pending Certificate and TrustList updates
572
167
  * and to complete a transaction as described in 7.10.2.
573
- *
168
+ *
574
169
  * ApplyChanges returns Bad_InvalidState if any TrustList is still open for writing.
575
170
  * No changes are applied and ApplyChanges can be called again after the TrustList is closed.
576
- *
171
+ *
577
172
  * If a Session is closed or abandoned then the transaction is closed and all pending changes are discarded.
578
- *
579
- * If ApplyChanges is called and there is no active transaction then the Server returns Bad_NothingToDo.
173
+ *
174
+ * If ApplyChanges is called and there is no active transaction then the Server returns Bad_NothingToDo.
580
175
  * If there is an active transaction, however, no changes are pending the result is Good and the transaction is closed.
581
- *
176
+ *
582
177
  * When a Server Certificate or TrustList changes active SecureChannels are not immediately affected.
583
- * This ensures the caller of ApplyChanges can get a response to the Method call.
584
- * Once the Method response is returned the Server shall force existing SecureChannels affected by
178
+ * This ensures the caller of ApplyChanges can get a response to the Method call.
179
+ * Once the Method response is returned the Server shall force existing SecureChannels affected by
585
180
  * the changes to renegotiate and use the new Server Certificate and/or TrustLists.
586
- *
587
- * Servers may close SecureChannels without discarding any Sessions or Subscriptions.
588
- * This will seem like a network interruption from the perspective of the Client and the Client reconnect
589
- * logic (see OPC 10000-4) allows them to recover their Session and Subscriptions.
181
+ *
182
+ * Servers may close SecureChannels without discarding any Sessions or Subscriptions.
183
+ * This will seem like a network interruption from the perspective of the Client and the Client reconnect
184
+ * logic (see OPC 10000-4) allows them to recover their Session and Subscriptions.
590
185
  * Note that some Clients may not be able to reconnect because they are no longer trusted.
591
- *
186
+ *
592
187
  * Other Servers may need to do a complete shutdown.
593
- *
594
- * In this case, the Server shall advertise its intent to interrupt connections by setting the
188
+ *
189
+ * In this case, the Server shall advertise its intent to interrupt connections by setting the
595
190
  * SecondsTillShutdown and ShutdownReason Properties in the ServerStatus Variable.
596
- *
597
- * If a TrustList change only affects UserIdentity associated with a Session then Servers
598
- * shall re-evaluate the UserIdentity and if it is no longer valid the Session and associated
191
+ *
192
+ * If a TrustList change only affects UserIdentity associated with a Session then Servers
193
+ * shall re-evaluate the UserIdentity and if it is no longer valid the Session and associated
599
194
  * Subscriptions are closed.
600
- *
601
- * This Method shall be called from an authenticated SecureChannel and from the Session that
195
+ *
196
+ * This Method shall be called from an authenticated SecureChannel and from the Session that
602
197
  * created the transaction and has access to the SecurityAdmin Role (see 7.2).
603
- *
198
+ *
604
199
  */
605
200
  public async applyChanges(): Promise<StatusCode> {
606
- // ApplyChanges is used to tell the Server to apply any security changes.
607
- // This Method should only be called if a previous call to a Method that changed the
608
- // configuration returns applyChangesRequired=true.
609
- //
610
- // If the Server Certificate has changed, Secure Channels using the old Certificate will
611
- // eventually be interrupted.
612
-
613
- this.emit("CertificateAboutToChange", this.$$actionQueue);
614
- await this.flushActionQueue();
615
-
616
- try {
617
- await this.applyPendingTasks();
618
- } catch (err) {
619
- debugLog("err ", err);
620
- return StatusCodes.BadInternalError;
621
- }
622
- this.emit("CertificateChanged", this.$$actionQueue);
623
- await this.flushActionQueue();
624
-
625
- // The only leeway the Server has is with the timing.
626
- // In the best case, the Server can close the TransportConnections for the affected Endpoints and leave any
627
- // Subscriptions intact. This should appear no different than a network interruption from the
628
- // perspective of the Client. The Client should be prepared to deal with Certificate changes
629
- // during its reconnect logic. In the worst case, a full shutdown which affects all connected
630
- // Clients will be necessary. In the latter case, the Server shall advertise its intent to interrupt
631
- // connections by setting the SecondsTillShutdown and ShutdownReason Properties in the
632
- // ServerStatus Variable.
633
-
634
- // If the Secure Channel being used to call this Method will be affected by the Certificate change
635
- // then the Server shall introduce a delay long enough to allow the caller to receive a reply.
636
- return StatusCodes.Good;
201
+ return await executeApplyChanges(this._context);
637
202
  }
638
203
 
639
- private getCertificateManager(certificateGroupId: NodeId | string): CertificateManager | null {
640
- const groupName = findCertificateGroupName(certificateGroupId);
641
- return this._map[groupName] || null;
204
+ public getCertificateManager(groupName: string): CertificateManager | null {
205
+ return this._context.map[groupName] || null;
642
206
  }
643
207
 
644
- private addPendingTask(functor: () => Promise<void>): void {
645
- this._pendingTasks.push(functor);
208
+ public getCertificateTypes(groupName: string): NodeId[] | undefined {
209
+ return this._context.certificateTypes[groupName];
646
210
  }
647
211
 
648
- private async applyPendingTasks(): Promise<void> {
649
- debugLog("start applyPendingTasks");
650
- const promises: Promise<void>[] = [];
651
- const t = this._pendingTasks.splice(0);
652
-
653
- if (false) {
654
- // node 10.2 and above
655
- for await (const task of t) {
656
- await task();
657
- }
658
- } else {
659
- while (t.length) {
660
- const task = t.shift()!;
661
- await task();
662
- }
663
- }
664
- await Promise.all(promises);
665
- debugLog("end applyPendingTasks");
666
- }
667
-
668
- private async flushActionQueue(): Promise<void> {
669
- while (this.$$actionQueue.length) {
670
- const first = this.$$actionQueue.pop()!;
671
- await first!();
672
- }
212
+ public async dispose(): Promise<void> {
213
+ await this._context.dispose();
673
214
  }
674
215
  }