node-opcua-pki 3.0.2 → 3.1.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.
Files changed (53) hide show
  1. package/.ignore +6 -6
  2. package/.prettierrc +5 -5
  3. package/LICENSE +22 -22
  4. package/bin/crypto_create_CA.js +0 -0
  5. package/bin/crypto_create_CA_config.example.js +18 -18
  6. package/bin/install_prerequisite.js +9 -9
  7. package/dist/crypto_create_CA.d.ts +2 -2
  8. package/dist/crypto_create_CA.js +897 -897
  9. package/dist/index.d.ts +6 -6
  10. package/dist/index.js +44 -44
  11. package/dist/misc/applicationurn.d.ts +1 -1
  12. package/dist/misc/applicationurn.js +46 -46
  13. package/dist/misc/hostname.d.ts +8 -8
  14. package/dist/misc/hostname.js +102 -102
  15. package/dist/misc/install_prerequisite.d.ts +9 -9
  16. package/dist/misc/install_prerequisite.js +363 -360
  17. package/dist/misc/install_prerequisite.js.map +1 -1
  18. package/dist/misc/subject.d.ts +26 -26
  19. package/dist/misc/subject.js +121 -121
  20. package/dist/pki/certificate_authority.d.ts +61 -61
  21. package/dist/pki/certificate_authority.js +481 -481
  22. package/dist/pki/certificate_manager.d.ts +144 -144
  23. package/dist/pki/certificate_manager.js +883 -883
  24. package/dist/pki/certificate_manager.js.map +1 -1
  25. package/dist/pki/common.d.ts +5 -5
  26. package/dist/pki/common.js +2 -2
  27. package/dist/pki/templates/ca_config_template.cnf.d.ts +2 -2
  28. package/dist/pki/templates/ca_config_template.cnf.js +129 -129
  29. package/dist/pki/templates/simple_config_template.cnf.d.ts +2 -2
  30. package/dist/pki/templates/simple_config_template.cnf.js +75 -75
  31. package/dist/pki/toolbox.d.ts +160 -160
  32. package/dist/pki/toolbox.js +699 -699
  33. package/dist/pki/toolbox_pfx.js +18 -18
  34. package/lib/crypto_create_CA.ts +1135 -1135
  35. package/lib/index.ts +28 -28
  36. package/lib/misc/applicationurn.ts +45 -45
  37. package/lib/misc/hostname.ts +89 -89
  38. package/lib/misc/install_prerequisite.ts +454 -454
  39. package/lib/misc/subject.ts +141 -141
  40. package/lib/pki/certificate_manager.ts +1 -1
  41. package/lib/pki/common.ts +5 -5
  42. package/lib/pki/templates/ca_config_template.cnf.ts +129 -129
  43. package/lib/pki/templates/simple_config_template.cnf.ts +75 -75
  44. package/lib/pki/toolbox_pfx.ts +19 -19
  45. package/package.json +89 -89
  46. package/readme.md +214 -214
  47. package/tsconfig.json +20 -20
  48. package/dist/misc/fs.d.ts +0 -24
  49. package/dist/misc/fs.js +0 -21
  50. package/dist/misc/fs.js.map +0 -1
  51. package/dist/misc/get_default_filesystem.d.ts +0 -2
  52. package/dist/misc/get_default_filesystem.js +0 -9
  53. package/dist/misc/get_default_filesystem.js.map +0 -1
