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.
- package/.ignore +6 -6
- package/.prettierrc +5 -5
- package/LICENSE +22 -22
- package/bin/crypto_create_CA.js +0 -0
- package/bin/crypto_create_CA_config.example.js +18 -18
- package/bin/install_prerequisite.js +9 -9
- package/dist/crypto_create_CA.d.ts +2 -2
- package/dist/crypto_create_CA.js +897 -897
- package/dist/index.d.ts +6 -6
- package/dist/index.js +44 -44
- package/dist/misc/applicationurn.d.ts +1 -1
- package/dist/misc/applicationurn.js +46 -46
- package/dist/misc/hostname.d.ts +8 -8
- package/dist/misc/hostname.js +102 -102
- package/dist/misc/install_prerequisite.d.ts +9 -9
- package/dist/misc/install_prerequisite.js +363 -360
- package/dist/misc/install_prerequisite.js.map +1 -1
- package/dist/misc/subject.d.ts +26 -26
- package/dist/misc/subject.js +121 -121
- package/dist/pki/certificate_authority.d.ts +61 -61
- package/dist/pki/certificate_authority.js +481 -481
- package/dist/pki/certificate_manager.d.ts +144 -144
- package/dist/pki/certificate_manager.js +883 -883
- package/dist/pki/certificate_manager.js.map +1 -1
- package/dist/pki/common.d.ts +5 -5
- package/dist/pki/common.js +2 -2
- package/dist/pki/templates/ca_config_template.cnf.d.ts +2 -2
- package/dist/pki/templates/ca_config_template.cnf.js +129 -129
- package/dist/pki/templates/simple_config_template.cnf.d.ts +2 -2
- package/dist/pki/templates/simple_config_template.cnf.js +75 -75
- package/dist/pki/toolbox.d.ts +160 -160
- package/dist/pki/toolbox.js +699 -699
- package/dist/pki/toolbox_pfx.js +18 -18
- package/lib/crypto_create_CA.ts +1135 -1135
- package/lib/index.ts +28 -28
- package/lib/misc/applicationurn.ts +45 -45
- package/lib/misc/hostname.ts +89 -89
- package/lib/misc/install_prerequisite.ts +454 -454
- package/lib/misc/subject.ts +141 -141
- package/lib/pki/certificate_manager.ts +1 -1
- package/lib/pki/common.ts +5 -5
- package/lib/pki/templates/ca_config_template.cnf.ts +129 -129
- package/lib/pki/templates/simple_config_template.cnf.ts +75 -75
- package/lib/pki/toolbox_pfx.ts +19 -19
- package/package.json +89 -89
- package/readme.md +214 -214
- package/tsconfig.json +20 -20
- package/dist/misc/fs.d.ts +0 -24
- package/dist/misc/fs.js +0 -21
- package/dist/misc/fs.js.map +0 -1
- package/dist/misc/get_default_filesystem.d.ts +0 -2
- package/dist/misc/get_default_filesystem.js +0 -9
- package/dist/misc/get_default_filesystem.js.map +0 -1
package/lib/crypto_create_CA.ts
CHANGED
|
@@ -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
|
+
}
|