node-opcua-server-discovery 2.51.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.
@@ -0,0 +1,564 @@
1
+ /**
2
+ * @module node-opcua-server-discovery
3
+ */
4
+ import * as chalk from "chalk";
5
+ import * as os from "os";
6
+ import * as path from "path";
7
+ import * as url from "url";
8
+ import { callbackify } from "util";
9
+ import envPaths = require("env-paths");
10
+
11
+ import { assert } from "node-opcua-assert";
12
+ import { UAString } from "node-opcua-basic-types";
13
+ import { makeApplicationUrn } from "node-opcua-common";
14
+ import { checkDebugFlag, make_debugLog } from "node-opcua-debug";
15
+ import { extractFullyQualifiedDomainName, resolveFullyQualifiedDomainName } from "node-opcua-hostname";
16
+ import { Message, Response, ServerSecureChannelLayer } from "node-opcua-secure-channel";
17
+ import { OPCUABaseServer, OPCUABaseServerOptions, OPCUAServerEndPoint } from "node-opcua-server";
18
+
19
+ import {
20
+ Announcement,
21
+ BonjourHolder,
22
+ FindServersOnNetworkRequest,
23
+ FindServersOnNetworkResponse,
24
+ MdnsDiscoveryConfiguration,
25
+ RegisteredServer,
26
+ RegisterServer2Request,
27
+ RegisterServer2Response,
28
+ RegisterServerRequest,
29
+ RegisterServerResponse,
30
+ sameAnnouncement,
31
+ ServerOnNetwork
32
+ } from "node-opcua-service-discovery";
33
+ import { ApplicationDescription } from "node-opcua-service-endpoints";
34
+ import { ApplicationDescriptionOptions, ApplicationType } from "node-opcua-service-endpoints";
35
+ import { ErrorCallback, StatusCode, StatusCodes } from "node-opcua-status-code";
36
+
37
+ import { MDNSResponder } from "./mdns_responder";
38
+ import { OPCUACertificateManager } from "node-opcua-certificate-manager";
39
+
40
+ const debugLog = make_debugLog(__filename);
41
+ const doDebug = checkDebugFlag(__filename);
42
+
43
+ function constructFilename(p: string): string {
44
+ const filename = path.join(__dirname, "..", p);
45
+ return filename;
46
+ }
47
+
48
+ function hasCapabilities(serverCapabilities: UAString[] | null, serverCapabilityFilter: string): boolean {
49
+ if (serverCapabilities == null) {
50
+ return true; // filter is empty => no filtering should take place
51
+ }
52
+ if (serverCapabilityFilter.length === 0) {
53
+ return true; // filter is empty => no filtering should take place
54
+ }
55
+ return !!serverCapabilities.join(" ").match(serverCapabilityFilter);
56
+ }
57
+
58
+ export interface OPCUADiscoveryServerOptions extends OPCUABaseServerOptions {
59
+ certificateFile?: string;
60
+ port?: number;
61
+ }
62
+
63
+ interface RegisteredServerExtended extends RegisteredServer {
64
+ bonjourHolder: BonjourHolder;
65
+ serverInfo: ApplicationDescriptionOptions;
66
+ discoveryConfiguration?: MdnsDiscoveryConfiguration[];
67
+ }
68
+
69
+ interface RegisterServerMap {
70
+ [key: string]: RegisteredServerExtended;
71
+ }
72
+
73
+ const defaultProductUri = "NodeOPCUA-LocalDiscoveryServer";
74
+ const defaultApplicationUri = makeApplicationUrn(os.hostname(), defaultProductUri);
75
+
76
+ function getDefaultCertificateManager(): OPCUACertificateManager {
77
+ const config = envPaths(defaultProductUri).config;
78
+ return new OPCUACertificateManager({
79
+ name: "PKI",
80
+ rootFolder: path.join(config, "PKI"),
81
+
82
+ automaticallyAcceptUnknownCertificate: true
83
+ });
84
+ }
85
+
86
+ export class OPCUADiscoveryServer extends OPCUABaseServer {
87
+ private mDnsResponder?: MDNSResponder;
88
+ private readonly registeredServers: RegisterServerMap;
89
+ private bonjourHolder: BonjourHolder;
90
+ private _delayInit?: () => void;
91
+
92
+ constructor(options: OPCUADiscoveryServerOptions) {
93
+ options.serverInfo = options.serverInfo || {};
94
+ const serverInfo = options.serverInfo;
95
+
96
+ serverInfo.applicationType = ApplicationType.DiscoveryServer;
97
+
98
+ serverInfo.applicationUri = serverInfo.applicationUri || defaultApplicationUri;
99
+
100
+ serverInfo.productUri = serverInfo.productUri || defaultProductUri;
101
+
102
+ serverInfo.applicationName = serverInfo.applicationName || {
103
+ text: defaultProductUri,
104
+
105
+ locale: null
106
+ };
107
+
108
+ serverInfo.gatewayServerUri = serverInfo.gatewayServerUri || "";
109
+ serverInfo.discoveryProfileUri = serverInfo.discoveryProfileUri || "";
110
+ serverInfo.discoveryUrls = serverInfo.discoveryUrls || [];
111
+
112
+ options.serverCertificateManager = options.serverCertificateManager || getDefaultCertificateManager();
113
+
114
+ super(options);
115
+
116
+ this.bonjourHolder = new BonjourHolder();
117
+
118
+ // see OPC UA Spec 1.2 part 6 : 7.4 Well Known Addresses
119
+ // opc.tcp://localhost:4840/UADiscovery
120
+ const port = options.port || 4840;
121
+
122
+ this.capabilitiesForMDNS = ["LDS"];
123
+ this.registeredServers = {};
124
+
125
+ this.mDnsResponder = undefined;
126
+
127
+ this._delayInit = async (): Promise<void> => {
128
+ const endPoint = new OPCUAServerEndPoint({
129
+ port,
130
+
131
+ certificateChain: this.getCertificateChain(),
132
+
133
+ certificateManager: this.serverCertificateManager,
134
+
135
+ privateKey: this.getPrivateKey(),
136
+ serverInfo: this.serverInfo
137
+ });
138
+ endPoint.addStandardEndpointDescriptions();
139
+
140
+ this.endpoints.push(endPoint);
141
+
142
+ endPoint.on("message", (message: Message, channel: ServerSecureChannelLayer) => {
143
+ this.on_request(message, channel);
144
+ });
145
+ };
146
+ }
147
+
148
+ public async start(): Promise<void>;
149
+ public start(done: ErrorCallback): void;
150
+ public start(done?: ErrorCallback): any {
151
+ assert(!this.mDnsResponder);
152
+ assert(Array.isArray(this.capabilitiesForMDNS));
153
+
154
+ this._preInitTask.push(async () => {
155
+ await this._delayInit!();
156
+ });
157
+
158
+ super.start((err?: Error | null) => {
159
+ if (err) {
160
+ return done!(err);
161
+ }
162
+ this.mDnsResponder = new MDNSResponder();
163
+ // declare discovery server in bonjour
164
+ this.bonjourHolder._announcedOnMulticastSubnetWithCallback(
165
+ {
166
+ capabilities: this.capabilitiesForMDNS,
167
+ name: this.serverInfo.applicationUri!,
168
+ path: "/DiscoveryServer",
169
+ port: this.endpoints[0].port
170
+ },
171
+ (err2: Error | null) => {
172
+ done!(err2!);
173
+ }
174
+ );
175
+ });
176
+ }
177
+
178
+ public async shutdown(): Promise<void>;
179
+ public shutdown(done: ErrorCallback): void;
180
+ public shutdown(done?: ErrorCallback): any {
181
+ debugLog("stopping announcement of LDS on mDNS");
182
+ this.bonjourHolder._stop_announcedOnMulticastSubnetWithCallback((err?: Error | null) => {
183
+ if (err) {
184
+ console.log("Error ", err.message);
185
+ }
186
+ debugLog("stopping announcement of LDS on mDNS - DONE");
187
+ debugLog("Shutting down Discovery Server");
188
+ super.shutdown(() => {
189
+ if (this.mDnsResponder) {
190
+ this.mDnsResponder.dispose();
191
+ this.mDnsResponder = undefined;
192
+ }
193
+ setTimeout(() => done!(), 200);
194
+ });
195
+ });
196
+ }
197
+
198
+ /**
199
+ * returns the number of registered servers
200
+ */
201
+ public get registeredServerCount(): number {
202
+ return Object.keys(this.registeredServers).length;
203
+ }
204
+
205
+ public getServers(channel: ServerSecureChannelLayer): ApplicationDescription[] {
206
+ this.serverInfo.discoveryUrls = this.getDiscoveryUrls();
207
+
208
+ const servers: ApplicationDescription[] = [this.serverInfo];
209
+
210
+ for (const registered_server of Object.values(this.registeredServers)) {
211
+ const serverInfo: ApplicationDescription = new ApplicationDescription(registered_server.serverInfo);
212
+ servers.push(serverInfo);
213
+ }
214
+
215
+ return servers;
216
+ }
217
+
218
+ protected _on_RegisterServer2Request(message: Message, channel: ServerSecureChannelLayer) {
219
+ assert(message.request instanceof RegisterServer2Request);
220
+ const request = message.request as RegisterServer2Request;
221
+
222
+ assert(request.schema.name === "RegisterServer2Request");
223
+
224
+ request.discoveryConfiguration = request.discoveryConfiguration || [];
225
+ this.__internalRegisterServerWithCallback(
226
+ RegisterServer2Response,
227
+ request.server,
228
+ request.discoveryConfiguration as MdnsDiscoveryConfiguration[],
229
+ (err: Error | null, response?: Response) => {
230
+ // istanbul ignore next
231
+ if (err) {
232
+ // tslint:disable-next-line: no-console
233
+ console.log("What shall I do ?", err.message);
234
+ // tslint:disable-next-line: no-console
235
+ console.log(err);
236
+ let additional_messages = [];
237
+ additional_messages.push("EXCEPTION CAUGHT WHILE PROCESSING REQUEST !!! " + request.schema.name);
238
+ additional_messages.push(err.message);
239
+ if (err.stack) {
240
+ additional_messages = additional_messages.concat(err.stack.split("\n"));
241
+ }
242
+
243
+ response = OPCUADiscoveryServer.makeServiceFault(StatusCodes.BadInternalError, additional_messages);
244
+ channel.send_response("MSG", response, message);
245
+ } else {
246
+ channel.send_response("MSG", response!, message);
247
+ }
248
+ }
249
+ );
250
+ }
251
+
252
+ protected _on_RegisterServerRequest(message: Message, channel: ServerSecureChannelLayer) {
253
+ assert(message.request instanceof RegisterServerRequest);
254
+ const request = message.request as RegisterServerRequest;
255
+ assert(request.schema.name === "RegisterServerRequest");
256
+ this.__internalRegisterServerWithCallback(
257
+ RegisterServerResponse,
258
+ request.server,
259
+ undefined,
260
+ (err: Error | null, response?: Response) => {
261
+ channel.send_response("MSG", response!, message);
262
+ }
263
+ );
264
+ }
265
+
266
+ protected _on_FindServersOnNetworkRequest(message: Message, channel: ServerSecureChannelLayer) {
267
+ // from OPCUA 1.04 part 4
268
+ // This Service returns the Servers known to a Discovery Server. Unlike FindServer, this Service is
269
+ // only implemented by Discovery Servers.
270
+ // The Client may reduce the number of results returned by specifying filter criteria. An empty list is
271
+ // returned if no Server matches the criteria specified by the Client.
272
+ // This Service shall not require message security but it may require transport layer security.
273
+ // Each time the Discovery Server creates or updates a record in its cache it shall assign a
274
+ // monotonically increasing identifier to the record. This allows Clients to request records in batches
275
+ // by specifying the identifier for the last record received in the last call to FindServersOnNetwork.
276
+ // To support this the Discovery Server shall return records in numerical order starting from the
277
+ // lowest record identifier. The Discovery Server shall also return the last time the counter was reset
278
+ // for example due to a restart of the Discovery Server. If a Client detects that this time is more
279
+ // recent than the last time the Client called the Service it shall call the Service again with a
280
+ // startingRecordId of 0.
281
+ // This Service can be used without security and it is therefore vulnerable to Denial Of Service
282
+ // (DOS) attacks. A Server should minimize the amount of processing required to send the response
283
+ // for this Service. This can be achieved by preparing the result in advance
284
+
285
+ assert(message.request instanceof FindServersOnNetworkRequest);
286
+ const request = message.request as FindServersOnNetworkRequest;
287
+
288
+ assert(request.schema.name === "FindServersOnNetworkRequest");
289
+
290
+ function sendError(statusCode: StatusCode) {
291
+ const response1 = new FindServersOnNetworkResponse({ responseHeader: { serviceResult: statusCode } });
292
+ return channel.send_response("MSG", response1, message);
293
+ }
294
+
295
+ // startingRecordId Counter Only records with an identifier greater than this number will be
296
+ // returned.
297
+ // Specify 0 to start with the first record in the cache.
298
+ // maxRecordsToReturn UInt32 The maximum number of records to return in the response.
299
+ // 0 indicates that there is no limit.
300
+ // serverCapabilityFilter[] String List of Server capability filters. The set of allowed server capabilities
301
+ // are defined in Part 12.
302
+ // Only records with all of the specified server capabilities are
303
+ // returned.
304
+ // The comparison is case insensitive.
305
+ // If this list is empty then no filtering is performed
306
+
307
+ // ------------------------
308
+
309
+ // The last time the counters were reset.
310
+ const lastCounterResetTime = new Date();
311
+
312
+ // servers[] ServerOnNetwork List of DNS service records that meet criteria specified in the
313
+ // request. This list is empty if no Servers meet the criteria
314
+ const servers = [];
315
+
316
+ request.serverCapabilityFilter = request.serverCapabilityFilter || [];
317
+ const serverCapabilityFilter: string = request.serverCapabilityFilter
318
+ .map((x: UAString) => x!.toUpperCase())
319
+ .sort()
320
+ .join(" ");
321
+
322
+ debugLog(" startingRecordId = ", request.startingRecordId);
323
+
324
+ if (this.mDnsResponder) {
325
+ for (const server of this.mDnsResponder.registeredServers) {
326
+ if (server.recordId <= request.startingRecordId) {
327
+ continue;
328
+ }
329
+ if (!hasCapabilities(server.serverCapabilities, serverCapabilityFilter)) {
330
+ continue;
331
+ }
332
+ servers.push(server);
333
+ if (servers.length === request.maxRecordsToReturn) {
334
+ break;
335
+ }
336
+ }
337
+ }
338
+ const response = new FindServersOnNetworkResponse({
339
+ lastCounterResetTime, // UtcTime The last time the counters were reset
340
+ servers
341
+ });
342
+ channel.send_response("MSG", response, message);
343
+ }
344
+
345
+ private async __internalRegisterServerWithCallback(
346
+ RegisterServerXResponse: any /* RegisterServer2Response | RegisterServerResponse */,
347
+ rawServer: RegisteredServer,
348
+ discoveryConfigurations: MdnsDiscoveryConfiguration[] | undefined,
349
+ callback: (err: Error | null, response?: Response) => void
350
+ ) {
351
+ // istanbul ignore next
352
+ callback(new Error("internal Error"));
353
+ }
354
+
355
+ private async __internalRegisterServer(
356
+ RegisterServerXResponse: any /* RegisterServer2Response | RegisterServerResponse */,
357
+ rawServer: RegisteredServer,
358
+ discoveryConfigurations?: MdnsDiscoveryConfiguration[]
359
+ ): Promise<Response> {
360
+ const server = rawServer as any as RegisteredServerExtended;
361
+
362
+ if (!discoveryConfigurations) {
363
+ discoveryConfigurations = [
364
+ new MdnsDiscoveryConfiguration({
365
+ mdnsServerName: undefined,
366
+ serverCapabilities: ["NA"]
367
+ })
368
+ ];
369
+ }
370
+
371
+ function sendError(statusCode: StatusCode): Response {
372
+ debugLog(chalk.red("_on_RegisterServer(2)Request error"), statusCode.toString());
373
+ const response1 = new RegisterServerXResponse({
374
+ responseHeader: { serviceResult: statusCode }
375
+ });
376
+ return response1;
377
+ }
378
+
379
+ async function _stop_announcedOnMulticastSubnet(conf: MdnsDiscoveryConfiguration): Promise<void> {
380
+ const b = (conf as any).bonjourHolder as BonjourHolder;
381
+ await b._stop_announcedOnMulticastSubnet();
382
+ (conf as any).bonjourHolder = undefined;
383
+ }
384
+
385
+ async function _announcedOnMulticastSubnet(conf: MdnsDiscoveryConfiguration, announcement: Announcement): Promise<void> {
386
+ let b = (conf as any).bonjourHolder as BonjourHolder;
387
+ if (b && b.announcement) {
388
+ if (sameAnnouncement(b.announcement, announcement)) {
389
+ debugLog("Configuration ", conf.mdnsServerName, " has not changed !");
390
+ // nothing to do
391
+ return;
392
+ } else {
393
+ debugLog("Configuration ", conf.mdnsServerName, " HAS changed !");
394
+ debugLog(" Was ", b.announcement);
395
+ debugLog(" is ", announcement);
396
+ }
397
+ await _stop_announcedOnMulticastSubnet(conf);
398
+ }
399
+ b = new BonjourHolder();
400
+ (conf as any).bonjourHolder = b;
401
+ await b._announcedOnMulticastSubnet(announcement);
402
+ }
403
+
404
+ async function dealWithDiscoveryConfiguration(
405
+ previousConfMap: any,
406
+ server1: RegisteredServer,
407
+ serverInfo: ApplicationDescriptionOptions,
408
+ discoveryConfiguration: MdnsDiscoveryConfiguration
409
+ ): Promise<StatusCode> {
410
+ // mdnsServerName String The name of the Server when it is announced via mDNS.
411
+ // See Part 12 for the details about mDNS. This string shall be less than 64 bytes.
412
+ // If not specified the first element of the serverNames array is used
413
+ // (truncated to 63 bytes if necessary).
414
+ // serverCapabilities [] String The set of Server capabilities supported by the Server.
415
+ // A Server capability is a short identifier for a feature
416
+ // The set of allowed Server capabilities are defined in Part 12.
417
+ discoveryConfiguration.mdnsServerName = discoveryConfiguration.mdnsServerName || server1.serverNames![0].text;
418
+
419
+ serverInfo.discoveryUrls = serverInfo.discoveryUrls || [];
420
+
421
+ const endpointUrl = serverInfo.discoveryUrls[0]!;
422
+ const parsedUrl = url.parse(endpointUrl);
423
+
424
+ discoveryConfiguration.serverCapabilities = discoveryConfiguration.serverCapabilities || [];
425
+ const announcement = {
426
+ capabilities: discoveryConfiguration.serverCapabilities.map((x: UAString) => x!) || ["DA"],
427
+ name: discoveryConfiguration.mdnsServerName!,
428
+ path: parsedUrl.pathname || "/",
429
+ port: parseInt(parsedUrl.port!, 10)
430
+ };
431
+
432
+ if (previousConfMap[discoveryConfiguration.mdnsServerName!]) {
433
+ // configuration already exists
434
+ debugLog("Configuration ", discoveryConfiguration.mdnsServerName, " already exists !");
435
+ const prevConf = previousConfMap[discoveryConfiguration.mdnsServerName!];
436
+ delete previousConfMap[discoveryConfiguration.mdnsServerName!];
437
+ (discoveryConfiguration as any).bonjourHolder = prevConf.bonjourHolder;
438
+ }
439
+
440
+ // let's announce the server on the multicast DNS
441
+ await _announcedOnMulticastSubnet(discoveryConfiguration, announcement);
442
+ return StatusCodes.Good;
443
+ }
444
+
445
+ // check serverType is valid
446
+ if (!_isValidServerType(server.serverType)) {
447
+ debugLog("Invalid server Type", ApplicationType[server.serverType]);
448
+ return sendError(StatusCodes.BadInvalidArgument);
449
+ }
450
+
451
+ if (!server.serverUri) {
452
+ debugLog("Missing serverURI");
453
+ return sendError(StatusCodes.BadInvalidArgument);
454
+ }
455
+
456
+ // BadServerUriInvalid
457
+ // TODO
458
+ server.serverNames = server.serverNames || [];
459
+ // BadServerNameMissing
460
+ if (server.serverNames.length === 0 || !server.serverNames[0].text) {
461
+ return sendError(StatusCodes.BadServerNameMissing);
462
+ }
463
+
464
+ // BadDiscoveryUrlMissing
465
+ server.discoveryUrls = server.discoveryUrls || [];
466
+ if (server.discoveryUrls.length === 0 || !server.discoveryUrls[0]) {
467
+ return sendError(StatusCodes.BadDiscoveryUrlMissing);
468
+ }
469
+
470
+ const key = server.serverUri;
471
+ let configurationResults: StatusCode[] | null = null;
472
+
473
+ if (server.isOnline) {
474
+ debugLog(chalk.cyan(" registering server : "), chalk.yellow(server.serverUri));
475
+
476
+ // prepare serverInfo which will be used by FindServers
477
+ const serverInfo: ApplicationDescriptionOptions = {
478
+ applicationName: server.serverNames[0], // which one shall we use ?
479
+ applicationType: server.serverType,
480
+ applicationUri: server.serverUri,
481
+ discoveryUrls: server.discoveryUrls,
482
+ gatewayServerUri: server.gatewayServerUri,
483
+ productUri: server.productUri
484
+ // XXX ?????? serverInfo.discoveryProfileUri = serverInfo.discoveryProfileUri;
485
+ };
486
+
487
+ const previousConfMap: any = [];
488
+ if (this.registeredServers[key]) {
489
+ // server already exists and must only be updated
490
+ const previousServer = this.registeredServers[key];
491
+
492
+ for (const conf of previousServer.discoveryConfiguration!) {
493
+ previousConfMap[conf.mdnsServerName!] = conf;
494
+ }
495
+ }
496
+ this.registeredServers[key] = server;
497
+
498
+ // xx server.semaphoreFilePath = server.semaphoreFilePath;
499
+ // xx server.serverNames = server.serverNames;
500
+ server.serverInfo = serverInfo;
501
+ server.discoveryConfiguration = discoveryConfigurations;
502
+
503
+ assert(discoveryConfigurations);
504
+
505
+ configurationResults = [];
506
+ for (const conf of discoveryConfigurations) {
507
+ const statusCode = await dealWithDiscoveryConfiguration(previousConfMap, server, serverInfo, conf);
508
+ configurationResults.push(statusCode);
509
+ }
510
+ // now also unregister unprocessed
511
+ if (Object.keys(previousConfMap).length !== 0) {
512
+ debugLog(" Warning some conf need to be removed !");
513
+ }
514
+ } else {
515
+ // server is announced offline
516
+ if (key in this.registeredServers) {
517
+ const server1 = this.registeredServers[key];
518
+ debugLog(chalk.cyan("unregistering server : "), chalk.yellow(server1.serverUri!));
519
+ configurationResults = [];
520
+
521
+ discoveryConfigurations = server1.discoveryConfiguration || [];
522
+
523
+ for (const conf of discoveryConfigurations) {
524
+ await _stop_announcedOnMulticastSubnet(conf);
525
+ configurationResults.push(StatusCodes.Good);
526
+ }
527
+ delete this.registeredServers[key];
528
+ }
529
+ }
530
+
531
+ const response = new RegisterServerXResponse({
532
+ configurationResults
533
+ });
534
+ return response;
535
+ }
536
+ }
537
+
538
+ /*== private
539
+ * returns true if the serverType can be added to a discovery server.
540
+ * @param serverType
541
+ * @return {boolean}
542
+ * @private
543
+ */
544
+ function _isValidServerType(serverType: ApplicationType): boolean {
545
+ switch (serverType) {
546
+ case ApplicationType.Client:
547
+ return false;
548
+ case ApplicationType.Server:
549
+ case ApplicationType.ClientAndServer:
550
+ case ApplicationType.DiscoveryServer:
551
+ return true;
552
+ }
553
+ return false;
554
+ }
555
+
556
+ (OPCUADiscoveryServer as any).prototype.__internalRegisterServerWithCallback = callbackify(
557
+ (OPCUADiscoveryServer as any).prototype.__internalRegisterServer
558
+ );
559
+
560
+ // tslint:disable-next-line: no-var-requires
561
+ const thenify = require("thenify");
562
+ const opts = { multiArgs: false };
563
+ OPCUADiscoveryServer.prototype.start = thenify.withCallback(OPCUADiscoveryServer.prototype.start, opts);
564
+ OPCUADiscoveryServer.prototype.shutdown = thenify.withCallback(OPCUADiscoveryServer.prototype.shutdown, opts);
@@ -0,0 +1 @@
1
+ require("node-opcua-pki/bin/crypto_create_CA");