@@ -1,1135 +1,1135 @@
1
- /* eslint-disable @typescript-eslint/no-unused-vars */
2
- // ---------------------------------------------------------------------------------------------------------------------
3
- // node-opcua
4
- // ---------------------------------------------------------------------------------------------------------------------
5
- // Copyright (c) 2014-2022 - Etienne Rossignon - etienne.rossignon (at) gadz.org
6
- // Copyright (c) 2022 - Sterfive.com
7
- // ---------------------------------------------------------------------------------------------------------------------
8
- //
9
- // This project is licensed under the terms of the MIT license.
10
- //
11
- // Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated
12
- // documentation files (the "Software"), to deal in the Software without restriction, including without limitation the
13
- // rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to
14
- // permit persons to whom the Software is furnished to do so, subject to the following conditions:
15
- //
16
- // The above copyright notice and this permission notice shall be included in all copies or substantial portions of the
17
- // Software.
18
- //
19
- // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
20
- // WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
21
- // COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
22
- // OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
23
- // ---------------------------------------------------------------------------------------------------------------------
24
- // Error.stackTraceLimit = Infinity;
25
- // tslint:disable:variable-name
26
- // tslint:disable:no-console
27
- // tslint:disable:object-literal-sort-keys
28
- // tslint:disable:no-shadowed-variable
29
-
30
- import * as assert from "assert";
31
- import * as chalk from "chalk";
32
- import * as rimraf from "rimraf";
33
- import * as fs from "fs";
34
- import * as path from "path";
35
- import * as os from "os";
36
-
37
- import { callbackify, promisify } from "util";
38
-
39
- import { makeApplicationUrn } from "./misc/applicationurn";
40
- import { extractFullyQualifiedDomainName, getFullyQualifiedDomainName } from "./misc/hostname";
41
- import { Subject, SubjectOptions } from "./misc/subject";
42
- import { CertificateAuthority, defaultSubject } from "./pki/certificate_authority";
43
- import { CertificateManager, CreateSelfSignCertificateParam1 } from "./pki/certificate_manager";
44
- import { ErrorCallback, Filename, KeySize } from "./pki/common";
45
- import {
46
- createCertificateSigningRequest,
47
- CreateCertificateSigningRequestWithConfigOptions,
48
- createPrivateKey,
49
- displayChapter,
50
- displaySubtitle,
51
- displayTitle,
52
- dumpCertificate,
53
- ensure_openssl_installed,
54
- fingerprint,
55
- g_config,
56
- getPublicKeyFromPrivateKey,
57
- make_path,
58
- mkdir,
59
- setEnv,
60
- toDer,
61
- debugLog,
62
- } from "./pki/toolbox";
63
-
64
- // see https://github.com/yargs/yargs/issues/781
65
- import * as commands from "yargs";
66
- // eslint-disable-next-line @typescript-eslint/no-var-requires
67
- const { hideBin } = require("yargs/helpers");
68
- // eslint-disable-next-line @typescript-eslint/no-var-requires
69
- const argv = require("yargs/yargs")(hideBin(process.argv));
70
-
71
- const epilog = "Copyright (c) sterfive - node-opcua - 2017-2022";
72
-
73
- // ------------------------------------------------- some useful dates
74
- function get_offset_date(date: Date, nbDays: number): Date {
75
- const d = new Date(date.getTime());
76
- d.setDate(d.getDate() + nbDays);
77
- return d;
78
- }
79
-
80
- const today = new Date();
81
- const yesterday = get_offset_date(today, -1);
82
- const two_years_ago = get_offset_date(today, -2 * 365);
83
- const next_year = get_offset_date(today, 365);
84
-
85
- interface LocalConfig {
86
- CAFolder?: string;
87
- PKIFolder?: string;
88
-
89
- keySize?: KeySize;
90
-
91
- subject?: SubjectOptions | string;
92
-
93
- certificateDir?: Filename;
94
-
95
- privateKey?: Filename;
96
-
97
- applicationUri?: string;
98
-
99
- outputFile?: string;
100
-
101
- altNames?: string[];
102
- dns?: string[];
103
- ip?: string[];
104
-
105
- startDate?: Date;
106
- validity?: number;
107
- }
108
-
109
- let gLocalConfig: LocalConfig = {};
110
-
111
- let g_certificateAuthority: CertificateAuthority; // the Certificate Authority
112
-
113
- /***
114
- *
115
- *
116
- * prerequisites :
117
- * g_config.CAFolder : the folder of the CA
118
- */
119
- async function construct_CertificateAuthority(subject: string) {
120
- // verify that g_config file has been loaded
121
- assert(typeof gLocalConfig.CAFolder === "string", "expecting a CAFolder in config");
122
- assert(typeof gLocalConfig.keySize === "number", "expecting a keySize in config");
123
-
124
- if (!g_certificateAuthority) {
125
- g_certificateAuthority = new CertificateAuthority({
126
- keySize: gLocalConfig.keySize,
127
- location: gLocalConfig.CAFolder,
128
- subject,
129
- });
130
- await g_certificateAuthority.initialize();
131
- }
132
- }
133
-
134
- let certificateManager: CertificateManager; // the Certificate Manager
135
- /***
136
- *
137
- *
138
- * prerequisites :
139
- * g_config.PKIFolder : the folder of the PKI
140
- */
141
- async function construct_CertificateManager() {
142
- assert(typeof gLocalConfig.PKIFolder === "string", "expecting a PKIFolder in config");
143
-
144
- if (!certificateManager) {
145
- certificateManager = new CertificateManager({
146
- keySize: gLocalConfig.keySize,
147
- location: gLocalConfig.PKIFolder,
148
- });
149
- await certificateManager.initialize();
150
- }
151
- }
152
-
153
- function displayConfig(config: { [key: string]: { toString: () => string } }) {
154
- function w(str: string, l: number): string {
155
- return (str + " ").substring(0, l);
156
- }
157
-
158
- console.log(chalk.yellow(" configuration = "));
159
-
160
- for (const [key, value] of Object.entries(config)) {
161
- console.log(" " + chalk.yellow(w(key, 30)) + " : " + chalk.cyan(value.toString()));
162
- }
163
- }
164
-
165
- function default_template_content(): string {
166
- // istanbul ignore next
167
- if ((process as any).pkg && (process as any).pkg.entrypoint) {
168
- // we are using PKG compiled package !
169
-
170
- // console.log("___filename", __filename);
171
- // console.log("__dirname", __dirname);
172
- // console.log("process.pkg.entrypoint", (process as any).pkg.entrypoint);
173
- const a = fs.readFileSync(path.join(__dirname, "../../bin/crypto_create_CA_config.example.js"), "utf8");
174
- console.log(a);
175
- return a;
176
- }
177
- function find_default_config_template() {
178
- const rootFolder = find_module_root_folder();
179
- let default_config_template = path.join(rootFolder, "bin", path.basename(__filename, ".js") + "_config.example.js");
180
-
181
- if (!fs.existsSync(default_config_template)) {
182
- default_config_template = path.join(__dirname, "..", path.basename(__filename, ".js") + "_config.example.js");
183
-
184
- if (!fs.existsSync(default_config_template)) {
185
- default_config_template = path.join(__dirname, "../bin/" + path.basename(__filename, ".js") + "_config.example.js");
186
- }
187
- }
188
- return default_config_template;
189
- }
190
- const default_config_template = find_default_config_template();
191
- assert(fs.existsSync(default_config_template));
192
- const default_config_template_content = fs.readFileSync(default_config_template, "utf8");
193
- return default_config_template_content;
194
- }
195
-
196
- /**
197
- *
198
- */
199
- function find_module_root_folder() {
200
- let rootFolder = path.join(__dirname);
201
-
202
- for (let i = 0; i < 4; i++) {
203
- if (fs.existsSync(path.join(rootFolder, "package.json"))) {
204
- return rootFolder;
205
- }
206
- rootFolder = path.join(rootFolder, "..");
207
- }
208
-
209
- assert(fs.existsSync(path.join(rootFolder, "package.json")), "root folder must have a package.json file");
210
- return rootFolder;
211
- }
212
-
213
- interface IReadConfigurationOpts {
214
- root: string;
215
- silent?: boolean;
216
- subject?: string;
217
- CAFolder?: string;
218
- PKIFolder?: string;
219
- privateKey?: string;
220
- applicationUri?: string;
221
- output?: string;
222
- altNames?: string;
223
- dns?: string;
224
- ip?: string;
225
- keySize?: KeySize;
226
- validity?: number;
227
- }
228
- interface IReadConfigurationOpts2 extends IReadConfigurationOpts {
229
- clean: boolean;
230
- dev: boolean;
231
- }
232
- interface IReadConfigurationOpts3 extends IReadConfigurationOpts {
233
- subject: string;
234
- }
235
- interface IReadConfigurationOpts4 extends IReadConfigurationOpts {
236
- selfSigned: boolean;
237
- }
238
-
239
- interface IReadConfigurationOpts5 extends IReadConfigurationOpts {
240
- certificateFile: string;
241
- }
242
-
243
- /* eslint complexity:off, max-statements:off */
244
- async function readConfiguration(argv: IReadConfigurationOpts) {
245
- if (argv.silent) {
246
- g_config.silent = true;
247
- } else {
248
- g_config.silent = false;
249
- }
250
-
251
- const fqdn = await extractFullyQualifiedDomainName();
252
- const hostname = os.hostname();
253
- let certificateDir: string;
254
-
255
- function performSubstitution(str: string): string {
256
- str = str.replace("{CWD}", process.cwd());
257
- if (certificateDir) {
258
- str = str.replace("{root}", certificateDir);
259
- }
260
- if (gLocalConfig && gLocalConfig.PKIFolder) {
261
- str = str.replace("{PKIFolder}", gLocalConfig.PKIFolder);
262
- }
263
- str = str.replace("{hostname}", hostname);
264
- str = str.replace("%FQDN%", fqdn);
265
- return str;
266
- }
267
-
268
- function prepare(file: Filename): Filename {
269
- const tmp = path.resolve(performSubstitution(file));
270
- return make_path(tmp);
271
- }
272
-
273
- // ------------------------------------------------------------------------------------------------------------
274
- certificateDir = argv.root;
275
- assert(typeof certificateDir === "string");
276
-
277
- certificateDir = prepare(certificateDir);
278
- mkdir(certificateDir);
279
- assert(fs.existsSync(certificateDir));
280
-
281
- // ------------------------------------------------------------------------------------------------------------
282
- const default_config = path.join(certificateDir, "config.js");
283
-
284
- if (!fs.existsSync(default_config)) {
285
- // copy
286
- debugLog(chalk.yellow(" Creating default g_config file "), chalk.cyan(default_config));
287
- const default_config_template_content = default_template_content();
288
- fs.writeFileSync(default_config, default_config_template_content);
289
- } else {
290
- debugLog(chalk.yellow(" using g_config file "), chalk.cyan(default_config));
291
- }
292
- if (!fs.existsSync(default_config)) {
293
- console.log(chalk.redBright(" cannot find config file ", default_config));
294
- }
295
-
296
- // see http://stackoverflow.com/questions/94445/using-openssl-what-does-unable-to-write-random-state-mean
297
- // set random file to be random.rnd in the same folder as the g_config file
298
- const defaultRandomFile = path.join(path.dirname(default_config), "random.rnd");
299
- setEnv("RANDFILE", defaultRandomFile);
300
-
301
- /* eslint global-require: 0*/
302
- gLocalConfig = require(default_config);
303
-
304
- gLocalConfig.subject = new Subject(gLocalConfig.subject || "");
305
-
306
- // if subject is provided on the command line , it has hight priority
307
- if (argv.subject) {
308
- gLocalConfig.subject = new Subject(argv.subject);
309
- }
310
-
311
- // istanbul ignore next
312
- if (!gLocalConfig.subject.commonName) {
313
- throw new Error("subject must have a Common Name");
314
- }
315
-
316
- gLocalConfig.certificateDir = certificateDir;
317
-
318
- // ------------------------------------------------------------------------------------------------------------
319
- let CAFolder = argv.CAFolder || path.join(certificateDir, "CA");
320
- CAFolder = prepare(CAFolder);
321
- gLocalConfig.CAFolder = CAFolder;
322
-
323
- // ------------------------------------------------------------------------------------------------------------
324
- gLocalConfig.PKIFolder = path.join(gLocalConfig.certificateDir, "PKI");
325
- if (argv.PKIFolder) {
326
- gLocalConfig.PKIFolder = prepare(argv.PKIFolder);
327
- }
328
- gLocalConfig.PKIFolder = prepare(gLocalConfig.PKIFolder);
329
- if (argv.privateKey) {
330
- gLocalConfig.privateKey = prepare(argv.privateKey);
331
- }
332
-
333
- if (argv.applicationUri) {
334
- gLocalConfig.applicationUri = performSubstitution(argv.applicationUri);
335
- }
336
-
337
- if (argv.output) {
338
- gLocalConfig.outputFile = argv.output;
339
- }
340
-
341
- gLocalConfig.altNames = [];
342
- if (argv.altNames) {
343
- gLocalConfig.altNames = argv.altNames.split(";");
344
- }
345
- gLocalConfig.dns = [getFullyQualifiedDomainName()];
346
- if (argv.dns) {
347
- gLocalConfig.dns = argv.dns.split(",").map(performSubstitution);
348
- }
349
- gLocalConfig.ip = [];
350
- if (argv.ip) {
351
- gLocalConfig.ip = argv.ip.split(",");
352
- }
353
- if (argv.keySize) {
354
- const v = argv.keySize;
355
- if (v !== 1024 && v !== 2048 && v !== 3072 && v !== 4096) {
356
- throw new Error("invalid keysize specified " + v + " should be 1024,2048,3072 or 4096");
357
- }
358
- gLocalConfig.keySize = argv.keySize;
359
- }
360
-
361
- if (argv.validity) {
362
- gLocalConfig.validity = argv.validity;
363
- }
364
- // xx displayConfig(g_config);
365
- // ------------------------------------------------------------------------------------------------------------
366
- }
367
-
368
- interface OptionMap {
369
- [key: string]: commands.Options;
370
- }
371
-
372
- function add_standard_option(options: OptionMap, optionName: string) {
373
- switch (optionName) {
374
- case "root":
375
- options.root = {
376
- alias: "r",
377
- type: "string",
378
- default: "{CWD}/certificates",
379
- describe: "the location of the Certificate folder",
380
- };
381
- break;
382
-
383
- case "CAFolder":
384
- options.CAFolder = {
385
- alias: "c",
386
- type: "string",
387
- default: "{root}/CA",
388
- describe: "the location of the Certificate Authority folder",
389
- };
390
- break;
391
-
392
- case "PKIFolder":
393
- options.PKIFolder = {
394
- type: "string",
395
- default: "{root}/PKI",
396
- describe: "the location of the Public Key Infrastructure",
397
- };
398
- break;
399
-
400
- case "silent":
401
- options.silent = {
402
- alias: "s",
403
- type: "boolean",
404
- default: false,
405
- describe: "minimize output",
406
- };
407
- break;
408
-
409
- case "privateKey":
410
- options.privateKey = {
411
- alias: "p",
412
- type: "string",
413
- default: "{PKIFolder}/own/private_key.pem",
414
- describe: "the private key to use to generate certificate",
415
- };
416
- break;
417
-
418
- case "keySize":
419
- options.keySize = {
420
- alias: ["k", "keyLength"],
421
- type: "number",
422
- default: 2048,
423
- describe: "the private key size in bits (1024|2048|3072|4096)",
424
- };
425
- break;
426
- default:
427
- throw Error("Unknown option " + optionName);
428
- }
429
- }
430
-
431
- function on_completion(err: Error | null | undefined, done: ErrorCallback) {
432
- assert(typeof done === "function", "expecting function");
433
- // istanbul ignore next
434
- if (err) {
435
- console.log(chalk.redBright("ERROR : ") + err.message);
436
- }
437
- done();
438
- }
439
-
440
- async function createDefaultCertificate(
441
- base_name: string,
442
- prefix: string,
443
- key_length: KeySize,
444
- applicationUri: string,
445
- dev: boolean
446
- ) {
447
- // possible key length in bits
448
- assert(key_length === 1024 || key_length === 2048 || key_length === 3072 || key_length === 4096);
449
-
450
- const private_key_file = make_path(base_name, prefix + "key_" + key_length + ".pem");
451
- const public_key_file = make_path(base_name, prefix + "public_key_" + key_length + ".pub");
452
- const certificate_file = make_path(base_name, prefix + "cert_" + key_length + ".pem");
453
- const certificate_file_outofdate = make_path(base_name, prefix + "cert_" + key_length + "_outofdate.pem");
454
- const certificate_file_not_active_yet = make_path(base_name, prefix + "cert_" + key_length + "_not_active_yet.pem");
455
- const certificate_revoked = make_path(base_name, prefix + "cert_" + key_length + "_revoked.pem");
456
- const self_signed_certificate_file = make_path(base_name, prefix + "selfsigned_cert_" + key_length + ".pem");
457
-
458
- const fqdn = getFullyQualifiedDomainName();
459
- const hostname = os.hostname();
460
- const dns: string[] = [
461
- // for conformance reason, localhost shall not be present in the DNS field of COP
462
- // ***FORBIDEN** "localhost",
463
- getFullyQualifiedDomainName(),
464
- ];
465
- if (hostname !== fqdn) {
466
- dns.push(hostname);
467
- }
468
-
469
- const ip: string[] = [];
470
-
471
- async function createCertificateIfNotExist(
472
- certificate: Filename,
473
- private_key: Filename,
474
- applicationUri: string,
475
- startDate: Date,
476
- validity: number
477
- ): Promise<string> {
478
- // istanbul ignore next
479
- if (fs.existsSync(certificate)) {
480
- console.log(chalk.yellow(" certificate"), chalk.cyan(certificate), chalk.yellow(" already exists => skipping"));
481
- return "";
482
- } else {
483
- return await createCertificate(certificate, private_key, applicationUri, startDate, validity);
484
- }
485
- }
486
-
487
- async function createCertificate(
488
- certificate: Filename,
489
- privateKey: Filename,
490
- applicationUri: string,
491
- startDate: Date,
492
- validity: number
493
- ): Promise<string> {
494
- const certificateSigningRequestFile = certificate + ".csr";
495
-
496
- const configFile = make_path(base_name, "../certificates/PKI/own/openssl.cnf");
497
-
498
- const dns = [os.hostname()];
499
- const ip = ["127.0.0.1"];
500
-
501
- const params: CreateCertificateSigningRequestWithConfigOptions = {
502
- applicationUri,
503
- privateKey,
504
- rootDir: ".",
505
- configFile,
506
- dns,
507
- ip,
508
- };
509
-
510
- // create CSR
511
- await promisify(createCertificateSigningRequest)(certificateSigningRequestFile, params);
512
-
513
- return await g_certificateAuthority.signCertificateRequest(certificate, certificateSigningRequestFile, {
514
- applicationUri,
515
- dns,
516
- ip,
517
- startDate,
518
- validity,
519
- });
520
- }
521
-
522
- async function createSelfSignedCertificate(
523
- certificate: Filename,
524
- private_key: Filename,
525
- applicationUri: string,
526
- startDate: Date,
527
- validity: number
528
- ) {
529
- await g_certificateAuthority.createSelfSignedCertificate(certificate, private_key, {
530
- applicationUri,
531
- dns,
532
- ip,
533
- startDate,
534
- validity,
535
- });
536
- }
537
-
538
- async function revoke_certificate(certificate: Filename) {
539
- await g_certificateAuthority.revokeCertificate(certificate, {});
540
- }
541
-
542
- async function createPrivateKeyIfNotExist(privateKey: Filename, keyLength: KeySize) {
543
- if (fs.existsSync(privateKey)) {
544
- console.log(chalk.yellow(" privateKey"), chalk.cyan(privateKey), chalk.yellow(" already exists => skipping"));
545
- return;
546
- } else {
547
- await promisify(createPrivateKey)(privateKey, keyLength);
548
- }
549
- }
550
-
551
- displaySubtitle(" create private key :" + private_key_file);
552
-
553
- await createPrivateKeyIfNotExist(private_key_file, key_length);
554
- displaySubtitle(" extract public key " + public_key_file + " from private key ");
555
- await promisify(getPublicKeyFromPrivateKey)(private_key_file, public_key_file);
556
- displaySubtitle(" create Certificate " + certificate_file);
557
-
558
- await createCertificateIfNotExist(certificate_file, private_key_file, applicationUri, yesterday, 365);
559
-
560
- displaySubtitle(" create self signed Certificate " + self_signed_certificate_file);
561
-
562
- if (fs.existsSync(self_signed_certificate_file)) {
563
- // self_signed certificate already exists
564
- return;
565
- }
566
- await createSelfSignedCertificate(self_signed_certificate_file, private_key_file, applicationUri, yesterday, 365);
567
-
568
- if (dev) {
569
- await createCertificateIfNotExist(certificate_file_outofdate, private_key_file, applicationUri, two_years_ago, 365);
570
-
571
- await createCertificateIfNotExist(certificate_file_not_active_yet, private_key_file, applicationUri, next_year, 365);
572
-
573
- if (!fs.existsSync(certificate_revoked)) {
574
- // self_signed certificate already exists
575
- const certificate = await createCertificateIfNotExist(
576
- certificate_revoked,
577
- private_key_file,
578
- applicationUri + "Revoked", // make sure we used a uniq URI here
579
- yesterday,
580
- 365
581
- );
582
- console.log(" certificate to revoke => ", certificate);
583
- revoke_certificate(certificate_revoked);
584
- }
585
- }
586
- }
587
-
588
- // tslint:disable-next-line:no-empty
589
- let done: ErrorCallback = (err?: Error | null) => {
590
- /** */
591
- };
592
-
593
- async function wrap(func: () => Promise<void>) {
594
- try {
595
- await func();
596
- } catch (err) {
597
- on_completion(err as Error, () => {
598
- /** */
599
- });
600
- }
601
- }
602
-
603
- async function create_default_certificates(dev: boolean) {
604
-
605
- assert(gLocalConfig);
606
- const base_name = gLocalConfig.certificateDir || "";
607
- assert(fs.existsSync(base_name));
608
-
609
- let clientURN: string;
610
- let serverURN: string;
611
- let discoveryServerURN: string;
612
- wrap(async () => {
613
- await extractFullyQualifiedDomainName();
614
- const hostname = os.hostname();
615
- const fqdn = getFullyQualifiedDomainName();
616
- console.log(chalk.yellow(" hostname = "), chalk.cyan(hostname));
617
- console.log(chalk.yellow(" fqdn = "), chalk.cyan(fqdn));
618
- clientURN = makeApplicationUrn(hostname, "NodeOPCUA-Client");
619
- serverURN = makeApplicationUrn(hostname, "NodeOPCUA-Server");
620
- discoveryServerURN = makeApplicationUrn(hostname, "NodeOPCUA-DiscoveryServer");
621
-
622
- displayTitle("Create Application Certificate for Server & its private key");
623
- await createDefaultCertificate(base_name, "client_", 1024, clientURN, dev);
624
- await createDefaultCertificate(base_name, "client_", 2048, clientURN, dev);
625
- await createDefaultCertificate(base_name, "client_", 3072, clientURN, dev);
626
- await createDefaultCertificate(base_name, "client_", 4096, clientURN, dev);
627
-
628
- displayTitle("Create Application Certificate for Client & its private key");
629
- await createDefaultCertificate(base_name, "server_", 1024, serverURN, dev);
630
- await createDefaultCertificate(base_name, "server_", 2048, serverURN, dev);
631
- await createDefaultCertificate(base_name, "server_", 3072, serverURN, dev);
632
- await createDefaultCertificate(base_name, "server_", 4096, serverURN, dev);
633
-
634
- displayTitle("Create Application Certificate for DiscoveryServer & its private key");
635
- await createDefaultCertificate(base_name, "discoveryServer_", 1024, discoveryServerURN, dev);
636
- await createDefaultCertificate(base_name, "discoveryServer_", 2048, discoveryServerURN, dev);
637
- await createDefaultCertificate(base_name, "discoveryServer_", 3072, discoveryServerURN, dev);
638
- await createDefaultCertificate(base_name, "discoveryServer_", 4096, discoveryServerURN, dev);
639
- });
640
- }
641
-
642
- async function createDefaultCertificates(dev: boolean) {
643
- await construct_CertificateAuthority("");
644
- await construct_CertificateManager();
645
- await create_default_certificates(dev);
646
- }
647
-
648
- assert(typeof done === "function");
649
- argv
650
- .strict()
651
- .wrap(132)
652
- .command(
653
- "demo",
654
- "create default certificate for node-opcua demos",
655
- (yargs: commands.Argv) => {
656
- const options: { [key: string]: commands.Options } = {};
657
- options.dev = {
658
- type: "boolean",
659
- describe: "create all sort of fancy certificates for dev testing purposes",
660
- };
661
- options.clean = {
662
- type: "boolean",
663
- describe: "Purge existing directory [use with care!]",
664
- };
665
-
666
- add_standard_option(options, "silent");
667
- add_standard_option(options, "root");
668
-
669
- const local_argv = yargs
670
- .strict()
671
- .wrap(132)
672
- .options(options)
673
- .usage("$0 demo [--dev] [--silent] [--clean]")
674
- .example("$0 demo --dev", "create a set of demo certificates")
675
- .help("help").argv;
676
-
677
- return local_argv;
678
- },
679
- (local_argv: IReadConfigurationOpts2) => {
680
- wrap(async () => {
681
- await promisify(ensure_openssl_installed)();
682
- displayChapter("Create Demo certificates");
683
- displayTitle("reading configuration");
684
- await readConfiguration(local_argv);
685
- if (local_argv.clean) {
686
- displayTitle("Cleaning old certificates");
687
- assert(gLocalConfig);
688
- const certificateDir = gLocalConfig.certificateDir || "";
689
- await promisify(rimraf)(certificateDir + "/*.pem*");
690
- await promisify(rimraf)(certificateDir + "/*.pub*");
691
- await promisify(mkdir)(certificateDir);
692
- }
693
- displayTitle("create certificates");
694
- await createDefaultCertificates(local_argv.dev);
695
- displayChapter("Demo certificates CREATED");
696
- });
697
- }
698
- )
699
-
700
- .command(
701
- "createCA",
702
- "create a Certificate Authority",
703
- /* builder*/(yargs: commands.Argv) => {
704
- const options: OptionMap = {
705
- subject: {
706
- default: defaultSubject,
707
- type: "string",
708
- describe: "the CA certificate subject",
709
- },
710
- };
711
-
712
- add_standard_option(options, "root");
713
- add_standard_option(options, "CAFolder");
714
- add_standard_option(options, "keySize");
715
- add_standard_option(options, "silent");
716
-
717
- const local_argv = yargs.strict().wrap(132).options(options).help("help").epilog(epilog).argv;
718
- return local_argv;
719
- },
720
- /*handler*/(local_argv: IReadConfigurationOpts3) => {
721
- wrap(async () => {
722
- await promisify(ensure_openssl_installed)();
723
- await readConfiguration(local_argv);
724
- await construct_CertificateAuthority(local_argv.subject);
725
- });
726
- }
727
- )
728
- .command(
729
- "createPKI",
730
- "create a Public Key Infrastructure",
731
- (yargs: commands.Argv) => {
732
- const options = {};
733
-
734
- add_standard_option(options, "root");
735
- add_standard_option(options, "PKIFolder");
736
- add_standard_option(options, "keySize");
737
- add_standard_option(options, "silent");
738
-
739
- return yargs.strict().wrap(132).options(options).help("help").epilog(epilog).argv;
740
- },
741
- (local_argv: IReadConfigurationOpts) => {
742
- wrap(async () => {
743
- await readConfiguration(local_argv);
744
- await construct_CertificateManager();
745
- });
746
- }
747
- )
748
-
749
- // ----------------------------------------------- certificate
750
- .command(
751
- "certificate",
752
- "create a new certificate",
753
- (yargs: commands.Argv) => {
754
- const options: OptionMap = {
755
- applicationUri: {
756
- alias: "a",
757
- demand: true,
758
- describe: "the application URI",
759
- default: "urn:{hostname}:Node-OPCUA-Server",
760
- type: "string",
761
- },
762
- output: {
763
- default: "my_certificate.pem",
764
- alias: "o",
765
- demand: true,
766
- describe: "the name of the generated certificate =>",
767
- type: "string",
768
- },
769
- selfSigned: {
770
- alias: "s",
771
- default: false,
772
- type: "boolean",
773
- describe: "if true, certificate will be self-signed",
774
- },
775
- validity: {
776
- alias: "v",
777
- default: null,
778
- type: "number",
779
- describe: "the certificate validity in days",
780
- },
781
- dns: {
782
- default: "{hostname}",
783
- type: "string",
784
- describe: "the list of valid domain name (comma separated)",
785
- },
786
- ip: {
787
- default: "",
788
- type: "string",
789
- describe: "the list of valid IPs (comma separated)",
790
- },
791
- subject: {
792
- default: "",
793
- type: "string",
794
- describe: "the certificate subject ( for instance C=FR/ST=Centre/L=Orleans/O=SomeOrganization/CN=Hello )",
795
- },
796
- };
797
- add_standard_option(options, "silent");
798
- add_standard_option(options, "root");
799
- add_standard_option(options, "CAFolder");
800
- add_standard_option(options, "PKIFolder");
801
- add_standard_option(options, "privateKey");
802
-
803
- return yargs.strict().wrap(132).options(options).help("help").epilog(epilog).argv;
804
-
805
- },
806
- (local_argv: IReadConfigurationOpts4) => {
807
- async function command_certificate(local_argv: IReadConfigurationOpts4) {
808
- assert(typeof done === "function");
809
- const selfSigned = !!local_argv.selfSigned;
810
- if (!selfSigned) {
811
- await command_full_certificate(local_argv);
812
- } else {
813
- await command_selfsigned_certificate(local_argv);
814
- }
815
- }
816
-
817
- async function command_selfsigned_certificate(local_argv: IReadConfigurationOpts) {
818
- const fqdn = await extractFullyQualifiedDomainName();
819
- await readConfiguration(local_argv);
820
- await construct_CertificateManager();
821
-
822
- displaySubtitle(" create self signed Certificate " + gLocalConfig.outputFile);
823
- let subject =
824
- local_argv.subject && local_argv.subject.length > 1
825
- ? new Subject(local_argv.subject)
826
- : gLocalConfig.subject || "";
827
-
828
- subject = JSON.parse(JSON.stringify(subject));
829
-
830
- const params: CreateSelfSignCertificateParam1 = {
831
- applicationUri: gLocalConfig.applicationUri || "",
832
- dns: gLocalConfig.dns || [],
833
- ip: gLocalConfig.ip || [],
834
- outputFile: gLocalConfig.outputFile || "self_signed_certificate.pem",
835
- startDate: gLocalConfig.startDate || new Date(),
836
- subject,
837
- validity: gLocalConfig.validity || 365,
838
- };
839
-
840
- await promisify(certificateManager.createSelfSignedCertificate).call(certificateManager, params);
841
- }
842
-
843
- async function command_full_certificate(local_argv: IReadConfigurationOpts) {
844
- await readConfiguration(local_argv);
845
- await construct_CertificateManager();
846
- await construct_CertificateAuthority("");
847
- assert(fs.existsSync(gLocalConfig.CAFolder || ""), " CA folder must exist");
848
- gLocalConfig.privateKey = undefined; // use PKI private key
849
- // create a Certificate Request from the certificate Manager
850
-
851
- gLocalConfig.subject =
852
- local_argv.subject && local_argv.subject.length > 1 ? local_argv.subject : gLocalConfig.subject;
853
-
854
- const csr_file = await promisify(certificateManager.createCertificateRequest).call(
855
- certificateManager,
856
- gLocalConfig
857
- );
858
- if (!csr_file) {
859
- return;
860
- }
861
- console.log(" csr_file = ", csr_file);
862
- const certificate = csr_file.replace(".csr", ".pem");
863
-
864
- if (fs.existsSync(certificate)) {
865
- throw new Error(" File " + certificate + " already exist");
866
- }
867
- await promisify(g_certificateAuthority.signCertificateRequest).call(
868
- g_certificateAuthority,
869
- certificate,
870
- csr_file,
871
- gLocalConfig
872
- );
873
-
874
- assert(typeof gLocalConfig.outputFile === "string");
875
- fs.writeFileSync(gLocalConfig.outputFile || "", fs.readFileSync(certificate, "ascii"));
876
- }
877
-
878
- wrap(async () => await command_certificate(local_argv));
879
- }
880
- )
881
-
882
- // ----------------------------------------------- revoke
883
- .command(
884
- "revoke <certificateFile>",
885
- "revoke a existing certificate",
886
- (yargs: commands.Argv) => {
887
- const options: OptionMap = {};
888
- add_standard_option(options, "root");
889
- add_standard_option(options, "CAFolder");
890
-
891
- yargs.strict().wrap(132).help("help").usage("$0 revoke my_certificate.pem").options(options).epilog(epilog);
892
- return yargs;
893
- },
894
- (local_argv: IReadConfigurationOpts5) => {
895
- function revoke_certificate(certificate: Filename, callback: ErrorCallback) {
896
- g_certificateAuthority.revokeCertificate(certificate, {}, callback);
897
- }
898
-
899
- wrap(async () => {
900
- // example : node bin\crypto_create_CA.js revoke my_certificate.pem
901
- const certificate = path.resolve(local_argv.certificateFile);
902
- console.log(chalk.yellow(" Certificate to revoke : "), chalk.cyan(certificate));
903
- if (!fs.existsSync(certificate)) {
904
- throw new Error("cannot find certificate to revoke " + certificate);
905
- }
906
- await readConfiguration(local_argv);
907
- await construct_CertificateAuthority("");
908
- await promisify(revoke_certificate)(certificate);
909
- console.log("done ... ");
910
- console.log(" crl = ", g_certificateAuthority.revocationList);
911
- console.log("\nyou should now publish the new Certificate Revocation List");
912
- });
913
- }
914
- )
915
-
916
- .command(
917
- "csr",
918
- "create a certificate signing request",
919
- (yargs: commands.Argv) => {
920
- const options: OptionMap = {
921
- applicationUri: {
922
- alias: "a",
923
- // demand: true,
924
- describe: "the application URI",
925
- default: "urn:{hostname}:Node-OPCUA-Server",
926
- type: "string",
927
- },
928
- output: {
929
- default: "my_certificate_signing_request.csr",
930
- alias: "o",
931
- // demand: true,
932
- describe: "the name of the generated signing_request",
933
- type: "string",
934
- },
935
- dns: {
936
- default: "{hostname}",
937
- type: "string",
938
- describe: "the list of valid domain name (comma separated)",
939
- },
940
- ip: {
941
- default: "",
942
- type: "string",
943
- describe: "the list of valid IPs (comma separated)",
944
- },
945
- subject: {
946
- default: "/CN=Certificate",
947
- type: "string",
948
- describe: "the certificate subject ( for instance /C=FR/ST=Centre/L=Orleans/O=SomeOrganization/CN=Hello )",
949
- },
950
- };
951
- add_standard_option(options, "silent");
952
- add_standard_option(options, "root");
953
- add_standard_option(options, "PKIFolder");
954
- add_standard_option(options, "privateKey");
955
-
956
- return yargs.strict().wrap(132).options(options).help("help").epilog(epilog).argv;
957
- },
958
- (local_argv: IReadConfigurationOpts) => {
959
- wrap(async () => {
960
- await readConfiguration(local_argv);
961
- if (!fs.existsSync(gLocalConfig.PKIFolder || "")) {
962
- console.log("PKI folder must exist");
963
- }
964
- await construct_CertificateManager();
965
- if (!gLocalConfig.outputFile || fs.existsSync(gLocalConfig.outputFile)) {
966
- throw new Error(" File " + gLocalConfig.outputFile + " already exist");
967
- }
968
- gLocalConfig.privateKey = undefined; // use PKI private key
969
- // create a Certificate Request from the certificate Manager
970
-
971
- gLocalConfig.subject =
972
- local_argv.subject && local_argv.subject.length > 1 ? local_argv.subject : gLocalConfig.subject;
973
-
974
- const internal_csr_file = await promisify(certificateManager.createCertificateRequest).call(
975
- certificateManager,
976
- gLocalConfig
977
- );
978
- if (!internal_csr_file) {
979
- return;
980
- }
981
- if (!gLocalConfig.outputFile) {
982
- console.log("please specify a output file");
983
- return;
984
- }
985
- const csr = await fs.promises.readFile(internal_csr_file, "utf-8");
986
- fs.writeFileSync(gLocalConfig.outputFile || "", csr, "utf-8");
987
-
988
- console.log("Subject = ", gLocalConfig.subject);
989
- console.log("applicationUri = ", gLocalConfig.applicationUri);
990
- console.log("altNames = ", gLocalConfig.altNames);
991
- console.log("dns = ", gLocalConfig.dns);
992
- console.log("ip = ", gLocalConfig.ip);
993
-
994
- console.log("CSR file = ", gLocalConfig.outputFile);
995
-
996
-
997
- });
998
- }
999
- )
1000
- .command(
1001
- "sign",
1002
- "validate a certificate signing request and generate a certificate",
1003
- (yargs: commands.Argv) => {
1004
- const options: OptionMap = {
1005
- csr: {
1006
- alias: "i",
1007
- default: "my_certificate_signing_request.csr",
1008
- type: "string",
1009
- demandOption: true,
1010
- description: "the csr"
1011
- },
1012
- output: {
1013
- default: "my_certificate.pem",
1014
- alias: "o",
1015
- demand: true,
1016
- describe: "the name of the generated certificate",
1017
- type: "string",
1018
- },
1019
- validity: {
1020
- alias: "v",
1021
- default: 365,
1022
- type: "number",
1023
- describe: "the certificate validity in days",
1024
- },
1025
- };
1026
- add_standard_option(options, "silent");
1027
- add_standard_option(options, "root");
1028
- add_standard_option(options, "CAFolder");
1029
- return yargs.strict().wrap(132).options(options).help("help").epilog(epilog).argv;
1030
-
1031
- }, (local_argv: IReadConfigurationOpts) => {
1032
- wrap(async () => {
1033
- /** */
1034
- await readConfiguration(local_argv);
1035
- if (!fs.existsSync(gLocalConfig.CAFolder || "")) {
1036
- throw new Error("CA folder must exist:" + gLocalConfig.CAFolder);
1037
- }
1038
- await construct_CertificateAuthority("");
1039
- const csr_file: string = path.resolve((local_argv as any).csr || "");
1040
- if (!fs.existsSync(csr_file)) {
1041
- throw new Error("Certificate signing request doesn't exist: " + csr_file);
1042
- }
1043
- const certificate = path.resolve(local_argv.output || csr_file.replace(".csr", ".pem"));
1044
- if (fs.existsSync(certificate)) {
1045
- throw new Error(" File " + certificate + " already exist");
1046
- }
1047
-
1048
- await promisify(g_certificateAuthority.signCertificateRequest).call(
1049
- g_certificateAuthority,
1050
- certificate,
1051
- csr_file,
1052
- gLocalConfig
1053
- );
1054
-
1055
- assert(typeof gLocalConfig.outputFile === "string");
1056
- fs.writeFileSync(gLocalConfig.outputFile || "", fs.readFileSync(certificate, "ascii"));
1057
- });
1058
- })
1059
- .command(
1060
- "dump <certificateFile>",
1061
- "display a certificate",
1062
- () => {
1063
- /** */
1064
- },
1065
- (yargs: { certificateFile: string }) => {
1066
- wrap(async () => {
1067
- const data = await promisify(dumpCertificate)(yargs.certificateFile);
1068
- console.log(data);
1069
- });
1070
- }
1071
- )
1072
-
1073
- .command(
1074
- "toder <pemCertificate>",
1075
- "convert a certificate to a DER format with finger print",
1076
- () => {
1077
- /** */
1078
- },
1079
- (yargs: { pemCertificate: string }) => {
1080
- wrap(async () => {
1081
- await promisify(toDer)(argv.pemCertificate);
1082
- });
1083
- }
1084
- )
1085
-
1086
- .command(
1087
- "fingerprint <certificateFile>",
1088
- "print the certificate fingerprint",
1089
- () => {
1090
- /** */
1091
- },
1092
- (local_argv: { certificateFile: string }) => {
1093
- wrap(async () => {
1094
- const certificate = local_argv.certificateFile;
1095
- const data = await promisify(fingerprint)(certificate);
1096
- if (!data) return;
1097
- const s = data.split("=")[1].split(":").join("").trim();
1098
- console.log(s);
1099
- });
1100
- }
1101
- )
1102
- .command("$0", "help", (yargs: commands.Argv) => {
1103
- console.log("--help for help");
1104
- return yargs;
1105
- })
1106
- .epilog(epilog)
1107
- .help("help")
1108
- .strict().argv;
1109
-
1110
- export function main(argumentsList: string, _done?: ErrorCallback) {
1111
- if (_done) {
1112
- done = _done;
1113
- }
1114
-
1115
- commands.parse(argumentsList, (err: Error | null, g_argv: { help: boolean }) => {
1116
- // istanbul ignore next
1117
- if (err) {
1118
- console.log(" err = ", err);
1119
- console.log(" use --help for more info");
1120
- setImmediate(() => {
1121
- commands.showHelp();
1122
- done(err);
1123
- });
1124
- } else {
1125
- if (g_argv.help) {
1126
- setImmediate(() => {
1127
- commands.showHelp();
1128
- done();
1129
- });
1130
- } else {
1131
- done();
1132
- }
1133
- }
1134
- });
1135
- }
1
+ /* eslint-disable @typescript-eslint/no-unused-vars */
2
+ // ---------------------------------------------------------------------------------------------------------------------
3
+ // node-opcua
4
+ // ---------------------------------------------------------------------------------------------------------------------
5
+ // Copyright (c) 2014-2022 - Etienne Rossignon - etienne.rossignon (at) gadz.org
6
+ // Copyright (c) 2022 - Sterfive.com
7
+ // ---------------------------------------------------------------------------------------------------------------------
8
+ //
9
+ // This project is licensed under the terms of the MIT license.
10
+ //
11
+ // Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated
12
+ // documentation files (the "Software"), to deal in the Software without restriction, including without limitation the
13
+ // rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to
14
+ // permit persons to whom the Software is furnished to do so, subject to the following conditions:
15
+ //
16
+ // The above copyright notice and this permission notice shall be included in all copies or substantial portions of the
17
+ // Software.
18
+ //
19
+ // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
20
+ // WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
21
+ // COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
22
+ // OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
23
+ // ---------------------------------------------------------------------------------------------------------------------
24
+ // Error.stackTraceLimit = Infinity;
25
+ // tslint:disable:variable-name
26
+ // tslint:disable:no-console
27
+ // tslint:disable:object-literal-sort-keys
28
+ // tslint:disable:no-shadowed-variable
29
+
30
+ import * as assert from "assert";
31
+ import * as chalk from "chalk";
32
+ import * as rimraf from "rimraf";
33
+ import * as fs from "fs";
34
+ import * as path from "path";
35
+ import * as os from "os";
36
+
37
+ import { callbackify, promisify } from "util";
38
+
39
+ import { makeApplicationUrn } from "./misc/applicationurn";
40
+ import { extractFullyQualifiedDomainName, getFullyQualifiedDomainName } from "./misc/hostname";
41
+ import { Subject, SubjectOptions } from "./misc/subject";
42
+ import { CertificateAuthority, defaultSubject } from "./pki/certificate_authority";
43
+ import { CertificateManager, CreateSelfSignCertificateParam1 } from "./pki/certificate_manager";
44
+ import { ErrorCallback, Filename, KeySize } from "./pki/common";
45
+ import {
46
+ createCertificateSigningRequest,
47
+ CreateCertificateSigningRequestWithConfigOptions,
48
+ createPrivateKey,
49
+ displayChapter,
50
+ displaySubtitle,
51
+ displayTitle,
52
+ dumpCertificate,
53
+ ensure_openssl_installed,
54
+ fingerprint,
55
+ g_config,
56
+ getPublicKeyFromPrivateKey,
57
+ make_path,
58
+ mkdir,
59
+ setEnv,
60
+ toDer,
61
+ debugLog,
62
+ } from "./pki/toolbox";
63
+
64
+ // see https://github.com/yargs/yargs/issues/781
65
+ import * as commands from "yargs";
66
+ // eslint-disable-next-line @typescript-eslint/no-var-requires
67
+ const { hideBin } = require("yargs/helpers");
68
+ // eslint-disable-next-line @typescript-eslint/no-var-requires
69
+ const argv = require("yargs/yargs")(hideBin(process.argv));
70
+
71
+ const epilog = "Copyright (c) sterfive - node-opcua - 2017-2022";
72
+
73
+ // ------------------------------------------------- some useful dates
74
+ function get_offset_date(date: Date, nbDays: number): Date {
75
+ const d = new Date(date.getTime());
76
+ d.setDate(d.getDate() + nbDays);
77
+ return d;
78
+ }
79
+
80
+ const today = new Date();
81
+ const yesterday = get_offset_date(today, -1);
82
+ const two_years_ago = get_offset_date(today, -2 * 365);
83
+ const next_year = get_offset_date(today, 365);
84
+
85
+ interface LocalConfig {
86
+ CAFolder?: string;
87
+ PKIFolder?: string;
88
+
89
+ keySize?: KeySize;
90
+
91
+ subject?: SubjectOptions | string;
92
+
93
+ certificateDir?: Filename;
94
+
95
+ privateKey?: Filename;
96
+
97
+ applicationUri?: string;
98
+
99
+ outputFile?: string;
100
+
101
+ altNames?: string[];
102
+ dns?: string[];
103
+ ip?: string[];
104
+
105
+ startDate?: Date;
106
+ validity?: number;
107
+ }
108
+
109
+ let gLocalConfig: LocalConfig = {};
110
+
111
+ let g_certificateAuthority: CertificateAuthority; // the Certificate Authority
112
+
113
+ /***
114
+ *
115
+ *
116
+ * prerequisites :
117
+ * g_config.CAFolder : the folder of the CA
118
+ */
119
+ async function construct_CertificateAuthority(subject: string) {
120
+ // verify that g_config file has been loaded
121
+ assert(typeof gLocalConfig.CAFolder === "string", "expecting a CAFolder in config");
122
+ assert(typeof gLocalConfig.keySize === "number", "expecting a keySize in config");
123
+
124
+ if (!g_certificateAuthority) {
125
+ g_certificateAuthority = new CertificateAuthority({
126
+ keySize: gLocalConfig.keySize,
127
+ location: gLocalConfig.CAFolder,
128
+ subject,
129
+ });
130
+ await g_certificateAuthority.initialize();
131
+ }
132
+ }
133
+
134
+ let certificateManager: CertificateManager; // the Certificate Manager
135
+ /***
136
+ *
137
+ *
138
+ * prerequisites :
139
+ * g_config.PKIFolder : the folder of the PKI
140
+ */
141
+ async function construct_CertificateManager() {
142
+ assert(typeof gLocalConfig.PKIFolder === "string", "expecting a PKIFolder in config");
143
+
144
+ if (!certificateManager) {
145
+ certificateManager = new CertificateManager({
146
+ keySize: gLocalConfig.keySize,
147
+ location: gLocalConfig.PKIFolder,
148
+ });
149
+ await certificateManager.initialize();
150
+ }
151
+ }
152
+
153
+ function displayConfig(config: { [key: string]: { toString: () => string } }) {
154
+ function w(str: string, l: number): string {
155
+ return (str + " ").substring(0, l);
156
+ }
157
+
158
+ console.log(chalk.yellow(" configuration = "));
159
+
160
+ for (const [key, value] of Object.entries(config)) {
161
+ console.log(" " + chalk.yellow(w(key, 30)) + " : " + chalk.cyan(value.toString()));
162
+ }
163
+ }
164
+
165
+ function default_template_content(): string {
166
+ // istanbul ignore next
167
+ if ((process as any).pkg && (process as any).pkg.entrypoint) {
168
+ // we are using PKG compiled package !
169
+
170
+ // console.log("___filename", __filename);
171
+ // console.log("__dirname", __dirname);
172
+ // console.log("process.pkg.entrypoint", (process as any).pkg.entrypoint);
173
+ const a = fs.readFileSync(path.join(__dirname, "../../bin/crypto_create_CA_config.example.js"), "utf8");
174
+ console.log(a);
175
+ return a;
176
+ }
177
+ function find_default_config_template() {
178
+ const rootFolder = find_module_root_folder();
179
+ let default_config_template = path.join(rootFolder, "bin", path.basename(__filename, ".js") + "_config.example.js");
180
+
181
+ if (!fs.existsSync(default_config_template)) {
182
+ default_config_template = path.join(__dirname, "..", path.basename(__filename, ".js") + "_config.example.js");
183
+
184
+ if (!fs.existsSync(default_config_template)) {
185
+ default_config_template = path.join(__dirname, "../bin/" + path.basename(__filename, ".js") + "_config.example.js");
186
+ }
187
+ }
188
+ return default_config_template;
189
+ }
190
+ const default_config_template = find_default_config_template();
191
+ assert(fs.existsSync(default_config_template));
192
+ const default_config_template_content = fs.readFileSync(default_config_template, "utf8");
193
+ return default_config_template_content;
194
+ }
195
+
196
+ /**
197
+ *
198
+ */
199
+ function find_module_root_folder() {
200
+ let rootFolder = path.join(__dirname);
201
+
202
+ for (let i = 0; i < 4; i++) {
203
+ if (fs.existsSync(path.join(rootFolder, "package.json"))) {
204
+ return rootFolder;
205
+ }
206
+ rootFolder = path.join(rootFolder, "..");
207
+ }
208
+
209
+ assert(fs.existsSync(path.join(rootFolder, "package.json")), "root folder must have a package.json file");
210
+ return rootFolder;
211
+ }
212
+
213
+ interface IReadConfigurationOpts {
214
+ root: string;
215
+ silent?: boolean;
216
+ subject?: string;
217
+ CAFolder?: string;
218
+ PKIFolder?: string;
219
+ privateKey?: string;
220
+ applicationUri?: string;
221
+ output?: string;
222
+ altNames?: string;
223
+ dns?: string;
224
+ ip?: string;
225
+ keySize?: KeySize;
226
+ validity?: number;
227
+ }
228
+ interface IReadConfigurationOpts2 extends IReadConfigurationOpts {
229
+ clean: boolean;
230
+ dev: boolean;
231
+ }
232
+ interface IReadConfigurationOpts3 extends IReadConfigurationOpts {
233
+ subject: string;
234
+ }
235
+ interface IReadConfigurationOpts4 extends IReadConfigurationOpts {
236
+ selfSigned: boolean;
237
+ }
238
+
239
+ interface IReadConfigurationOpts5 extends IReadConfigurationOpts {
240
+ certificateFile: string;
241
+ }
242
+
243
+ /* eslint complexity:off, max-statements:off */
244
+ async function readConfiguration(argv: IReadConfigurationOpts) {
245
+ if (argv.silent) {
246
+ g_config.silent = true;
247
+ } else {
248
+ g_config.silent = false;
249
+ }
250
+
251
+ const fqdn = await extractFullyQualifiedDomainName();
252
+ const hostname = os.hostname();
253
+ let certificateDir: string;
254
+
255
+ function performSubstitution(str: string): string {
256
+ str = str.replace("{CWD}", process.cwd());
257
+ if (certificateDir) {
258
+ str = str.replace("{root}", certificateDir);
259
+ }
260
+ if (gLocalConfig && gLocalConfig.PKIFolder) {
261
+ str = str.replace("{PKIFolder}", gLocalConfig.PKIFolder);
262
+ }
263
+ str = str.replace("{hostname}", hostname);
264
+ str = str.replace("%FQDN%", fqdn);
265
+ return str;
266
+ }
267
+
268
+ function prepare(file: Filename): Filename {
269
+ const tmp = path.resolve(performSubstitution(file));
270
+ return make_path(tmp);
271
+ }
272
+
273
+ // ------------------------------------------------------------------------------------------------------------
274
+ certificateDir = argv.root;
275
+ assert(typeof certificateDir === "string");
276
+
277
+ certificateDir = prepare(certificateDir);
278
+ mkdir(certificateDir);
279
+ assert(fs.existsSync(certificateDir));
280
+
281
+ // ------------------------------------------------------------------------------------------------------------
282
+ const default_config = path.join(certificateDir, "config.js");
283
+
284
+ if (!fs.existsSync(default_config)) {
285
+ // copy
286
+ debugLog(chalk.yellow(" Creating default g_config file "), chalk.cyan(default_config));
287
+ const default_config_template_content = default_template_content();
288
+ fs.writeFileSync(default_config, default_config_template_content);
289
+ } else {
290
+ debugLog(chalk.yellow(" using g_config file "), chalk.cyan(default_config));
291
+ }
292
+ if (!fs.existsSync(default_config)) {
293
+ console.log(chalk.redBright(" cannot find config file ", default_config));
294
+ }
295
+
296
+ // see http://stackoverflow.com/questions/94445/using-openssl-what-does-unable-to-write-random-state-mean
297
+ // set random file to be random.rnd in the same folder as the g_config file
298
+ const defaultRandomFile = path.join(path.dirname(default_config), "random.rnd");
299
+ setEnv("RANDFILE", defaultRandomFile);
300
+
301
+ /* eslint global-require: 0*/
302
+ gLocalConfig = require(default_config);
303
+
304
+ gLocalConfig.subject = new Subject(gLocalConfig.subject || "");
305
+
306
+ // if subject is provided on the command line , it has hight priority
307
+ if (argv.subject) {
308
+ gLocalConfig.subject = new Subject(argv.subject);
309
+ }
310
+
311
+ // istanbul ignore next
312
+ if (!gLocalConfig.subject.commonName) {
313
+ throw new Error("subject must have a Common Name");
314
+ }
315
+
316
+ gLocalConfig.certificateDir = certificateDir;
317
+
318
+ // ------------------------------------------------------------------------------------------------------------
319
+ let CAFolder = argv.CAFolder || path.join(certificateDir, "CA");
320
+ CAFolder = prepare(CAFolder);
321
+ gLocalConfig.CAFolder = CAFolder;
322
+
323
+ // ------------------------------------------------------------------------------------------------------------
324
+ gLocalConfig.PKIFolder = path.join(gLocalConfig.certificateDir, "PKI");
325
+ if (argv.PKIFolder) {
326
+ gLocalConfig.PKIFolder = prepare(argv.PKIFolder);
327
+ }
328
+ gLocalConfig.PKIFolder = prepare(gLocalConfig.PKIFolder);
329
+ if (argv.privateKey) {
330
+ gLocalConfig.privateKey = prepare(argv.privateKey);
331
+ }
332
+
333
+ if (argv.applicationUri) {
334
+ gLocalConfig.applicationUri = performSubstitution(argv.applicationUri);
335
+ }
336
+
337
+ if (argv.output) {
338
+ gLocalConfig.outputFile = argv.output;
339
+ }
340
+
341
+ gLocalConfig.altNames = [];
342
+ if (argv.altNames) {
343
+ gLocalConfig.altNames = argv.altNames.split(";");
344
+ }
345
+ gLocalConfig.dns = [getFullyQualifiedDomainName()];
346
+ if (argv.dns) {
347
+ gLocalConfig.dns = argv.dns.split(",").map(performSubstitution);
348
+ }
349
+ gLocalConfig.ip = [];
350
+ if (argv.ip) {
351
+ gLocalConfig.ip = argv.ip.split(",");
352
+ }
353
+ if (argv.keySize) {
354
+ const v = argv.keySize;
355
+ if (v !== 1024 && v !== 2048 && v !== 3072 && v !== 4096) {
356
+ throw new Error("invalid keysize specified " + v + " should be 1024,2048,3072 or 4096");
357
+ }
358
+ gLocalConfig.keySize = argv.keySize;
359
+ }
360
+
361
+ if (argv.validity) {
362
+ gLocalConfig.validity = argv.validity;
363
+ }
364
+ // xx displayConfig(g_config);
365
+ // ------------------------------------------------------------------------------------------------------------
366
+ }
367
+
368
+ interface OptionMap {
369
+ [key: string]: commands.Options;
370
+ }
371
+
372
+ function add_standard_option(options: OptionMap, optionName: string) {
373
+ switch (optionName) {
374
+ case "root":
375
+ options.root = {
376
+ alias: "r",
377
+ type: "string",
378
+ default: "{CWD}/certificates",
379
+ describe: "the location of the Certificate folder",
380
+ };
381
+ break;
382
+
383
+ case "CAFolder":
384
+ options.CAFolder = {
385
+ alias: "c",
386
+ type: "string",
387
+ default: "{root}/CA",
388
+ describe: "the location of the Certificate Authority folder",
389
+ };
390
+ break;
391
+
392
+ case "PKIFolder":
393
+ options.PKIFolder = {
394
+ type: "string",
395
+ default: "{root}/PKI",
396
+ describe: "the location of the Public Key Infrastructure",
397
+ };
398
+ break;
399
+
400
+ case "silent":
401
+ options.silent = {
402
+ alias: "s",
403
+ type: "boolean",
404
+ default: false,
405
+ describe: "minimize output",
406
+ };
407
+ break;
408
+
409
+ case "privateKey":
410
+ options.privateKey = {
411
+ alias: "p",
412
+ type: "string",
413
+ default: "{PKIFolder}/own/private_key.pem",
414
+ describe: "the private key to use to generate certificate",
415
+ };
416
+ break;
417
+
418
+ case "keySize":
419
+ options.keySize = {
420
+ alias: ["k", "keyLength"],
421
+ type: "number",
422
+ default: 2048,
423
+ describe: "the private key size in bits (1024|2048|3072|4096)",
424
+ };
425
+ break;
426
+ default:
427
+ throw Error("Unknown option " + optionName);
428
+ }
429
+ }
430
+
431
+ function on_completion(err: Error | null | undefined, done: ErrorCallback) {
432
+ assert(typeof done === "function", "expecting function");
433
+ // istanbul ignore next
434
+ if (err) {
435
+ console.log(chalk.redBright("ERROR : ") + err.message);
436
+ }
437
+ done();
438
+ }
439
+
440
+ async function createDefaultCertificate(
441
+ base_name: string,
442
+ prefix: string,
443
+ key_length: KeySize,
444
+ applicationUri: string,
445
+ dev: boolean
446
+ ) {
447
+ // possible key length in bits
448
+ assert(key_length === 1024 || key_length === 2048 || key_length === 3072 || key_length === 4096);
449
+
450
+ const private_key_file = make_path(base_name, prefix + "key_" + key_length + ".pem");
451
+ const public_key_file = make_path(base_name, prefix + "public_key_" + key_length + ".pub");
452
+ const certificate_file = make_path(base_name, prefix + "cert_" + key_length + ".pem");
453
+ const certificate_file_outofdate = make_path(base_name, prefix + "cert_" + key_length + "_outofdate.pem");
454
+ const certificate_file_not_active_yet = make_path(base_name, prefix + "cert_" + key_length + "_not_active_yet.pem");
455
+ const certificate_revoked = make_path(base_name, prefix + "cert_" + key_length + "_revoked.pem");
456
+ const self_signed_certificate_file = make_path(base_name, prefix + "selfsigned_cert_" + key_length + ".pem");
457
+
458
+ const fqdn = getFullyQualifiedDomainName();
459
+ const hostname = os.hostname();
460
+ const dns: string[] = [
461
+ // for conformance reason, localhost shall not be present in the DNS field of COP
462
+ // ***FORBIDEN** "localhost",
463
+ getFullyQualifiedDomainName(),
464
+ ];
465
+ if (hostname !== fqdn) {
466
+ dns.push(hostname);
467
+ }
468
+
469
+ const ip: string[] = [];
470
+
471
+ async function createCertificateIfNotExist(
472
+ certificate: Filename,
473
+ private_key: Filename,
474
+ applicationUri: string,
475
+ startDate: Date,
476
+ validity: number
477
+ ): Promise<string> {
478
+ // istanbul ignore next
479
+ if (fs.existsSync(certificate)) {
480
+ console.log(chalk.yellow(" certificate"), chalk.cyan(certificate), chalk.yellow(" already exists => skipping"));
481
+ return "";
482
+ } else {
483
+ return await createCertificate(certificate, private_key, applicationUri, startDate, validity);
484
+ }
485
+ }
486
+
487
+ async function createCertificate(
488
+ certificate: Filename,
489
+ privateKey: Filename,
490
+ applicationUri: string,
491
+ startDate: Date,
492
+ validity: number
493
+ ): Promise<string> {
494
+ const certificateSigningRequestFile = certificate + ".csr";
495
+
496
+ const configFile = make_path(base_name, "../certificates/PKI/own/openssl.cnf");
497
+
498
+ const dns = [os.hostname()];
499
+ const ip = ["127.0.0.1"];
500
+
501
+ const params: CreateCertificateSigningRequestWithConfigOptions = {
502
+ applicationUri,
503
+ privateKey,
504
+ rootDir: ".",
505
+ configFile,
506
+ dns,
507
+ ip,
508
+ };
509
+
510
+ // create CSR
511
+ await promisify(createCertificateSigningRequest)(certificateSigningRequestFile, params);
512
+
513
+ return await g_certificateAuthority.signCertificateRequest(certificate, certificateSigningRequestFile, {
514
+ applicationUri,
515
+ dns,
516
+ ip,
517
+ startDate,
518
+ validity,
519
+ });
520
+ }
521
+
522
+ async function createSelfSignedCertificate(
523
+ certificate: Filename,
524
+ private_key: Filename,
525
+ applicationUri: string,
526
+ startDate: Date,
527
+ validity: number
528
+ ) {
529
+ await g_certificateAuthority.createSelfSignedCertificate(certificate, private_key, {
530
+ applicationUri,
531
+ dns,
532
+ ip,
533
+ startDate,
534
+ validity,
535
+ });
536
+ }
537
+
538
+ async function revoke_certificate(certificate: Filename) {
539
+ await g_certificateAuthority.revokeCertificate(certificate, {});
540
+ }
541
+
542
+ async function createPrivateKeyIfNotExist(privateKey: Filename, keyLength: KeySize) {
543
+ if (fs.existsSync(privateKey)) {
544
+ console.log(chalk.yellow(" privateKey"), chalk.cyan(privateKey), chalk.yellow(" already exists => skipping"));
545
+ return;
546
+ } else {
547
+ await promisify(createPrivateKey)(privateKey, keyLength);
548
+ }
549
+ }
550
+
551
+ displaySubtitle(" create private key :" + private_key_file);
552
+
553
+ await createPrivateKeyIfNotExist(private_key_file, key_length);
554
+ displaySubtitle(" extract public key " + public_key_file + " from private key ");
555
+ await promisify(getPublicKeyFromPrivateKey)(private_key_file, public_key_file);
556
+ displaySubtitle(" create Certificate " + certificate_file);
557
+
558
+ await createCertificateIfNotExist(certificate_file, private_key_file, applicationUri, yesterday, 365);
559
+
560
+ displaySubtitle(" create self signed Certificate " + self_signed_certificate_file);
561
+
562
+ if (fs.existsSync(self_signed_certificate_file)) {
563
+ // self_signed certificate already exists
564
+ return;
565
+ }
566
+ await createSelfSignedCertificate(self_signed_certificate_file, private_key_file, applicationUri, yesterday, 365);
567
+
568
+ if (dev) {
569
+ await createCertificateIfNotExist(certificate_file_outofdate, private_key_file, applicationUri, two_years_ago, 365);
570
+
571
+ await createCertificateIfNotExist(certificate_file_not_active_yet, private_key_file, applicationUri, next_year, 365);
572
+
573
+ if (!fs.existsSync(certificate_revoked)) {
574
+ // self_signed certificate already exists
575
+ const certificate = await createCertificateIfNotExist(
576
+ certificate_revoked,
577
+ private_key_file,
578
+ applicationUri + "Revoked", // make sure we used a uniq URI here
579
+ yesterday,
580
+ 365
581
+ );
582
+ console.log(" certificate to revoke => ", certificate);
583
+ revoke_certificate(certificate_revoked);
584
+ }
585
+ }
586
+ }
587
+
588
+ // tslint:disable-next-line:no-empty
589
+ let done: ErrorCallback = (err?: Error | null) => {
590
+ /** */
591
+ };
592
+
593
+ async function wrap(func: () => Promise<void>) {
594
+ try {
595
+ await func();
596
+ } catch (err) {
597
+ on_completion(err as Error, () => {
598
+ /** */
599
+ });
600
+ }
601
+ }
602
+
603
+ async function create_default_certificates(dev: boolean) {
604
+
605
+ assert(gLocalConfig);
606
+ const base_name = gLocalConfig.certificateDir || "";
607
+ assert(fs.existsSync(base_name));
608
+
609
+ let clientURN: string;
610
+ let serverURN: string;
611
+ let discoveryServerURN: string;
612
+ wrap(async () => {
613
+ await extractFullyQualifiedDomainName();
614
+ const hostname = os.hostname();
615
+ const fqdn = getFullyQualifiedDomainName();
616
+ console.log(chalk.yellow(" hostname = "), chalk.cyan(hostname));
617
+ console.log(chalk.yellow(" fqdn = "), chalk.cyan(fqdn));
618
+ clientURN = makeApplicationUrn(hostname, "NodeOPCUA-Client");
619
+ serverURN = makeApplicationUrn(hostname, "NodeOPCUA-Server");
620
+ discoveryServerURN = makeApplicationUrn(hostname, "NodeOPCUA-DiscoveryServer");
621
+
622
+ displayTitle("Create Application Certificate for Server & its private key");
623
+ await createDefaultCertificate(base_name, "client_", 1024, clientURN, dev);
624
+ await createDefaultCertificate(base_name, "client_", 2048, clientURN, dev);
625
+ await createDefaultCertificate(base_name, "client_", 3072, clientURN, dev);
626
+ await createDefaultCertificate(base_name, "client_", 4096, clientURN, dev);
627
+
628
+ displayTitle("Create Application Certificate for Client & its private key");
629
+ await createDefaultCertificate(base_name, "server_", 1024, serverURN, dev);
630
+ await createDefaultCertificate(base_name, "server_", 2048, serverURN, dev);
631
+ await createDefaultCertificate(base_name, "server_", 3072, serverURN, dev);
632
+ await createDefaultCertificate(base_name, "server_", 4096, serverURN, dev);
633
+
634
+ displayTitle("Create Application Certificate for DiscoveryServer & its private key");
635
+ await createDefaultCertificate(base_name, "discoveryServer_", 1024, discoveryServerURN, dev);
636
+ await createDefaultCertificate(base_name, "discoveryServer_", 2048, discoveryServerURN, dev);
637
+ await createDefaultCertificate(base_name, "discoveryServer_", 3072, discoveryServerURN, dev);
638
+ await createDefaultCertificate(base_name, "discoveryServer_", 4096, discoveryServerURN, dev);
639
+ });
640
+ }
641
+
642
+ async function createDefaultCertificates(dev: boolean) {
643
+ await construct_CertificateAuthority("");
644
+ await construct_CertificateManager();
645
+ await create_default_certificates(dev);
646
+ }
647
+
648
+ assert(typeof done === "function");
649
+ argv
650
+ .strict()
651
+ .wrap(132)
652
+ .command(
653
+ "demo",
654
+ "create default certificate for node-opcua demos",
655
+ (yargs: commands.Argv) => {
656
+ const options: { [key: string]: commands.Options } = {};
657
+ options.dev = {
658
+ type: "boolean",
659
+ describe: "create all sort of fancy certificates for dev testing purposes",
660
+ };
661
+ options.clean = {
662
+ type: "boolean",
663
+ describe: "Purge existing directory [use with care!]",
664
+ };
665
+
666
+ add_standard_option(options, "silent");
667
+ add_standard_option(options, "root");
668
+
669
+ const local_argv = yargs
670
+ .strict()
671
+ .wrap(132)
672
+ .options(options)
673
+ .usage("$0 demo [--dev] [--silent] [--clean]")
674
+ .example("$0 demo --dev", "create a set of demo certificates")
675
+ .help("help").argv;
676
+
677
+ return local_argv;
678
+ },
679
+ (local_argv: IReadConfigurationOpts2) => {
680
+ wrap(async () => {
681
+ await promisify(ensure_openssl_installed)();
682
+ displayChapter("Create Demo certificates");
683
+ displayTitle("reading configuration");
684
+ await readConfiguration(local_argv);
685
+ if (local_argv.clean) {
686
+ displayTitle("Cleaning old certificates");
687
+ assert(gLocalConfig);
688
+ const certificateDir = gLocalConfig.certificateDir || "";
689
+ await promisify(rimraf)(certificateDir + "/*.pem*");
690
+ await promisify(rimraf)(certificateDir + "/*.pub*");
691
+ await promisify(mkdir)(certificateDir);
692
+ }
693
+ displayTitle("create certificates");
694
+ await createDefaultCertificates(local_argv.dev);
695
+ displayChapter("Demo certificates CREATED");
696
+ });
697
+ }
698
+ )
699
+
700
+ .command(
701
+ "createCA",
702
+ "create a Certificate Authority",
703
+ /* builder*/(yargs: commands.Argv) => {
704
+ const options: OptionMap = {
705
+ subject: {
706
+ default: defaultSubject,
707
+ type: "string",
708
+ describe: "the CA certificate subject",
709
+ },
710
+ };
711
+
712
+ add_standard_option(options, "root");
713
+ add_standard_option(options, "CAFolder");
714
+ add_standard_option(options, "keySize");
715
+ add_standard_option(options, "silent");
716
+
717
+ const local_argv = yargs.strict().wrap(132).options(options).help("help").epilog(epilog).argv;
718
+ return local_argv;
719
+ },
720
+ /*handler*/(local_argv: IReadConfigurationOpts3) => {
721
+ wrap(async () => {
722
+ await promisify(ensure_openssl_installed)();
723
+ await readConfiguration(local_argv);
724
+ await construct_CertificateAuthority(local_argv.subject);
725
+ });
726
+ }
727
+ )
728
+ .command(
729
+ "createPKI",
730
+ "create a Public Key Infrastructure",
731
+ (yargs: commands.Argv) => {
732
+ const options = {};
733
+
734
+ add_standard_option(options, "root");
735
+ add_standard_option(options, "PKIFolder");
736
+ add_standard_option(options, "keySize");
737
+ add_standard_option(options, "silent");
738
+
739
+ return yargs.strict().wrap(132).options(options).help("help").epilog(epilog).argv;
740
+ },
741
+ (local_argv: IReadConfigurationOpts) => {
742
+ wrap(async () => {
743
+ await readConfiguration(local_argv);
744
+ await construct_CertificateManager();
745
+ });
746
+ }
747
+ )
748
+
749
+ // ----------------------------------------------- certificate
750
+ .command(
751
+ "certificate",
752
+ "create a new certificate",
753
+ (yargs: commands.Argv) => {
754
+ const options: OptionMap = {
755
+ applicationUri: {
756
+ alias: "a",
757
+ demand: true,
758
+ describe: "the application URI",
759
+ default: "urn:{hostname}:Node-OPCUA-Server",
760
+ type: "string",
761
+ },
762
+ output: {
763
+ default: "my_certificate.pem",
764
+ alias: "o",
765
+ demand: true,
766
+ describe: "the name of the generated certificate =>",
767
+ type: "string",
768
+ },
769
+ selfSigned: {
770
+ alias: "s",
771
+ default: false,
772
+ type: "boolean",
773
+ describe: "if true, certificate will be self-signed",
774
+ },
775
+ validity: {
776
+ alias: "v",
777
+ default: null,
778
+ type: "number",
779
+ describe: "the certificate validity in days",
780
+ },
781
+ dns: {
782
+ default: "{hostname}",
783
+ type: "string",
784
+ describe: "the list of valid domain name (comma separated)",
785
+ },
786
+ ip: {
787
+ default: "",
788
+ type: "string",
789
+ describe: "the list of valid IPs (comma separated)",
790
+ },
791
+ subject: {
792
+ default: "",
793
+ type: "string",
794
+ describe: "the certificate subject ( for instance C=FR/ST=Centre/L=Orleans/O=SomeOrganization/CN=Hello )",
795
+ },
796
+ };
797
+ add_standard_option(options, "silent");
798
+ add_standard_option(options, "root");
799
+ add_standard_option(options, "CAFolder");
800
+ add_standard_option(options, "PKIFolder");
801
+ add_standard_option(options, "privateKey");
802
+
803
+ return yargs.strict().wrap(132).options(options).help("help").epilog(epilog).argv;
804
+
805
+ },
806
+ (local_argv: IReadConfigurationOpts4) => {
807
+ async function command_certificate(local_argv: IReadConfigurationOpts4) {
808
+ assert(typeof done === "function");
809
+ const selfSigned = !!local_argv.selfSigned;
810
+ if (!selfSigned) {
811
+ await command_full_certificate(local_argv);
812
+ } else {
813
+ await command_selfsigned_certificate(local_argv);
814
+ }
815
+ }
816
+
817
+ async function command_selfsigned_certificate(local_argv: IReadConfigurationOpts) {
818
+ const fqdn = await extractFullyQualifiedDomainName();
819
+ await readConfiguration(local_argv);
820
+ await construct_CertificateManager();
821
+
822
+ displaySubtitle(" create self signed Certificate " + gLocalConfig.outputFile);
823
+ let subject =
824
+ local_argv.subject && local_argv.subject.length > 1
825
+ ? new Subject(local_argv.subject)
826
+ : gLocalConfig.subject || "";
827
+
828
+ subject = JSON.parse(JSON.stringify(subject));
829
+
830
+ const params: CreateSelfSignCertificateParam1 = {
831
+ applicationUri: gLocalConfig.applicationUri || "",
832
+ dns: gLocalConfig.dns || [],
833
+ ip: gLocalConfig.ip || [],
834
+ outputFile: gLocalConfig.outputFile || "self_signed_certificate.pem",
835
+ startDate: gLocalConfig.startDate || new Date(),
836
+ subject,
837
+ validity: gLocalConfig.validity || 365,
838
+ };
839
+
840
+ await promisify(certificateManager.createSelfSignedCertificate).call(certificateManager, params);
841
+ }
842
+
843
+ async function command_full_certificate(local_argv: IReadConfigurationOpts) {
844
+ await readConfiguration(local_argv);
845
+ await construct_CertificateManager();
846
+ await construct_CertificateAuthority("");
847
+ assert(fs.existsSync(gLocalConfig.CAFolder || ""), " CA folder must exist");
848
+ gLocalConfig.privateKey = undefined; // use PKI private key
849
+ // create a Certificate Request from the certificate Manager
850
+
851
+ gLocalConfig.subject =
852
+ local_argv.subject && local_argv.subject.length > 1 ? local_argv.subject : gLocalConfig.subject;
853
+
854
+ const csr_file = await promisify(certificateManager.createCertificateRequest).call(
855
+ certificateManager,
856
+ gLocalConfig
857
+ );
858
+ if (!csr_file) {
859
+ return;
860
+ }
861
+ console.log(" csr_file = ", csr_file);
862
+ const certificate = csr_file.replace(".csr", ".pem");
863
+
864
+ if (fs.existsSync(certificate)) {
865
+ throw new Error(" File " + certificate + " already exist");
866
+ }
867
+ await promisify(g_certificateAuthority.signCertificateRequest).call(
868
+ g_certificateAuthority,
869
+ certificate,
870
+ csr_file,
871
+ gLocalConfig
872
+ );
873
+
874
+ assert(typeof gLocalConfig.outputFile === "string");
875
+ fs.writeFileSync(gLocalConfig.outputFile || "", fs.readFileSync(certificate, "ascii"));
876
+ }
877
+
878
+ wrap(async () => await command_certificate(local_argv));
879
+ }
880
+ )
881
+
882
+ // ----------------------------------------------- revoke
883
+ .command(
884
+ "revoke <certificateFile>",
885
+ "revoke a existing certificate",
886
+ (yargs: commands.Argv) => {
887
+ const options: OptionMap = {};
888
+ add_standard_option(options, "root");
889
+ add_standard_option(options, "CAFolder");
890
+
891
+ yargs.strict().wrap(132).help("help").usage("$0 revoke my_certificate.pem").options(options).epilog(epilog);
892
+ return yargs;
893
+ },
894
+ (local_argv: IReadConfigurationOpts5) => {
895
+ function revoke_certificate(certificate: Filename, callback: ErrorCallback) {
896
+ g_certificateAuthority.revokeCertificate(certificate, {}, callback);
897
+ }
898
+
899
+ wrap(async () => {
900
+ // example : node bin\crypto_create_CA.js revoke my_certificate.pem
901
+ const certificate = path.resolve(local_argv.certificateFile);
902
+ console.log(chalk.yellow(" Certificate to revoke : "), chalk.cyan(certificate));
903
+ if (!fs.existsSync(certificate)) {
904
+ throw new Error("cannot find certificate to revoke " + certificate);
905
+ }
906
+ await readConfiguration(local_argv);
907
+ await construct_CertificateAuthority("");
908
+ await promisify(revoke_certificate)(certificate);
909
+ console.log("done ... ");
910
+ console.log(" crl = ", g_certificateAuthority.revocationList);
911
+ console.log("\nyou should now publish the new Certificate Revocation List");
912
+ });
913
+ }
914
+ )
915
+
916
+ .command(
917
+ "csr",
918
+ "create a certificate signing request",
919
+ (yargs: commands.Argv) => {
920
+ const options: OptionMap = {
921
+ applicationUri: {
922
+ alias: "a",
923
+ // demand: true,
924
+ describe: "the application URI",
925
+ default: "urn:{hostname}:Node-OPCUA-Server",
926
+ type: "string",
927
+ },
928
+ output: {
929
+ default: "my_certificate_signing_request.csr",
930
+ alias: "o",
931
+ // demand: true,
932
+ describe: "the name of the generated signing_request",
933
+ type: "string",
934
+ },
935
+ dns: {
936
+ default: "{hostname}",
937
+ type: "string",
938
+ describe: "the list of valid domain name (comma separated)",
939
+ },
940
+ ip: {
941
+ default: "",
942
+ type: "string",
943
+ describe: "the list of valid IPs (comma separated)",
944
+ },
945
+ subject: {
946
+ default: "/CN=Certificate",
947
+ type: "string",
948
+ describe: "the certificate subject ( for instance /C=FR/ST=Centre/L=Orleans/O=SomeOrganization/CN=Hello )",
949
+ },
950
+ };
951
+ add_standard_option(options, "silent");
952
+ add_standard_option(options, "root");
953
+ add_standard_option(options, "PKIFolder");
954
+ add_standard_option(options, "privateKey");
955
+
956
+ return yargs.strict().wrap(132).options(options).help("help").epilog(epilog).argv;
957
+ },
958
+ (local_argv: IReadConfigurationOpts) => {
959
+ wrap(async () => {
960
+ await readConfiguration(local_argv);
961
+ if (!fs.existsSync(gLocalConfig.PKIFolder || "")) {
962
+ console.log("PKI folder must exist");
963
+ }
964
+ await construct_CertificateManager();
965
+ if (!gLocalConfig.outputFile || fs.existsSync(gLocalConfig.outputFile)) {
966
+ throw new Error(" File " + gLocalConfig.outputFile + " already exist");
967
+ }
968
+ gLocalConfig.privateKey = undefined; // use PKI private key
969
+ // create a Certificate Request from the certificate Manager
970
+
971
+ gLocalConfig.subject =
972
+ local_argv.subject && local_argv.subject.length > 1 ? local_argv.subject : gLocalConfig.subject;
973
+
974
+ const internal_csr_file = await promisify(certificateManager.createCertificateRequest).call(
975
+ certificateManager,
976
+ gLocalConfig
977
+ );
978
+ if (!internal_csr_file) {
979
+ return;
980
+ }
981
+ if (!gLocalConfig.outputFile) {
982
+ console.log("please specify a output file");
983
+ return;
984
+ }
985
+ const csr = await fs.promises.readFile(internal_csr_file, "utf-8");
986
+ fs.writeFileSync(gLocalConfig.outputFile || "", csr, "utf-8");
987
+
988
+ console.log("Subject = ", gLocalConfig.subject);
989
+ console.log("applicationUri = ", gLocalConfig.applicationUri);
990
+ console.log("altNames = ", gLocalConfig.altNames);
991
+ console.log("dns = ", gLocalConfig.dns);
992
+ console.log("ip = ", gLocalConfig.ip);
993
+
994
+ console.log("CSR file = ", gLocalConfig.outputFile);
995
+
996
+
997
+ });
998
+ }
999
+ )
1000
+ .command(
1001
+ "sign",
1002
+ "validate a certificate signing request and generate a certificate",
1003
+ (yargs: commands.Argv) => {
1004
+ const options: OptionMap = {
1005
+ csr: {
1006
+ alias: "i",
1007
+ default: "my_certificate_signing_request.csr",
1008
+ type: "string",
1009
+ demandOption: true,
1010
+ description: "the csr"
1011
+ },
1012
+ output: {
1013
+ default: "my_certificate.pem",
1014
+ alias: "o",
1015
+ demand: true,
1016
+ describe: "the name of the generated certificate",
1017
+ type: "string",
1018
+ },
1019
+ validity: {
1020
+ alias: "v",
1021
+ default: 365,
1022
+ type: "number",
1023
+ describe: "the certificate validity in days",
1024
+ },
1025
+ };
1026
+ add_standard_option(options, "silent");
1027
+ add_standard_option(options, "root");
1028
+ add_standard_option(options, "CAFolder");
1029
+ return yargs.strict().wrap(132).options(options).help("help").epilog(epilog).argv;
1030
+
1031
+ }, (local_argv: IReadConfigurationOpts) => {
1032
+ wrap(async () => {
1033
+ /** */
1034
+ await readConfiguration(local_argv);
1035
+ if (!fs.existsSync(gLocalConfig.CAFolder || "")) {
1036
+ throw new Error("CA folder must exist:" + gLocalConfig.CAFolder);
1037
+ }
1038
+ await construct_CertificateAuthority("");
1039
+ const csr_file: string = path.resolve((local_argv as any).csr || "");
1040
+ if (!fs.existsSync(csr_file)) {
1041
+ throw new Error("Certificate signing request doesn't exist: " + csr_file);
1042
+ }
1043
+ const certificate = path.resolve(local_argv.output || csr_file.replace(".csr", ".pem"));
1044
+ if (fs.existsSync(certificate)) {
1045
+ throw new Error(" File " + certificate + " already exist");
1046
+ }
1047
+
1048
+ await promisify(g_certificateAuthority.signCertificateRequest).call(
1049
+ g_certificateAuthority,
1050
+ certificate,
1051
+ csr_file,
1052
+ gLocalConfig
1053
+ );
1054
+
1055
+ assert(typeof gLocalConfig.outputFile === "string");
1056
+ fs.writeFileSync(gLocalConfig.outputFile || "", fs.readFileSync(certificate, "ascii"));
1057
+ });
1058
+ })
1059
+ .command(
1060
+ "dump <certificateFile>",
1061
+ "display a certificate",
1062
+ () => {
1063
+ /** */
1064
+ },
1065
+ (yargs: { certificateFile: string }) => {
1066
+ wrap(async () => {
1067
+ const data = await promisify(dumpCertificate)(yargs.certificateFile);
1068
+ console.log(data);
1069
+ });
1070
+ }
1071
+ )
1072
+
1073
+ .command(
1074
+ "toder <pemCertificate>",
1075
+ "convert a certificate to a DER format with finger print",
1076
+ () => {
1077
+ /** */
1078
+ },
1079
+ (yargs: { pemCertificate: string }) => {
1080
+ wrap(async () => {
1081
+ await promisify(toDer)(argv.pemCertificate);
1082
+ });
1083
+ }
1084
+ )
1085
+
1086
+ .command(
1087
+ "fingerprint <certificateFile>",
1088
+ "print the certificate fingerprint",
1089
+ () => {
1090
+ /** */
1091
+ },
1092
+ (local_argv: { certificateFile: string }) => {
1093
+ wrap(async () => {
1094
+ const certificate = local_argv.certificateFile;
1095
+ const data = await promisify(fingerprint)(certificate);
1096
+ if (!data) return;
1097
+ const s = data.split("=")[1].split(":").join("").trim();
1098
+ console.log(s);
1099
+ });
1100
+ }
1101
+ )
1102
+ .command("$0", "help", (yargs: commands.Argv) => {
1103
+ console.log("--help for help");
1104
+ return yargs;
1105
+ })
1106
+ .epilog(epilog)
1107
+ .help("help")
1108
+ .strict().argv;
1109
+
1110
+ export function main(argumentsList: string, _done?: ErrorCallback) {
1111
+ if (_done) {
1112
+ done = _done;
1113
+ }
1114
+
1115
+ commands.parse(argumentsList, (err: Error | null, g_argv: { help: boolean }) => {
1116
+ // istanbul ignore next
1117
+ if (err) {
1118
+ console.log(" err = ", err);
1119
+ console.log(" use --help for more info");
1120
+ setImmediate(() => {
1121
+ commands.showHelp();
1122
+ done(err);
1123
+ });
1124
+ } else {
1125
+ if (g_argv.help) {
1126
+ setImmediate(() => {
1127
+ commands.showHelp();
1128
+ done();
1129
+ });
1130
+ } else {
1131
+ done();
1132
+ }
1133
+ }
1134
+ });
1135
+ }