node-opcua-pki 6.3.0 → 6.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/bin/pki.mjs CHANGED
@@ -1,15 +1,3437 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
3
  __commonJS,
4
+ __dirname,
5
+ __esm,
6
+ __filename,
4
7
  init_esm_shims
5
- } from "../chunk-VXGTT7QM.mjs";
8
+ } from "../chunk-GCHH54PS.mjs";
9
+
10
+ // packages/node-opcua-pki/lib/misc/applicationurn.ts
11
+ import assert from "assert";
12
+ import { createHash } from "crypto";
13
+ function makeApplicationUrn(hostname, suffix) {
14
+ let hostnameHash = hostname;
15
+ if (hostnameHash.length + 7 + suffix.length >= 64) {
16
+ hostnameHash = createHash("md5").update(hostname).digest("hex").substring(0, 16);
17
+ }
18
+ const applicationUrn = `urn:${hostnameHash}:${suffix}`;
19
+ assert(applicationUrn.length <= 64);
20
+ return applicationUrn;
21
+ }
22
+ var init_applicationurn = __esm({
23
+ "packages/node-opcua-pki/lib/misc/applicationurn.ts"() {
24
+ "use strict";
25
+ init_esm_shims();
26
+ }
27
+ });
28
+
29
+ // packages/node-opcua-pki/lib/misc/hostname.ts
30
+ import dns from "dns";
31
+ import os from "os";
32
+ import { promisify } from "util";
33
+ function trim(str, length) {
34
+ if (!length) {
35
+ return str;
36
+ }
37
+ return str.substring(0, Math.min(str.length, length));
38
+ }
39
+ function fqdn(callback) {
40
+ const uqdn = os.hostname();
41
+ dns.lookup(uqdn, { hints: dns.ADDRCONFIG }, (err1, ip) => {
42
+ if (err1) {
43
+ return callback(err1);
44
+ }
45
+ dns.lookupService(ip, 0, (err2, _fqdn) => {
46
+ if (err2) {
47
+ return callback(err2);
48
+ }
49
+ _fqdn = _fqdn.replace(".localdomain", "");
50
+ callback(null, _fqdn);
51
+ });
52
+ });
53
+ }
54
+ async function extractFullyQualifiedDomainName() {
55
+ if (_fullyQualifiedDomainNameInCache) {
56
+ return _fullyQualifiedDomainNameInCache;
57
+ }
58
+ if (process.platform === "win32") {
59
+ const env = process.env;
60
+ _fullyQualifiedDomainNameInCache = env.COMPUTERNAME + (env.USERDNSDOMAIN && env.USERDNSDOMAIN?.length > 0 ? `.${env.USERDNSDOMAIN}` : "");
61
+ } else {
62
+ try {
63
+ _fullyQualifiedDomainNameInCache = await promisify(fqdn)();
64
+ if (_fullyQualifiedDomainNameInCache === "localhost") {
65
+ throw new Error("localhost not expected");
66
+ }
67
+ if (/sethostname/.test(_fullyQualifiedDomainNameInCache)) {
68
+ throw new Error("Detecting fqdn on windows !!!");
69
+ }
70
+ } catch (_err) {
71
+ _fullyQualifiedDomainNameInCache = os.hostname();
72
+ }
73
+ }
74
+ return _fullyQualifiedDomainNameInCache;
75
+ }
76
+ async function prepareFQDN() {
77
+ _fullyQualifiedDomainNameInCache = await extractFullyQualifiedDomainName();
78
+ }
79
+ function getFullyQualifiedDomainName(optional_max_length) {
80
+ if (!_fullyQualifiedDomainNameInCache) {
81
+ throw new Error("FullyQualifiedDomainName computation is not completed yet");
82
+ }
83
+ return _fullyQualifiedDomainNameInCache ? trim(_fullyQualifiedDomainNameInCache, optional_max_length) : "%FQDN%";
84
+ }
85
+ var _fullyQualifiedDomainNameInCache;
86
+ var init_hostname = __esm({
87
+ "packages/node-opcua-pki/lib/misc/hostname.ts"() {
88
+ "use strict";
89
+ init_esm_shims();
90
+ prepareFQDN();
91
+ }
92
+ });
93
+
94
+ // packages/node-opcua-pki/lib/toolbox/config.ts
95
+ var g_config;
96
+ var init_config = __esm({
97
+ "packages/node-opcua-pki/lib/toolbox/config.ts"() {
98
+ "use strict";
99
+ init_esm_shims();
100
+ g_config = {
101
+ opensslVersion: "unset",
102
+ silent: process.env.VERBOSE ? !process.env.VERBOSE : true,
103
+ force: false
104
+ };
105
+ }
106
+ });
107
+
108
+ // packages/node-opcua-pki/lib/toolbox/debug.ts
109
+ function debugLog(...args) {
110
+ if (displayDebug) {
111
+ console.log.apply(null, args);
112
+ }
113
+ }
114
+ function warningLog(...args) {
115
+ console.log.apply(null, args);
116
+ }
117
+ var doDebug, displayError, displayDebug;
118
+ var init_debug = __esm({
119
+ "packages/node-opcua-pki/lib/toolbox/debug.ts"() {
120
+ "use strict";
121
+ init_esm_shims();
122
+ doDebug = process.env.NODEOPCUAPKIDEBUG || false;
123
+ displayError = true;
124
+ displayDebug = !!process.env.NODEOPCUAPKIDEBUG || false;
125
+ }
126
+ });
127
+
128
+ // packages/node-opcua-pki/lib/toolbox/common2.ts
129
+ import assert2 from "assert";
130
+ import fs from "fs";
131
+ import path from "path";
132
+ import chalk from "chalk";
133
+ function certificateFileExist(certificateFile) {
134
+ if (fs.existsSync(certificateFile) && !g_config.force) {
135
+ warningLog(
136
+ chalk.yellow(" certificate ") + chalk.cyan(certificateFile) + chalk.yellow(" already exists => do not overwrite")
137
+ );
138
+ return false;
139
+ }
140
+ return true;
141
+ }
142
+ function mkdirRecursiveSync(folder) {
143
+ if (!fs.existsSync(folder)) {
144
+ debugLog(chalk.white(" .. constructing "), folder);
145
+ fs.mkdirSync(folder, { recursive: true });
146
+ }
147
+ }
148
+ function makePath(folderName, filename) {
149
+ let s;
150
+ if (filename) {
151
+ s = path.join(path.normalize(folderName), filename);
152
+ } else {
153
+ assert2(folderName);
154
+ s = folderName;
155
+ }
156
+ s = s.replace(/\\/g, "/");
157
+ return s;
158
+ }
159
+ var init_common2 = __esm({
160
+ "packages/node-opcua-pki/lib/toolbox/common2.ts"() {
161
+ "use strict";
162
+ init_esm_shims();
163
+ init_config();
164
+ init_debug();
165
+ }
166
+ });
167
+
168
+ // packages/node-opcua-pki/lib/toolbox/display.ts
169
+ import chalk2 from "chalk";
170
+ function displayChapter(str) {
171
+ const l = " ";
172
+ warningLog(`${chalk2.bgWhite(l)} `);
173
+ str = ` ${str}${l}`.substring(0, l.length);
174
+ warningLog(chalk2.bgWhite.cyan(str));
175
+ warningLog(`${chalk2.bgWhite(l)} `);
176
+ }
177
+ function displayTitle(str) {
178
+ if (!g_config.silent) {
179
+ warningLog("");
180
+ warningLog(chalk2.yellowBright(str));
181
+ warningLog(chalk2.yellow(new Array(str.length + 1).join("=")), "\n");
182
+ }
183
+ }
184
+ function displaySubtitle(str) {
185
+ if (!g_config.silent) {
186
+ warningLog("");
187
+ warningLog(` ${chalk2.yellowBright(str)}`);
188
+ warningLog(` ${chalk2.white(new Array(str.length + 1).join("-"))}`, "\n");
189
+ }
190
+ }
191
+ function display(str) {
192
+ if (!g_config.silent) {
193
+ warningLog(` ${str}`);
194
+ }
195
+ }
196
+ var init_display = __esm({
197
+ "packages/node-opcua-pki/lib/toolbox/display.ts"() {
198
+ "use strict";
199
+ init_esm_shims();
200
+ init_config();
201
+ init_debug();
202
+ }
203
+ });
204
+
205
+ // packages/node-opcua-pki/lib/toolbox/without_openssl/create_certificate_signing_request.ts
206
+ import assert3 from "assert";
207
+ import fs2 from "fs";
208
+ import { createCertificateSigningRequest, pemToPrivateKey, Subject } from "node-opcua-crypto";
209
+ async function createCertificateSigningRequestAsync(certificateSigningRequestFilename, params) {
210
+ assert3(params);
211
+ assert3(params.rootDir);
212
+ assert3(params.configFile);
213
+ assert3(params.privateKey);
214
+ assert3(typeof params.privateKey === "string");
215
+ assert3(fs2.existsSync(params.privateKey), `Private key must exist${params.privateKey}`);
216
+ assert3(fs2.existsSync(params.rootDir), "RootDir key must exist");
217
+ assert3(typeof certificateSigningRequestFilename === "string");
218
+ const subject = params.subject ? new Subject(params.subject).toString() : void 0;
219
+ displaySubtitle("- Creating a Certificate Signing Request with subtile");
220
+ const privateKeyPem = await fs2.promises.readFile(params.privateKey, "utf-8");
221
+ const privateKey = await pemToPrivateKey(privateKeyPem);
222
+ const { csr } = await createCertificateSigningRequest({
223
+ privateKey,
224
+ dns: params.dns,
225
+ ip: params.ip,
226
+ subject,
227
+ applicationUri: params.applicationUri,
228
+ purpose: params.purpose
229
+ });
230
+ await fs2.promises.writeFile(certificateSigningRequestFilename, csr, "utf-8");
231
+ display(`- privateKey ${params.privateKey}`);
232
+ display(`- certificateSigningRequestFilename ${certificateSigningRequestFilename}`);
233
+ }
234
+ var init_create_certificate_signing_request = __esm({
235
+ "packages/node-opcua-pki/lib/toolbox/without_openssl/create_certificate_signing_request.ts"() {
236
+ "use strict";
237
+ init_esm_shims();
238
+ init_display();
239
+ }
240
+ });
241
+
242
+ // packages/node-opcua-pki/lib/toolbox/common.ts
243
+ import assert4 from "assert";
244
+ function quote(str) {
245
+ return `"${str || ""}"`;
246
+ }
247
+ function adjustDate(params) {
248
+ assert4(params instanceof Object);
249
+ params.startDate = params.startDate || /* @__PURE__ */ new Date();
250
+ assert4(params.startDate instanceof Date);
251
+ params.validity = params.validity || 365;
252
+ params.endDate = new Date(params.startDate.getTime());
253
+ params.endDate.setDate(params.startDate.getDate() + params.validity);
254
+ assert4(params.endDate instanceof Date);
255
+ assert4(params.startDate instanceof Date);
256
+ }
257
+ function adjustApplicationUri(params) {
258
+ const applicationUri = params.applicationUri || "";
259
+ if (applicationUri.length > 200) {
260
+ throw new Error(`Openssl doesn't support urn with length greater than 200${applicationUri}`);
261
+ }
262
+ }
263
+ var init_common = __esm({
264
+ "packages/node-opcua-pki/lib/toolbox/common.ts"() {
265
+ "use strict";
266
+ init_esm_shims();
267
+ }
268
+ });
269
+
270
+ // packages/node-opcua-pki/lib/toolbox/without_openssl/create_self_signed_certificate.ts
271
+ import assert5 from "assert";
272
+ import fs3 from "fs";
273
+ import {
274
+ CertificatePurpose,
275
+ createSelfSignedCertificate as createSelfSignedCertificate1,
276
+ pemToPrivateKey as pemToPrivateKey2,
277
+ Subject as Subject2
278
+ } from "node-opcua-crypto";
279
+ async function createSelfSignedCertificateAsync(certificate, params) {
280
+ params.purpose = params.purpose || CertificatePurpose.ForApplication;
281
+ assert5(params.purpose, "Please provide a Certificate Purpose");
282
+ assert5(fs3.existsSync(params.configFile));
283
+ assert5(fs3.existsSync(params.rootDir));
284
+ assert5(fs3.existsSync(params.privateKey));
285
+ if (!params.subject) {
286
+ throw Error("Missing subject");
287
+ }
288
+ assert5(typeof params.applicationUri === "string");
289
+ assert5(Array.isArray(params.dns));
290
+ adjustDate(params);
291
+ assert5(Object.prototype.hasOwnProperty.call(params, "validity"));
292
+ let subject = new Subject2(params.subject);
293
+ subject = subject.toString();
294
+ const purpose = params.purpose;
295
+ displayTitle("Generate a certificate request");
296
+ const privateKeyPem = await fs3.promises.readFile(params.privateKey, "utf-8");
297
+ const privateKey = await pemToPrivateKey2(privateKeyPem);
298
+ const { cert } = await createSelfSignedCertificate1({
299
+ privateKey,
300
+ notBefore: params.startDate,
301
+ notAfter: params.endDate,
302
+ validity: params.validity,
303
+ dns: params.dns,
304
+ ip: params.ip,
305
+ subject,
306
+ applicationUri: params.applicationUri,
307
+ purpose
308
+ });
309
+ await fs3.promises.writeFile(certificate, cert, "utf-8");
310
+ }
311
+ async function createSelfSignedCertificate(certificate, params) {
312
+ await createSelfSignedCertificateAsync(certificate, params);
313
+ }
314
+ var init_create_self_signed_certificate = __esm({
315
+ "packages/node-opcua-pki/lib/toolbox/without_openssl/create_self_signed_certificate.ts"() {
316
+ "use strict";
317
+ init_esm_shims();
318
+ init_common();
319
+ init_display();
320
+ }
321
+ });
322
+
323
+ // packages/node-opcua-pki/lib/toolbox/without_openssl/index.ts
324
+ var init_without_openssl = __esm({
325
+ "packages/node-opcua-pki/lib/toolbox/without_openssl/index.ts"() {
326
+ "use strict";
327
+ init_esm_shims();
328
+ init_create_certificate_signing_request();
329
+ init_create_self_signed_certificate();
330
+ }
331
+ });
332
+
333
+ // packages/node-opcua-pki/lib/pki/templates/simple_config_template.cnf.ts
334
+ var config, simple_config_template_cnf_default;
335
+ var init_simple_config_template_cnf = __esm({
336
+ "packages/node-opcua-pki/lib/pki/templates/simple_config_template.cnf.ts"() {
337
+ "use strict";
338
+ init_esm_shims();
339
+ config = '##################################################################################################\n## SIMPLE OPENSSL CONFIG FILE FOR SELF-SIGNED CERTIFICATE GENERATION\n################################################################################################################\n\ndistinguished_name = req_distinguished_name\ndefault_md = sha1\n\ndefault_md = sha256 # The default digest algorithm\n\n[ v3_ca ]\nsubjectKeyIdentifier = hash\nauthorityKeyIdentifier = keyid:always,issuer:always\n\n# authorityKeyIdentifier = keyid\nbasicConstraints = CA:TRUE\nkeyUsage = critical, cRLSign, keyCertSign\nnsComment = "Self-signed Certificate for CA generated by Node-OPCUA Certificate utility"\n#nsCertType = sslCA, emailCA\n#subjectAltName = email:copy\n#issuerAltName = issuer:copy\n#obj = DER:02:03\n# crlDistributionPoints = @crl_info\n# [ crl_info ]\n# URI.0 = http://localhost:8900/crl.pem\nsubjectAltName = $ENV::ALTNAME\n\n[ req ]\ndays = 390\nreq_extensions = v3_req\nx509_extensions = v3_ca\n\n[v3_req]\nbasicConstraints = CA:false\nkeyUsage = critical, cRLSign, keyCertSign\nsubjectAltName = $ENV::ALTNAME\n\n[ v3_ca_signed]\nsubjectKeyIdentifier = hash\nauthorityKeyIdentifier = keyid,issuer\nbasicConstraints = critical, CA:FALSE\nkeyUsage = nonRepudiation, digitalSignature, keyEncipherment, dataEncipherment, keyCertSign\nextendedKeyUsage = clientAuth,serverAuth \nnsComment = "certificate generated by Node-OPCUA Certificate utility and signed by a CA"\nsubjectAltName = $ENV::ALTNAME\n[ v3_selfsigned]\nsubjectKeyIdentifier = hash\nauthorityKeyIdentifier = keyid,issuer\nbasicConstraints = critical, CA:FALSE\nkeyUsage = nonRepudiation, digitalSignature, keyEncipherment, dataEncipherment, keyCertSign\nextendedKeyUsage = clientAuth,serverAuth \nnsComment = "Self-signed certificate generated by Node-OPCUA Certificate utility"\nsubjectAltName = $ENV::ALTNAME\n[ req_distinguished_name ]\ncountryName = Country Name (2 letter code)\ncountryName_default = FR\ncountryName_min = 2\ncountryName_max = 2\n# stateOrProvinceName = State or Province Name (full name)\n# stateOrProvinceName_default = Ile de France\n# localityName = Locality Name (city, district)\n# localityName_default = Paris\norganizationName = Organization Name (company)\norganizationName_default = NodeOPCUA\n# organizationalUnitName = Organizational Unit Name (department, division)\n# organizationalUnitName_default = R&D\ncommonName = Common Name (hostname, FQDN, IP, or your name)\ncommonName_max = 256\ncommonName_default = NodeOPCUA\n# emailAddress = Email Address\n# emailAddress_max = 40\n# emailAddress_default = node-opcua (at) node-opcua (dot) com\nsubjectAltName = $ENV::ALTNAME';
340
+ simple_config_template_cnf_default = config;
341
+ }
342
+ });
343
+
344
+ // packages/node-opcua-pki/lib/pki/certificate_manager.ts
345
+ import fs4 from "fs";
346
+ import path2 from "path";
347
+ import { withLock } from "@ster5/global-mutex";
348
+ import chalk3 from "chalk";
349
+ import chokidar from "chokidar";
350
+ import {
351
+ exploreCertificate,
352
+ exploreCertificateInfo,
353
+ exploreCertificateRevocationList,
354
+ generatePrivateKeyFile,
355
+ makeSHA1Thumbprint,
356
+ readCertificate,
357
+ readCertificateRevocationList,
358
+ split_der,
359
+ toPem,
360
+ verifyCertificateChain,
361
+ verifyCertificateSignature
362
+ } from "node-opcua-crypto";
363
+ function getOrComputeInfo(entry) {
364
+ if (!entry.info) {
365
+ entry.info = exploreCertificate(entry.certificate);
366
+ }
367
+ return entry.info;
368
+ }
369
+ function makeFingerprint(certificate) {
370
+ const chain = split_der(certificate);
371
+ return makeSHA1Thumbprint(chain[0]).toString("hex");
372
+ }
373
+ function short(stringToShorten) {
374
+ return stringToShorten.substring(0, 10);
375
+ }
376
+ function buildIdealCertificateName(certificate) {
377
+ const fingerprint2 = makeFingerprint(certificate);
378
+ try {
379
+ const commonName = exploreCertificate(certificate).tbsCertificate.subject.commonName || "";
380
+ const sanitizedCommonName = commonName.replace(forbiddenChars, "_");
381
+ return `${sanitizedCommonName}[${fingerprint2}]`;
382
+ } catch (_err) {
383
+ return `invalid_certificate_[${fingerprint2}]`;
384
+ }
385
+ }
386
+ function findMatchingIssuerKey(entries, wantedIssuerKey) {
387
+ return entries.filter((entry) => {
388
+ const info = getOrComputeInfo(entry);
389
+ return info.tbsCertificate.extensions && info.tbsCertificate.extensions.subjectKeyIdentifier === wantedIssuerKey;
390
+ });
391
+ }
392
+ function isSelfSigned2(info) {
393
+ return info.tbsCertificate.extensions?.subjectKeyIdentifier === info.tbsCertificate.extensions?.authorityKeyIdentifier?.keyIdentifier;
394
+ }
395
+ function isSelfSigned3(certificate) {
396
+ const info = exploreCertificate(certificate);
397
+ return isSelfSigned2(info);
398
+ }
399
+ function findIssuerCertificateInChain(certificate, chain) {
400
+ if (!certificate) {
401
+ return null;
402
+ }
403
+ const certInfo = exploreCertificate(certificate);
404
+ if (isSelfSigned2(certInfo)) {
405
+ return certificate;
406
+ }
407
+ const wantedIssuerKey = certInfo.tbsCertificate.extensions?.authorityKeyIdentifier?.keyIdentifier;
408
+ if (!wantedIssuerKey) {
409
+ debugLog("Certificate has no extension 3");
410
+ return null;
411
+ }
412
+ const potentialIssuers = chain.filter((c) => {
413
+ const info = exploreCertificate(c);
414
+ return info.tbsCertificate.extensions && info.tbsCertificate.extensions.subjectKeyIdentifier === wantedIssuerKey;
415
+ });
416
+ if (potentialIssuers.length === 1) {
417
+ return potentialIssuers[0];
418
+ }
419
+ if (potentialIssuers.length > 1) {
420
+ debugLog("findIssuerCertificateInChain: certificate is not self-signed but has several issuers");
421
+ return potentialIssuers[0];
422
+ }
423
+ return null;
424
+ }
425
+ var configurationFileSimpleTemplate, fsWriteFile, forbiddenChars, CertificateManager;
426
+ var init_certificate_manager = __esm({
427
+ "packages/node-opcua-pki/lib/pki/certificate_manager.ts"() {
428
+ "use strict";
429
+ init_esm_shims();
430
+ init_common2();
431
+ init_debug();
432
+ init_without_openssl();
433
+ init_simple_config_template_cnf();
434
+ configurationFileSimpleTemplate = simple_config_template_cnf_default;
435
+ fsWriteFile = fs4.promises.writeFile;
436
+ forbiddenChars = /[\x00-\x1F<>:"/\\|?*]/g;
437
+ CertificateManager = class _CertificateManager {
438
+ // ── Global instance registry ─────────────────────────────────
439
+ // Tracks all initialized CertificateManager instances so their
440
+ // file watchers can be closed automatically on process exit,
441
+ // even if the consumer forgets to call dispose().
442
+ static #activeInstances = /* @__PURE__ */ new Set();
443
+ static #cleanupInstalled = false;
444
+ static #installProcessCleanup() {
445
+ if (_CertificateManager.#cleanupInstalled) return;
446
+ _CertificateManager.#cleanupInstalled = true;
447
+ const closeDanglingWatchers = () => {
448
+ for (const cm of _CertificateManager.#activeInstances) {
449
+ for (const w of cm.#watchers) {
450
+ try {
451
+ w.close();
452
+ } catch {
453
+ }
454
+ }
455
+ cm.#watchers.splice(0);
456
+ cm.state = 4 /* Disposed */;
457
+ }
458
+ _CertificateManager.#activeInstances.clear();
459
+ };
460
+ process.on("beforeExit", closeDanglingWatchers);
461
+ for (const signal of ["SIGINT", "SIGTERM"]) {
462
+ process.once(signal, () => {
463
+ closeDanglingWatchers();
464
+ process.exit();
465
+ });
466
+ }
467
+ }
468
+ /**
469
+ * Dispose **all** active CertificateManager instances,
470
+ * closing their file watchers and freeing resources.
471
+ *
472
+ * This is mainly useful in test tear-down to ensure the
473
+ * Node.js process can exit cleanly.
474
+ */
475
+ static async disposeAll() {
476
+ const instances = [..._CertificateManager.#activeInstances];
477
+ await Promise.all(instances.map((cm) => cm.dispose()));
478
+ }
479
+ /**
480
+ * Assert that all CertificateManager instances have been
481
+ * properly disposed. Throws an Error listing the locations
482
+ * of any leaked instances.
483
+ *
484
+ * Intended for use in test `afterAll()` / `afterEach()`
485
+ * hooks to catch missing `dispose()` calls early.
486
+ *
487
+ * @example
488
+ * ```ts
489
+ * after(() => {
490
+ * CertificateManager.checkAllDisposed();
491
+ * });
492
+ * ```
493
+ */
494
+ static checkAllDisposed() {
495
+ if (_CertificateManager.#activeInstances.size === 0) return;
496
+ const locations = [..._CertificateManager.#activeInstances].map((cm) => cm.rootDir);
497
+ throw new Error(
498
+ `${_CertificateManager.#activeInstances.size} CertificateManager instance(s) not disposed:
499
+ - ${locations.join("\n - ")}`
500
+ );
501
+ }
502
+ // ─────────────────────────────────────────────────────────────
503
+ /**
504
+ * When `true` (the default), any certificate that is not
505
+ * already in the trusted or rejected store is automatically
506
+ * written to the rejected folder the first time it is seen.
507
+ */
508
+ untrustUnknownCertificate = true;
509
+ /** Current lifecycle state of this instance. */
510
+ state = 0 /* Uninitialized */;
511
+ /** @deprecated Use {@link folderPollingInterval} instead (typo fix). */
512
+ folderPoolingInterval = 5e3;
513
+ /** Interval in milliseconds for file-system polling (when enabled). */
514
+ get folderPollingInterval() {
515
+ return this.folderPoolingInterval;
516
+ }
517
+ set folderPollingInterval(value) {
518
+ this.folderPoolingInterval = value;
519
+ }
520
+ /** RSA key size used when generating the private key. */
521
+ keySize;
522
+ #location;
523
+ #watchers = [];
524
+ #readCertificatesCalled = false;
525
+ #filenameToHash = /* @__PURE__ */ new Map();
526
+ #initializingPromise;
527
+ #thumbs = {
528
+ rejected: /* @__PURE__ */ new Map(),
529
+ trusted: /* @__PURE__ */ new Map(),
530
+ issuers: {
531
+ certs: /* @__PURE__ */ new Map()
532
+ },
533
+ crl: /* @__PURE__ */ new Map(),
534
+ issuersCrl: /* @__PURE__ */ new Map()
535
+ };
536
+ /**
537
+ * Create a new CertificateManager.
538
+ *
539
+ * The constructor creates the root directory if it does not
540
+ * exist but does **not** initialise the PKI store — call
541
+ * {@link initialize} before using any other method.
542
+ *
543
+ * @param options - configuration options
544
+ */
545
+ constructor(options) {
546
+ options.keySize = options.keySize || 2048;
547
+ if (!options.location) {
548
+ throw new Error("CertificateManager: missing 'location' option");
549
+ }
550
+ this.#location = makePath(options.location, "");
551
+ this.keySize = options.keySize;
552
+ mkdirRecursiveSync(options.location);
553
+ if (!fs4.existsSync(this.#location)) {
554
+ throw new Error(`CertificateManager cannot access location ${this.#location}`);
555
+ }
556
+ }
557
+ /** Path to the OpenSSL configuration file. */
558
+ get configFile() {
559
+ return path2.join(this.rootDir, "own/openssl.cnf");
560
+ }
561
+ /** Root directory of the PKI store. */
562
+ get rootDir() {
563
+ return this.#location;
564
+ }
565
+ /** Path to the private key file (`own/private/private_key.pem`). */
566
+ get privateKey() {
567
+ return path2.join(this.rootDir, "own/private/private_key.pem");
568
+ }
569
+ /** Path to the OpenSSL random seed file. */
570
+ get randomFile() {
571
+ return path2.join(this.rootDir, "./random.rnd");
572
+ }
573
+ /**
574
+ * Move a certificate to the rejected store.
575
+ * If the certificate was previously trusted, it will be removed from the trusted folder.
576
+ * @param certificate - the DER-encoded certificate
577
+ */
578
+ async rejectCertificate(certificate) {
579
+ await this.#moveCertificate(certificate, "rejected");
580
+ }
581
+ /**
582
+ * Move a certificate to the trusted store.
583
+ * If the certificate was previously rejected, it will be removed from the rejected folder.
584
+ * @param certificate - the DER-encoded certificate
585
+ */
586
+ async trustCertificate(certificate) {
587
+ await this.#moveCertificate(certificate, "trusted");
588
+ }
589
+ /** Path to the rejected certificates folder. */
590
+ get rejectedFolder() {
591
+ return path2.join(this.rootDir, "rejected");
592
+ }
593
+ /** Path to the trusted certificates folder. */
594
+ get trustedFolder() {
595
+ return path2.join(this.rootDir, "trusted/certs");
596
+ }
597
+ /** Path to the trusted CRL folder. */
598
+ get crlFolder() {
599
+ return path2.join(this.rootDir, "trusted/crl");
600
+ }
601
+ /** Path to the issuer (CA) certificates folder. */
602
+ get issuersCertFolder() {
603
+ return path2.join(this.rootDir, "issuers/certs");
604
+ }
605
+ /** Path to the issuer CRL folder. */
606
+ get issuersCrlFolder() {
607
+ return path2.join(this.rootDir, "issuers/crl");
608
+ }
609
+ /** Path to the own certificate folder. */
610
+ get ownCertFolder() {
611
+ return path2.join(this.rootDir, "own/certs");
612
+ }
613
+ get ownPrivateFolder() {
614
+ return path2.join(this.rootDir, "own/private");
615
+ }
616
+ /**
617
+ * Check if a certificate is in the trusted store.
618
+ * If the certificate is unknown and `untrustUnknownCertificate` is set,
619
+ * it will be written to the rejected folder.
620
+ * @param certificate - the DER-encoded certificate
621
+ * @returns `"Good"` if trusted, `"BadCertificateUntrusted"` if rejected/unknown,
622
+ * or `"BadCertificateInvalid"` if the certificate cannot be parsed.
623
+ */
624
+ async isCertificateTrusted(certificate) {
625
+ const fingerprint2 = makeFingerprint(certificate);
626
+ if (this.#thumbs.trusted.has(fingerprint2)) {
627
+ return "Good";
628
+ }
629
+ if (!this.#thumbs.rejected.has(fingerprint2)) {
630
+ if (!this.untrustUnknownCertificate) {
631
+ return "Good";
632
+ }
633
+ try {
634
+ exploreCertificateInfo(certificate);
635
+ } catch (_err) {
636
+ return "BadCertificateInvalid";
637
+ }
638
+ const filename = path2.join(this.rejectedFolder, `${buildIdealCertificateName(certificate)}.pem`);
639
+ debugLog("certificate has never been seen before and is now rejected (untrusted) ", filename);
640
+ await fsWriteFile(filename, toPem(certificate, "CERTIFICATE"));
641
+ this.#thumbs.rejected.set(fingerprint2, { certificate, filename });
642
+ }
643
+ return "BadCertificateUntrusted";
644
+ }
645
+ async #innerVerifyCertificateAsync(certificate, _isIssuer, level, options) {
646
+ if (level >= 5) {
647
+ return "BadSecurityChecksFailed" /* BadSecurityChecksFailed */;
648
+ }
649
+ const chain = split_der(certificate);
650
+ debugLog("NB CERTIFICATE IN CHAIN = ", chain.length);
651
+ const info = exploreCertificate(chain[0]);
652
+ let hasValidIssuer = false;
653
+ let hasTrustedIssuer = false;
654
+ const hasIssuerKey = info.tbsCertificate.extensions?.authorityKeyIdentifier?.keyIdentifier;
655
+ debugLog("Certificate as an Issuer Key", hasIssuerKey);
656
+ if (hasIssuerKey) {
657
+ const isSelfSigned = isSelfSigned2(info);
658
+ debugLog("Is the Certificate self-signed ?", isSelfSigned);
659
+ if (!isSelfSigned) {
660
+ debugLog(
661
+ "Is issuer found in the list of know issuers ?",
662
+ "\n subjectKeyIdentifier = ",
663
+ info.tbsCertificate.extensions?.subjectKeyIdentifier,
664
+ "\n authorityKeyIdentifier = ",
665
+ info.tbsCertificate.extensions?.authorityKeyIdentifier?.keyIdentifier
666
+ );
667
+ let issuerCertificate = await this.findIssuerCertificate(chain[0]);
668
+ if (!issuerCertificate) {
669
+ issuerCertificate = findIssuerCertificateInChain(chain[0], chain);
670
+ if (!issuerCertificate) {
671
+ debugLog(
672
+ " the issuer has not been found in the chain itself nor in the issuer.cert list => the chain is incomplete!"
673
+ );
674
+ return "BadCertificateChainIncomplete" /* BadCertificateChainIncomplete */;
675
+ }
676
+ debugLog(" the issuer certificate has been found in the chain itself ! the chain is complete !");
677
+ } else {
678
+ debugLog(" the issuer certificate has been found in the issuer.cert folder !");
679
+ }
680
+ const issuerStatus = await this.#innerVerifyCertificateAsync(issuerCertificate, true, level + 1, options);
681
+ if (issuerStatus === "BadCertificateRevocationUnknown" /* BadCertificateRevocationUnknown */) {
682
+ return "BadCertificateIssuerRevocationUnknown" /* BadCertificateIssuerRevocationUnknown */;
683
+ }
684
+ if (issuerStatus === "BadCertificateIssuerRevocationUnknown" /* BadCertificateIssuerRevocationUnknown */) {
685
+ return "BadCertificateIssuerRevocationUnknown" /* BadCertificateIssuerRevocationUnknown */;
686
+ }
687
+ if (issuerStatus === "BadCertificateTimeInvalid" /* BadCertificateTimeInvalid */) {
688
+ if (!options || !options.acceptOutDatedIssuerCertificate) {
689
+ return "BadCertificateIssuerTimeInvalid" /* BadCertificateIssuerTimeInvalid */;
690
+ }
691
+ }
692
+ if (issuerStatus === "BadCertificateUntrusted" /* BadCertificateUntrusted */) {
693
+ debugLog("warning issuerStatus = ", issuerStatus.toString(), "the issuer certificate is not trusted");
694
+ }
695
+ if (issuerStatus !== "Good" /* Good */ && issuerStatus !== "BadCertificateUntrusted" /* BadCertificateUntrusted */) {
696
+ return "BadSecurityChecksFailed" /* BadSecurityChecksFailed */;
697
+ }
698
+ const isCertificateSignatureOK = verifyCertificateSignature(certificate, issuerCertificate);
699
+ if (!isCertificateSignatureOK) {
700
+ debugLog(" the certificate was not signed by the issuer as it claim to be ! Danger");
701
+ return "BadSecurityChecksFailed" /* BadSecurityChecksFailed */;
702
+ }
703
+ hasValidIssuer = true;
704
+ let revokedStatus = await this.isCertificateRevoked(certificate);
705
+ if (revokedStatus === "BadCertificateRevocationUnknown" /* BadCertificateRevocationUnknown */) {
706
+ if (options?.ignoreMissingRevocationList) {
707
+ revokedStatus = "Good" /* Good */;
708
+ }
709
+ }
710
+ if (revokedStatus !== "Good" /* Good */) {
711
+ debugLog("revokedStatus", revokedStatus);
712
+ return revokedStatus;
713
+ }
714
+ const issuerTrustedStatus = await this.#checkRejectedOrTrusted(issuerCertificate);
715
+ debugLog("issuerTrustedStatus", issuerTrustedStatus);
716
+ if (issuerTrustedStatus === "unknown") {
717
+ hasTrustedIssuer = false;
718
+ } else if (issuerTrustedStatus === "trusted") {
719
+ hasTrustedIssuer = true;
720
+ } else if (issuerTrustedStatus === "rejected") {
721
+ return "BadSecurityChecksFailed" /* BadSecurityChecksFailed */;
722
+ }
723
+ } else {
724
+ const isCertificateSignatureOK = verifyCertificateSignature(certificate, certificate);
725
+ if (!isCertificateSignatureOK) {
726
+ debugLog("Self-signed Certificate signature is not valid");
727
+ return "BadSecurityChecksFailed" /* BadSecurityChecksFailed */;
728
+ }
729
+ const revokedStatus = await this.isCertificateRevoked(certificate);
730
+ debugLog("revokedStatus of self signed certificate:", revokedStatus);
731
+ }
732
+ }
733
+ const status = await this.#checkRejectedOrTrusted(certificate);
734
+ if (status === "rejected") {
735
+ if (!(options.acceptCertificateWithValidIssuerChain && hasValidIssuer && hasTrustedIssuer)) {
736
+ return "BadCertificateUntrusted" /* BadCertificateUntrusted */;
737
+ }
738
+ }
739
+ const _c2 = chain[1] ? exploreCertificateInfo(chain[1]) : "non";
740
+ debugLog("chain[1] info=", _c2);
741
+ const certificateInfo = exploreCertificateInfo(certificate);
742
+ const now = /* @__PURE__ */ new Date();
743
+ let isTimeInvalid = false;
744
+ if (certificateInfo.notBefore.getTime() > now.getTime()) {
745
+ debugLog(
746
+ chalk3.red("certificate is invalid : certificate is not active yet !") + " not before date =" + certificateInfo.notBefore
747
+ );
748
+ if (!options.acceptPendingCertificate) {
749
+ isTimeInvalid = true;
750
+ }
751
+ }
752
+ if (certificateInfo.notAfter.getTime() <= now.getTime()) {
753
+ debugLog(
754
+ `${chalk3.red("certificate is invalid : certificate has expired !")} not after date =${certificateInfo.notAfter}`
755
+ );
756
+ if (!options.acceptOutdatedCertificate) {
757
+ isTimeInvalid = true;
758
+ }
759
+ }
760
+ if (status === "trusted") {
761
+ return isTimeInvalid ? "BadCertificateTimeInvalid" /* BadCertificateTimeInvalid */ : "Good" /* Good */;
762
+ }
763
+ if (hasIssuerKey) {
764
+ if (!hasTrustedIssuer) {
765
+ return "BadCertificateUntrusted" /* BadCertificateUntrusted */;
766
+ }
767
+ if (!hasValidIssuer) {
768
+ return "BadCertificateUntrusted" /* BadCertificateUntrusted */;
769
+ }
770
+ if (!options.acceptCertificateWithValidIssuerChain) {
771
+ return "BadCertificateUntrusted" /* BadCertificateUntrusted */;
772
+ }
773
+ return isTimeInvalid ? "BadCertificateTimeInvalid" /* BadCertificateTimeInvalid */ : "Good" /* Good */;
774
+ } else {
775
+ return "BadCertificateUntrusted" /* BadCertificateUntrusted */;
776
+ }
777
+ }
778
+ /**
779
+ * Internal verification hook called by {@link verifyCertificate}.
780
+ *
781
+ * Subclasses can override this to inject additional validation
782
+ * logic (e.g. application-level policy checks) while still
783
+ * delegating to the default chain/CRL/trust verification.
784
+ *
785
+ * @param certificate - the DER-encoded certificate to verify
786
+ * @param options - verification options forwarded from the
787
+ * public API
788
+ * @returns the verification status code
789
+ */
790
+ async verifyCertificateAsync(certificate, options) {
791
+ const status1 = await this.#innerVerifyCertificateAsync(certificate, false, 0, options);
792
+ return status1;
793
+ }
794
+ /**
795
+ * Verify a certificate against the PKI trust store.
796
+ *
797
+ * This performs a full validation including trust status,
798
+ * issuer chain, CRL revocation checks, and time validity.
799
+ *
800
+ * @param certificate - the DER-encoded certificate to verify
801
+ * @param options - optional flags to relax validation rules
802
+ * @returns the verification status code
803
+ */
804
+ async verifyCertificate(certificate, options) {
805
+ if (!certificate) {
806
+ return "BadSecurityChecksFailed" /* BadSecurityChecksFailed */;
807
+ }
808
+ try {
809
+ const status = await this.verifyCertificateAsync(certificate, options || {});
810
+ return status;
811
+ } catch (error) {
812
+ warningLog(`verifyCertificate error: ${error.message}`);
813
+ return "BadCertificateInvalid" /* BadCertificateInvalid */;
814
+ }
815
+ }
816
+ /**
817
+ * Initialize the PKI directory structure, generate the
818
+ * private key (if missing), and start file-system watchers.
819
+ *
820
+ * This method is idempotent — subsequent calls are no-ops.
821
+ * It must be called before any certificate operations.
822
+ */
823
+ async initialize() {
824
+ if (this.state !== 0 /* Uninitialized */) {
825
+ return;
826
+ }
827
+ this.state = 1 /* Initializing */;
828
+ this.#initializingPromise = this.#initialize();
829
+ await this.#initializingPromise;
830
+ this.#initializingPromise = void 0;
831
+ this.state = 2 /* Initialized */;
832
+ _CertificateManager.#activeInstances.add(this);
833
+ _CertificateManager.#installProcessCleanup();
834
+ }
835
+ async #initialize() {
836
+ this.state = 1 /* Initializing */;
837
+ const pkiDir = this.#location;
838
+ mkdirRecursiveSync(pkiDir);
839
+ mkdirRecursiveSync(path2.join(pkiDir, "own"));
840
+ mkdirRecursiveSync(path2.join(pkiDir, "own/certs"));
841
+ mkdirRecursiveSync(path2.join(pkiDir, "own/private"));
842
+ mkdirRecursiveSync(path2.join(pkiDir, "rejected"));
843
+ mkdirRecursiveSync(path2.join(pkiDir, "trusted"));
844
+ mkdirRecursiveSync(path2.join(pkiDir, "trusted/certs"));
845
+ mkdirRecursiveSync(path2.join(pkiDir, "trusted/crl"));
846
+ mkdirRecursiveSync(path2.join(pkiDir, "issuers"));
847
+ mkdirRecursiveSync(path2.join(pkiDir, "issuers/certs"));
848
+ mkdirRecursiveSync(path2.join(pkiDir, "issuers/crl"));
849
+ if (!fs4.existsSync(this.configFile) || !fs4.existsSync(this.privateKey)) {
850
+ return await this.withLock2(async () => {
851
+ if (this.state === 3 /* Disposing */ || this.state === 4 /* Disposed */) {
852
+ return;
853
+ }
854
+ if (!fs4.existsSync(this.configFile)) {
855
+ fs4.writeFileSync(this.configFile, configurationFileSimpleTemplate);
856
+ }
857
+ if (!fs4.existsSync(this.privateKey)) {
858
+ debugLog("generating private key ...");
859
+ await generatePrivateKeyFile(this.privateKey, this.keySize);
860
+ await this.#readCertificates();
861
+ } else {
862
+ await this.#readCertificates();
863
+ }
864
+ });
865
+ } else {
866
+ await this.#readCertificates();
867
+ }
868
+ }
869
+ /**
870
+ * Dispose of the CertificateManager, releasing file watchers
871
+ * and other resources. The instance should not be used after
872
+ * calling this method.
873
+ */
874
+ async dispose() {
875
+ if (this.state === 3 /* Disposing */) {
876
+ throw new Error("Already disposing");
877
+ }
878
+ if (this.state === 0 /* Uninitialized */) {
879
+ this.state = 4 /* Disposed */;
880
+ return;
881
+ }
882
+ if (this.state === 1 /* Initializing */) {
883
+ if (this.#initializingPromise) {
884
+ await this.#initializingPromise;
885
+ }
886
+ }
887
+ try {
888
+ this.state = 3 /* Disposing */;
889
+ await Promise.all(this.#watchers.map((w) => w.close()));
890
+ this.#watchers.forEach((w) => {
891
+ w.removeAllListeners();
892
+ });
893
+ this.#watchers.splice(0);
894
+ } finally {
895
+ this.state = 4 /* Disposed */;
896
+ _CertificateManager.#activeInstances.delete(this);
897
+ }
898
+ }
899
+ /**
900
+ * Force a full re-scan of all PKI folders, rebuilding
901
+ * the in-memory `_thumbs` index from scratch.
902
+ *
903
+ * Call this after external processes have modified the
904
+ * PKI folders (e.g. via `writeTrustList` or CLI tools)
905
+ * to ensure the CertificateManager sees the latest
906
+ * state without waiting for file-system events.
907
+ */
908
+ async reloadCertificates() {
909
+ await Promise.all(this.#watchers.map((w) => w.close()));
910
+ for (const w of this.#watchers) {
911
+ w.removeAllListeners();
912
+ }
913
+ this.#watchers.splice(0);
914
+ this.#thumbs.rejected.clear();
915
+ this.#thumbs.trusted.clear();
916
+ this.#thumbs.issuers.certs.clear();
917
+ this.#thumbs.crl.clear();
918
+ this.#thumbs.issuersCrl.clear();
919
+ this.#filenameToHash.clear();
920
+ this.#readCertificatesCalled = false;
921
+ await this.#readCertificates();
922
+ }
923
+ async withLock2(action) {
924
+ const lockFileName = path2.join(this.rootDir, "mutex.lock");
925
+ return withLock({ fileToLock: lockFileName }, async () => {
926
+ return await action();
927
+ });
928
+ }
929
+ /**
930
+ * Create a self-signed certificate for this PKI's private key.
931
+ *
932
+ * The certificate is written to `params.outputFile` or
933
+ * `own/certs/self_signed_certificate.pem` by default.
934
+ *
935
+ * @param params - certificate parameters (subject, SANs,
936
+ * validity, etc.)
937
+ */
938
+ async createSelfSignedCertificate(params) {
939
+ if (typeof params.applicationUri !== "string") {
940
+ throw new Error("createSelfSignedCertificate: expecting applicationUri to be a string");
941
+ }
942
+ if (!fs4.existsSync(this.privateKey)) {
943
+ throw new Error(`Cannot find private key ${this.privateKey}`);
944
+ }
945
+ let certificateFilename = path2.join(this.rootDir, "own/certs/self_signed_certificate.pem");
946
+ certificateFilename = params.outputFile || certificateFilename;
947
+ const _params = params;
948
+ _params.rootDir = this.rootDir;
949
+ _params.configFile = this.configFile;
950
+ _params.privateKey = this.privateKey;
951
+ _params.subject = params.subject || "CN=FIXME";
952
+ await this.withLock2(async () => {
953
+ await createSelfSignedCertificate(certificateFilename, _params);
954
+ });
955
+ }
956
+ /**
957
+ * Create a Certificate Signing Request (CSR) using this
958
+ * PKI's private key and configuration.
959
+ *
960
+ * The CSR file is written to `own/certs/` with a timestamped
961
+ * filename.
962
+ *
963
+ * @param params - CSR parameters (subject, SANs)
964
+ * @returns the filesystem path to the generated CSR file
965
+ */
966
+ async createCertificateRequest(params) {
967
+ if (!params) {
968
+ throw new Error("params is required");
969
+ }
970
+ const _params = params;
971
+ if (Object.prototype.hasOwnProperty.call(_params, "rootDir")) {
972
+ throw new Error("rootDir should not be specified ");
973
+ }
974
+ _params.rootDir = path2.resolve(this.rootDir);
975
+ _params.configFile = path2.resolve(this.configFile);
976
+ _params.privateKey = path2.resolve(this.privateKey);
977
+ return await this.withLock2(async () => {
978
+ const now = /* @__PURE__ */ new Date();
979
+ const today2 = `${now.toISOString().slice(0, 10)}_${now.getTime()}`;
980
+ const certificateSigningRequestFilename = path2.join(this.rootDir, "own/certs", `certificate_${today2}.csr`);
981
+ await createCertificateSigningRequestAsync(certificateSigningRequestFilename, _params);
982
+ return certificateSigningRequestFilename;
983
+ });
984
+ }
985
+ /**
986
+ * Add a CA (issuer) certificate to the issuers store.
987
+ * If the certificate is already present, this is a no-op.
988
+ * @param certificate - the DER-encoded CA certificate
989
+ * @param validate - if `true`, verify the certificate before adding
990
+ * @param addInTrustList - if `true`, also add to the trusted store
991
+ * @returns `VerificationStatus.Good` on success
992
+ */
993
+ async addIssuer(certificate, validate = false, addInTrustList = false) {
994
+ if (validate) {
995
+ const status = await this.verifyCertificate(certificate);
996
+ if (status !== "Good" /* Good */ && status !== "BadCertificateUntrusted" /* BadCertificateUntrusted */) {
997
+ return status;
998
+ }
999
+ }
1000
+ const pemCertificate = toPem(certificate, "CERTIFICATE");
1001
+ const fingerprint2 = makeFingerprint(certificate);
1002
+ if (this.#thumbs.issuers.certs.has(fingerprint2)) {
1003
+ return "Good" /* Good */;
1004
+ }
1005
+ const filename = path2.join(this.issuersCertFolder, `issuer_${buildIdealCertificateName(certificate)}.pem`);
1006
+ await fs4.promises.writeFile(filename, pemCertificate, "ascii");
1007
+ this.#thumbs.issuers.certs.set(fingerprint2, { certificate, filename });
1008
+ if (addInTrustList) {
1009
+ await this.trustCertificate(certificate);
1010
+ }
1011
+ return "Good" /* Good */;
1012
+ }
1013
+ /**
1014
+ * Add a CRL to the certificate manager.
1015
+ * @param crl - the CRL to add
1016
+ * @param target - "issuers" (default) writes to issuers/crl, "trusted" writes to trusted/crl
1017
+ */
1018
+ async addRevocationList(crl, target = "issuers") {
1019
+ return await this.withLock2(async () => {
1020
+ try {
1021
+ const index = target === "trusted" ? this.#thumbs.crl : this.#thumbs.issuersCrl;
1022
+ const folder = target === "trusted" ? this.crlFolder : this.issuersCrlFolder;
1023
+ const crlInfo = exploreCertificateRevocationList(crl);
1024
+ const key = crlInfo.tbsCertList.issuerFingerprint;
1025
+ if (!index.has(key)) {
1026
+ index.set(key, { crls: [], serialNumbers: {} });
1027
+ }
1028
+ const pemCertificate = toPem(crl, "X509 CRL");
1029
+ const filename = path2.join(folder, `crl_${buildIdealCertificateName(crl)}.pem`);
1030
+ await fs4.promises.writeFile(filename, pemCertificate, "ascii");
1031
+ await this.#onCrlFileAdded(index, filename);
1032
+ await this.#waitAndCheckCRLProcessingStatus();
1033
+ return "Good" /* Good */;
1034
+ } catch (err) {
1035
+ debugLog(err);
1036
+ return "BadSecurityChecksFailed" /* BadSecurityChecksFailed */;
1037
+ }
1038
+ });
1039
+ }
1040
+ /**
1041
+ * Remove all CRL files from the specified folder(s) and clear the
1042
+ * corresponding in-memory index.
1043
+ * @param target - "issuers" clears issuers/crl, "trusted" clears
1044
+ * trusted/crl, "all" clears both.
1045
+ */
1046
+ async clearRevocationLists(target) {
1047
+ const clearFolder = async (folder, index) => {
1048
+ try {
1049
+ const files = await fs4.promises.readdir(folder);
1050
+ for (const file of files) {
1051
+ const ext = path2.extname(file).toLowerCase();
1052
+ if (ext === ".crl" || ext === ".pem" || ext === ".der") {
1053
+ await fs4.promises.unlink(path2.join(folder, file));
1054
+ }
1055
+ }
1056
+ } catch (err) {
1057
+ if (err.code !== "ENOENT") {
1058
+ throw err;
1059
+ }
1060
+ }
1061
+ index.clear();
1062
+ };
1063
+ if (target === "issuers" || target === "all") {
1064
+ await clearFolder(this.issuersCrlFolder, this.#thumbs.issuersCrl);
1065
+ }
1066
+ if (target === "trusted" || target === "all") {
1067
+ await clearFolder(this.crlFolder, this.#thumbs.crl);
1068
+ }
1069
+ }
1070
+ /**
1071
+ * Check whether an issuer certificate with the given thumbprint
1072
+ * is already registered.
1073
+ * @param thumbprint - hex-encoded SHA-1 thumbprint (lowercase)
1074
+ */
1075
+ async hasIssuer(thumbprint) {
1076
+ await this.#readCertificates();
1077
+ const normalized = thumbprint.toLowerCase();
1078
+ return this.#thumbs.issuers.certs.has(normalized);
1079
+ }
1080
+ /**
1081
+ * Remove a trusted certificate identified by its SHA-1 thumbprint.
1082
+ * Deletes the file on disk and removes the entry from the
1083
+ * in-memory index.
1084
+ * @param thumbprint - hex-encoded SHA-1 thumbprint (lowercase)
1085
+ * @returns the removed certificate buffer, or `null` if not found
1086
+ */
1087
+ async removeTrustedCertificate(thumbprint) {
1088
+ await this.#readCertificates();
1089
+ const normalized = thumbprint.toLowerCase();
1090
+ const entry = this.#thumbs.trusted.get(normalized);
1091
+ if (!entry) {
1092
+ return null;
1093
+ }
1094
+ try {
1095
+ await fs4.promises.unlink(entry.filename);
1096
+ } catch (err) {
1097
+ if (err.code !== "ENOENT") {
1098
+ throw err;
1099
+ }
1100
+ }
1101
+ this.#thumbs.trusted.delete(normalized);
1102
+ return entry.certificate;
1103
+ }
1104
+ /**
1105
+ * Remove an issuer certificate identified by its SHA-1 thumbprint.
1106
+ * Deletes the file on disk and removes the entry from the
1107
+ * in-memory index.
1108
+ * @param thumbprint - hex-encoded SHA-1 thumbprint (lowercase)
1109
+ * @returns the removed certificate buffer, or `null` if not found
1110
+ */
1111
+ async removeIssuer(thumbprint) {
1112
+ await this.#readCertificates();
1113
+ const normalized = thumbprint.toLowerCase();
1114
+ const entry = this.#thumbs.issuers.certs.get(normalized);
1115
+ if (!entry) {
1116
+ return null;
1117
+ }
1118
+ try {
1119
+ await fs4.promises.unlink(entry.filename);
1120
+ } catch (err) {
1121
+ if (err.code !== "ENOENT") {
1122
+ throw err;
1123
+ }
1124
+ }
1125
+ this.#thumbs.issuers.certs.delete(normalized);
1126
+ return entry.certificate;
1127
+ }
1128
+ /**
1129
+ * Remove all CRL files that were issued by the given CA certificate
1130
+ * from the specified folder (or both).
1131
+ * @param issuerCertificate - the CA certificate whose CRLs to remove
1132
+ * @param target - "issuers", "trusted", or "all" (default "all")
1133
+ */
1134
+ async removeRevocationListsForIssuer(issuerCertificate, target = "all") {
1135
+ const issuerInfo = exploreCertificate(issuerCertificate);
1136
+ const issuerFingerprint = issuerInfo.tbsCertificate.subjectFingerPrint;
1137
+ const processIndex = async (index) => {
1138
+ const crlData = index.get(issuerFingerprint);
1139
+ if (!crlData) return;
1140
+ for (const crlEntry of crlData.crls) {
1141
+ try {
1142
+ await fs4.promises.unlink(crlEntry.filename);
1143
+ } catch (err) {
1144
+ if (err.code !== "ENOENT") {
1145
+ throw err;
1146
+ }
1147
+ }
1148
+ }
1149
+ index.delete(issuerFingerprint);
1150
+ };
1151
+ if (target === "issuers" || target === "all") {
1152
+ await processIndex(this.#thumbs.issuersCrl);
1153
+ }
1154
+ if (target === "trusted" || target === "all") {
1155
+ await processIndex(this.#thumbs.crl);
1156
+ }
1157
+ }
1158
+ /**
1159
+ * Validate a certificate (optionally with its chain) and add
1160
+ * the leaf certificate to the trusted store.
1161
+ *
1162
+ * The certificate buffer may contain a single certificate or a
1163
+ * full chain (leaf + issuer certificates concatenated in DER).
1164
+ * Only the leaf certificate is added to the trusted store.
1165
+ *
1166
+ * When the chain contains issuer certificates, this method
1167
+ * verifies that each issuer is already registered via
1168
+ * {@link addIssuer} before trusting the leaf.
1169
+ *
1170
+ * @param certificateChain - DER-encoded certificate or chain
1171
+ * @returns `VerificationStatus.Good` on success, or an error
1172
+ * status indicating why the certificate was rejected.
1173
+ */
1174
+ async addTrustedCertificateFromChain(certificateChain) {
1175
+ const certificates = split_der(certificateChain);
1176
+ const leafCertificate = certificates[0];
1177
+ try {
1178
+ exploreCertificate(leafCertificate);
1179
+ } catch (_err) {
1180
+ return "BadCertificateInvalid" /* BadCertificateInvalid */;
1181
+ }
1182
+ const result = await verifyCertificateChain([leafCertificate]);
1183
+ if (result.status !== "Good") {
1184
+ return "BadCertificateInvalid" /* BadCertificateInvalid */;
1185
+ }
1186
+ if (certificates.length > 1) {
1187
+ const issuerFolder = this.issuersCertFolder;
1188
+ const issuerThumbprints = /* @__PURE__ */ new Set();
1189
+ const files = await fs4.promises.readdir(issuerFolder);
1190
+ for (const file of files) {
1191
+ const ext = path2.extname(file).toLowerCase();
1192
+ if (ext === ".pem" || ext === ".der") {
1193
+ try {
1194
+ const issuerCert = readCertificate(path2.join(issuerFolder, file));
1195
+ const fp = makeFingerprint(issuerCert);
1196
+ issuerThumbprints.add(fp);
1197
+ } catch (_err) {
1198
+ }
1199
+ }
1200
+ }
1201
+ for (const issuerCert of certificates.slice(1)) {
1202
+ const thumbprint = makeFingerprint(issuerCert);
1203
+ if (!issuerThumbprints.has(thumbprint)) {
1204
+ return "BadCertificateChainIncomplete" /* BadCertificateChainIncomplete */;
1205
+ }
1206
+ }
1207
+ }
1208
+ await this.trustCertificate(leafCertificate);
1209
+ return "Good" /* Good */;
1210
+ }
1211
+ /**
1212
+ * Check whether an issuer certificate is still needed by any
1213
+ * certificate in the trusted store.
1214
+ *
1215
+ * This is used before removing an issuer to ensure that
1216
+ * doing so would not break the chain of any trusted
1217
+ * certificate.
1218
+ *
1219
+ * @param issuerCertificate - the CA certificate to check
1220
+ * @returns `true` if at least one trusted certificate was
1221
+ * signed by this issuer.
1222
+ */
1223
+ async isIssuerInUseByTrustedCertificate(issuerCertificate) {
1224
+ await this.#readCertificates();
1225
+ for (const entry of this.#thumbs.trusted.values()) {
1226
+ if (!entry.certificate) continue;
1227
+ try {
1228
+ if (verifyCertificateSignature(entry.certificate, issuerCertificate)) {
1229
+ return true;
1230
+ }
1231
+ } catch (_err) {
1232
+ }
1233
+ }
1234
+ return false;
1235
+ }
1236
+ /**
1237
+ * find the issuer certificate among the trusted issuer certificates.
1238
+ *
1239
+ * The findIssuerCertificate method is an asynchronous method that attempts to find
1240
+ * the issuer certificate for a given certificate from the list of issuer certificate declared in the PKI
1241
+ *
1242
+ * - If the certificate is self-signed, it returns the certificate itself.
1243
+ *
1244
+ * - If the certificate has no extension 3, it is assumed to be generated by an old system, and a null value is returned.
1245
+ *
1246
+ * - the method checks both issuer and trusted certificates and returns the appropriate issuercertificate,
1247
+ * if found. If multiple matching certificates are found, a warning is logged to the console.
1248
+ *
1249
+ */
1250
+ async findIssuerCertificate(certificate) {
1251
+ const certInfo = exploreCertificate(certificate);
1252
+ if (isSelfSigned2(certInfo)) {
1253
+ return certificate;
1254
+ }
1255
+ const wantedIssuerKey = certInfo.tbsCertificate.extensions?.authorityKeyIdentifier?.keyIdentifier;
1256
+ if (!wantedIssuerKey) {
1257
+ debugLog("Certificate has no extension 3");
1258
+ return null;
1259
+ }
1260
+ const issuerCertificates = [...this.#thumbs.issuers.certs.values()];
1261
+ const selectedIssuerCertificates = findMatchingIssuerKey(issuerCertificates, wantedIssuerKey);
1262
+ if (selectedIssuerCertificates.length > 0) {
1263
+ if (selectedIssuerCertificates.length > 1) {
1264
+ warningLog("Warning more than one issuer certificate exists with subjectKeyIdentifier ", wantedIssuerKey);
1265
+ }
1266
+ return selectedIssuerCertificates[0].certificate || null;
1267
+ }
1268
+ const trustedCertificates = [...this.#thumbs.trusted.values()];
1269
+ const selectedTrustedCertificates = findMatchingIssuerKey(trustedCertificates, wantedIssuerKey);
1270
+ if (selectedTrustedCertificates.length > 1) {
1271
+ warningLog(
1272
+ "Warning more than one certificate exists with subjectKeyIdentifier in trusted certificate list ",
1273
+ wantedIssuerKey,
1274
+ selectedTrustedCertificates.length
1275
+ );
1276
+ }
1277
+ return selectedTrustedCertificates.length > 0 ? selectedTrustedCertificates[0].certificate : null;
1278
+ }
1279
+ /**
1280
+ *
1281
+ * check if the certificate explicitly appear in the trust list, the reject list or none.
1282
+ * In case of being in the reject and trusted list at the same time is consider: rejected.
1283
+ * @internal
1284
+ * @private
1285
+ */
1286
+ async #checkRejectedOrTrusted(certificate) {
1287
+ const fingerprint2 = makeFingerprint(certificate);
1288
+ debugLog("#checkRejectedOrTrusted fingerprint ", short(fingerprint2));
1289
+ await this.#readCertificates();
1290
+ if (this.#thumbs.rejected.has(fingerprint2)) {
1291
+ return "rejected";
1292
+ }
1293
+ if (this.#thumbs.trusted.has(fingerprint2)) {
1294
+ return "trusted";
1295
+ }
1296
+ return "unknown";
1297
+ }
1298
+ async #moveCertificate(certificate, newStatus) {
1299
+ await this.withLock2(async () => {
1300
+ const fingerprint2 = makeFingerprint(certificate);
1301
+ let status = await this.#checkRejectedOrTrusted(certificate);
1302
+ if (status === "unknown") {
1303
+ const pem = toPem(certificate, "CERTIFICATE");
1304
+ const filename = path2.join(this.rejectedFolder, `${buildIdealCertificateName(certificate)}.pem`);
1305
+ await fs4.promises.writeFile(filename, pem);
1306
+ this.#thumbs.rejected.set(fingerprint2, { certificate, filename });
1307
+ status = "rejected";
1308
+ }
1309
+ debugLog("#moveCertificate", fingerprint2.substring(0, 10), "from", status, "to", newStatus);
1310
+ if (status !== "rejected" && status !== "trusted") {
1311
+ throw new Error(`#moveCertificate: unexpected status '${status}' for certificate ${fingerprint2.substring(0, 10)}`);
1312
+ }
1313
+ if (status !== newStatus) {
1314
+ const indexSrc = status === "rejected" ? this.#thumbs.rejected : this.#thumbs.trusted;
1315
+ const srcEntry = indexSrc.get(fingerprint2);
1316
+ if (!srcEntry) {
1317
+ debugLog(" cannot find certificate ", fingerprint2.substring(0, 10), " in", status);
1318
+ throw new Error(`#moveCertificate: certificate ${fingerprint2.substring(0, 10)} not found in ${status} index`);
1319
+ }
1320
+ const destFolder = newStatus === "trusted" ? this.trustedFolder : this.rejectedFolder;
1321
+ const certificateDest = path2.join(destFolder, path2.basename(srcEntry.filename));
1322
+ debugLog("#moveCertificate", fingerprint2.substring(0, 10), "old name", srcEntry.filename);
1323
+ debugLog("#moveCertificate", fingerprint2.substring(0, 10), "new name", certificateDest);
1324
+ await fs4.promises.rename(srcEntry.filename, certificateDest);
1325
+ indexSrc.delete(fingerprint2);
1326
+ const indexDest = newStatus === "trusted" ? this.#thumbs.trusted : this.#thumbs.rejected;
1327
+ indexDest.set(fingerprint2, { certificate, filename: certificateDest });
1328
+ }
1329
+ });
1330
+ }
1331
+ #findAssociatedCRLs(issuerCertificate) {
1332
+ const issuerCertificateInfo = exploreCertificate(issuerCertificate);
1333
+ const key = issuerCertificateInfo.tbsCertificate.subjectFingerPrint;
1334
+ return this.#thumbs.issuersCrl.get(key) ?? this.#thumbs.crl.get(key) ?? null;
1335
+ }
1336
+ /**
1337
+ * Check whether a certificate has been revoked by its issuer's CRL.
1338
+ *
1339
+ * - Self-signed certificates are never considered revoked.
1340
+ * - If no `issuerCertificate` is provided, the method attempts
1341
+ * to find it via {@link findIssuerCertificate}.
1342
+ *
1343
+ * @param certificate - the DER-encoded certificate to check
1344
+ * @param issuerCertificate - optional issuer certificate; looked
1345
+ * up automatically when omitted
1346
+ * @returns `Good` if not revoked, `BadCertificateRevoked` if the
1347
+ * serial number appears in a CRL,
1348
+ * `BadCertificateRevocationUnknown` if no CRL is available,
1349
+ * or `BadCertificateChainIncomplete` if the issuer cannot be
1350
+ * found.
1351
+ */
1352
+ async isCertificateRevoked(certificate, issuerCertificate) {
1353
+ if (isSelfSigned3(certificate)) {
1354
+ return "Good" /* Good */;
1355
+ }
1356
+ if (!issuerCertificate) {
1357
+ issuerCertificate = await this.findIssuerCertificate(certificate);
1358
+ }
1359
+ if (!issuerCertificate) {
1360
+ return "BadCertificateChainIncomplete" /* BadCertificateChainIncomplete */;
1361
+ }
1362
+ const crls = this.#findAssociatedCRLs(issuerCertificate);
1363
+ if (!crls) {
1364
+ return "BadCertificateRevocationUnknown" /* BadCertificateRevocationUnknown */;
1365
+ }
1366
+ const certInfo = exploreCertificate(certificate);
1367
+ const serialNumber = certInfo.tbsCertificate.serialNumber || certInfo.tbsCertificate.extensions?.authorityKeyIdentifier?.serial || "";
1368
+ const key = certInfo.tbsCertificate.extensions?.authorityKeyIdentifier?.authorityCertIssuerFingerPrint || "<unknown>";
1369
+ const crl2 = this.#thumbs.crl.get(key) ?? null;
1370
+ if (crls.serialNumbers[serialNumber] || crl2?.serialNumbers[serialNumber]) {
1371
+ return "BadCertificateRevoked" /* BadCertificateRevoked */;
1372
+ }
1373
+ return "Good" /* Good */;
1374
+ }
1375
+ #pendingCrlToProcess = 0;
1376
+ #onCrlProcess;
1377
+ #queue = [];
1378
+ #onCrlFileAdded(index, filename) {
1379
+ this.#queue.push({ index, filename });
1380
+ this.#pendingCrlToProcess += 1;
1381
+ if (this.#pendingCrlToProcess === 1) {
1382
+ this.#processNextCrl();
1383
+ }
1384
+ }
1385
+ async #processNextCrl() {
1386
+ try {
1387
+ const nextCRL = this.#queue.shift();
1388
+ if (!nextCRL) return;
1389
+ const { index, filename } = nextCRL;
1390
+ const crl = await readCertificateRevocationList(filename);
1391
+ const crlInfo = exploreCertificateRevocationList(crl);
1392
+ debugLog(chalk3.cyan("add CRL in folder "), filename);
1393
+ const fingerprint2 = crlInfo.tbsCertList.issuerFingerprint;
1394
+ if (!index.has(fingerprint2)) {
1395
+ index.set(fingerprint2, { crls: [], serialNumbers: {} });
1396
+ }
1397
+ const data = index.get(fingerprint2) || { crls: [], serialNumbers: {} };
1398
+ data.crls.push({ crlInfo, filename });
1399
+ for (const revokedCertificate of crlInfo.tbsCertList.revokedCertificates) {
1400
+ const serialNumber = revokedCertificate.userCertificate;
1401
+ if (!data.serialNumbers[serialNumber]) {
1402
+ data.serialNumbers[serialNumber] = revokedCertificate.revocationDate;
1403
+ }
1404
+ }
1405
+ debugLog(chalk3.cyan("CRL"), fingerprint2, "serial numbers = ", Object.keys(data.serialNumbers));
1406
+ } catch (err) {
1407
+ debugLog("CRL filename error =");
1408
+ debugLog(err);
1409
+ }
1410
+ this.#pendingCrlToProcess -= 1;
1411
+ if (this.#pendingCrlToProcess === 0) {
1412
+ if (this.#onCrlProcess) {
1413
+ this.#onCrlProcess();
1414
+ this.#onCrlProcess = void 0;
1415
+ }
1416
+ } else {
1417
+ this.#processNextCrl();
1418
+ }
1419
+ }
1420
+ async #readCertificates() {
1421
+ if (this.#readCertificatesCalled) {
1422
+ return;
1423
+ }
1424
+ this.#readCertificatesCalled = true;
1425
+ const usePolling = process.env.OPCUA_PKI_USE_POLLING === "true";
1426
+ const chokidarOptions = {
1427
+ usePolling,
1428
+ ...usePolling ? { interval: Math.min(10 * 60 * 1e3, Math.max(100, this.folderPoolingInterval)) } : {},
1429
+ persistent: false
1430
+ };
1431
+ const createUnreffedWatcher = (folder) => {
1432
+ const capturedHandles = [];
1433
+ const origWatch = fs4.watch;
1434
+ fs4.watch = ((...args) => {
1435
+ const handle = origWatch.apply(fs4, args);
1436
+ capturedHandles.push(handle);
1437
+ return handle;
1438
+ });
1439
+ const w = chokidar.watch(folder, chokidarOptions);
1440
+ const unreffAll = () => {
1441
+ fs4.watch = origWatch;
1442
+ for (const h of capturedHandles) {
1443
+ h.unref();
1444
+ }
1445
+ };
1446
+ return { w, capturedHandles, unreffAll };
1447
+ };
1448
+ const promises = [
1449
+ this.#walkAllFiles(this.trustedFolder, this.#thumbs.trusted, createUnreffedWatcher),
1450
+ this.#walkAllFiles(this.issuersCertFolder, this.#thumbs.issuers.certs, createUnreffedWatcher),
1451
+ this.#walkAllFiles(this.rejectedFolder, this.#thumbs.rejected, createUnreffedWatcher),
1452
+ this.#walkCRLFiles(this.crlFolder, this.#thumbs.crl, createUnreffedWatcher),
1453
+ this.#walkCRLFiles(this.issuersCrlFolder, this.#thumbs.issuersCrl, createUnreffedWatcher)
1454
+ ];
1455
+ await Promise.all(promises);
1456
+ await this.#waitAndCheckCRLProcessingStatus();
1457
+ }
1458
+ async #walkCRLFiles(folder, index, createUnreffedWatcher) {
1459
+ await new Promise((resolve, _reject) => {
1460
+ const { w, unreffAll } = createUnreffedWatcher(folder);
1461
+ w.on("unlink", (filename) => {
1462
+ for (const [key, data] of index.entries()) {
1463
+ data.crls = data.crls.filter((c) => c.filename !== filename);
1464
+ if (data.crls.length === 0) {
1465
+ index.delete(key);
1466
+ }
1467
+ }
1468
+ });
1469
+ w.on("add", (filename) => {
1470
+ this.#onCrlFileAdded(index, filename);
1471
+ });
1472
+ w.on("change", (changedPath) => {
1473
+ debugLog("change in folder ", folder, changedPath);
1474
+ });
1475
+ this.#watchers.push(w);
1476
+ w.on("ready", () => {
1477
+ unreffAll();
1478
+ resolve();
1479
+ });
1480
+ });
1481
+ }
1482
+ async #walkAllFiles(folder, index, createUnreffedWatcher) {
1483
+ const { w, unreffAll } = createUnreffedWatcher(folder);
1484
+ w.on("unlink", (filename) => {
1485
+ debugLog(chalk3.cyan(`unlink in folder ${folder}`), filename);
1486
+ const h = this.#filenameToHash.get(filename);
1487
+ if (h && index.has(h)) {
1488
+ index.delete(h);
1489
+ }
1490
+ });
1491
+ w.on("add", (filename) => {
1492
+ debugLog(chalk3.cyan(`add in folder ${folder}`), filename);
1493
+ try {
1494
+ const certificate = readCertificate(filename);
1495
+ const info = exploreCertificate(certificate);
1496
+ const fingerprint2 = makeFingerprint(certificate);
1497
+ index.set(fingerprint2, { certificate, filename, info });
1498
+ this.#filenameToHash.set(filename, fingerprint2);
1499
+ debugLog(
1500
+ chalk3.magenta("CERT"),
1501
+ info.tbsCertificate.subjectFingerPrint,
1502
+ info.tbsCertificate.serialNumber,
1503
+ info.tbsCertificate.extensions?.authorityKeyIdentifier?.authorityCertIssuerFingerPrint
1504
+ );
1505
+ } catch (err) {
1506
+ debugLog(`Walk files in folder ${folder} with file ${filename}`);
1507
+ debugLog(err);
1508
+ }
1509
+ });
1510
+ w.on("change", (changedPath) => {
1511
+ debugLog(chalk3.cyan(`change in folder ${folder}`), changedPath);
1512
+ try {
1513
+ const certificate = readCertificate(changedPath);
1514
+ const newFingerprint = makeFingerprint(certificate);
1515
+ const oldHash = this.#filenameToHash.get(changedPath);
1516
+ if (oldHash && oldHash !== newFingerprint) {
1517
+ index.delete(oldHash);
1518
+ }
1519
+ index.set(newFingerprint, { certificate, filename: changedPath, info: exploreCertificate(certificate) });
1520
+ this.#filenameToHash.set(changedPath, newFingerprint);
1521
+ } catch (err) {
1522
+ debugLog(`change event: failed to re-read ${changedPath}`, err);
1523
+ }
1524
+ });
1525
+ this.#watchers.push(w);
1526
+ await new Promise((resolve, _reject) => {
1527
+ w.on("ready", () => {
1528
+ unreffAll();
1529
+ debugLog("ready");
1530
+ debugLog([...index.keys()].map((k) => k.substring(0, 10)));
1531
+ resolve();
1532
+ });
1533
+ });
1534
+ }
1535
+ // make sure that all crls have been processed.
1536
+ async #waitAndCheckCRLProcessingStatus() {
1537
+ return new Promise((resolve, reject) => {
1538
+ if (this.#pendingCrlToProcess === 0) {
1539
+ setImmediate(resolve);
1540
+ return;
1541
+ }
1542
+ if (this.#onCrlProcess) {
1543
+ return reject(new Error("Internal Error"));
1544
+ }
1545
+ this.#onCrlProcess = resolve;
1546
+ });
1547
+ }
1548
+ };
1549
+ }
1550
+ });
1551
+
1552
+ // packages/node-opcua-pki/lib/toolbox/index.ts
1553
+ var init_toolbox = __esm({
1554
+ "packages/node-opcua-pki/lib/toolbox/index.ts"() {
1555
+ "use strict";
1556
+ init_esm_shims();
1557
+ init_common();
1558
+ init_common2();
1559
+ init_config();
1560
+ init_debug();
1561
+ init_display();
1562
+ }
1563
+ });
1564
+
1565
+ // packages/node-opcua-pki/lib/toolbox/with_openssl/_env.ts
1566
+ function setEnv(varName, value) {
1567
+ if (!g_config.silent) {
1568
+ warningLog(` set ${varName}=${value}`);
1569
+ }
1570
+ exportedEnvVars[varName] = value;
1571
+ if (["OPENSSL_CONF"].indexOf(varName) >= 0) {
1572
+ process.env[varName] = value;
1573
+ }
1574
+ if (["RANDFILE"].indexOf(varName) >= 0) {
1575
+ process.env[varName] = value;
1576
+ }
1577
+ }
1578
+ function getEnv(varName) {
1579
+ return exportedEnvVars[varName];
1580
+ }
1581
+ function getEnvironmentVarNames() {
1582
+ return Object.keys(exportedEnvVars).map((varName) => {
1583
+ return { key: varName, pattern: `\\$ENV\\:\\:${varName}` };
1584
+ });
1585
+ }
1586
+ function processAltNames(params) {
1587
+ params.dns = params.dns || [];
1588
+ params.ip = params.ip || [];
1589
+ let subjectAltName = [];
1590
+ subjectAltName.push(`URI:${params.applicationUri}`);
1591
+ subjectAltName = [].concat(
1592
+ subjectAltName,
1593
+ params.dns.map((d) => `DNS:${d}`)
1594
+ );
1595
+ subjectAltName = [].concat(
1596
+ subjectAltName,
1597
+ params.ip.map((d) => `IP:${d}`)
1598
+ );
1599
+ const subjectAltNameString = subjectAltName.join(", ");
1600
+ setEnv("ALTNAME", subjectAltNameString);
1601
+ }
1602
+ var exportedEnvVars;
1603
+ var init_env = __esm({
1604
+ "packages/node-opcua-pki/lib/toolbox/with_openssl/_env.ts"() {
1605
+ "use strict";
1606
+ init_esm_shims();
1607
+ init_config();
1608
+ init_debug();
1609
+ exportedEnvVars = {};
1610
+ }
1611
+ });
1612
+
1613
+ // packages/node-opcua-pki/lib/misc/subject.ts
1614
+ import { Subject as Subject3 } from "node-opcua-crypto";
1615
+ var init_subject = __esm({
1616
+ "packages/node-opcua-pki/lib/misc/subject.ts"() {
1617
+ "use strict";
1618
+ init_esm_shims();
1619
+ }
1620
+ });
1621
+
1622
+ // packages/node-opcua-pki/lib/toolbox/with_openssl/install_prerequisite.ts
1623
+ import child_process from "child_process";
1624
+ import fs5 from "fs";
1625
+ import os2 from "os";
1626
+ import path3 from "path";
1627
+ import url from "url";
1628
+ import byline from "byline";
1629
+ import chalk4 from "chalk";
1630
+ import ProgressBar from "progress";
1631
+ import wget from "wget-improved-2";
1632
+ import yauzl from "yauzl";
1633
+ function makeOptions() {
1634
+ const proxy = process.env.HTTPS_PROXY || process.env.https_proxy || process.env.HTTP_PROXY || process.env.http_proxy || void 0;
1635
+ if (proxy) {
1636
+ const a = new url.URL(proxy);
1637
+ const auth = a.username ? `${a.username}:${a.password}` : void 0;
1638
+ const options = {
1639
+ proxy: {
1640
+ port: a.port ? parseInt(a.port, 10) : 80,
1641
+ protocol: a.protocol.replace(":", ""),
1642
+ host: a.hostname ?? "",
1643
+ proxyAuth: auth
1644
+ }
1645
+ };
1646
+ warningLog(chalk4.green("- using proxy "), proxy);
1647
+ warningLog(options);
1648
+ return options;
1649
+ }
1650
+ return {};
1651
+ }
1652
+ async function execute(cmd, cwd) {
1653
+ let output = "";
1654
+ const options = {
1655
+ cwd,
1656
+ windowsHide: true
1657
+ };
1658
+ return await new Promise((resolve, reject) => {
1659
+ const child = child_process.exec(
1660
+ cmd,
1661
+ options,
1662
+ (err) => {
1663
+ const exitCode = err === null ? 0 : err.code || 1;
1664
+ if (err) reject(err);
1665
+ else {
1666
+ resolve({ exitCode, output });
1667
+ }
1668
+ }
1669
+ );
1670
+ const stream1 = byline(child.stdout);
1671
+ stream1.on("data", (line) => {
1672
+ output += `${line}
1673
+ `;
1674
+ if (doDebug2) {
1675
+ process.stdout.write(` stdout ${chalk4.yellow(line)}
1676
+ `);
1677
+ }
1678
+ });
1679
+ });
1680
+ }
1681
+ function quote2(str) {
1682
+ return `"${str.replace(/\\/g, "/")}"`;
1683
+ }
1684
+ function is_expected_openssl_version(strVersion) {
1685
+ return !!strVersion.match(/OpenSSL 1|3/);
1686
+ }
1687
+ async function getopensslExecPath() {
1688
+ let result1;
1689
+ try {
1690
+ result1 = await execute("which openssl");
1691
+ } catch (err) {
1692
+ warningLog("warning: ", err.message);
1693
+ throw new Error("Cannot find openssl");
1694
+ }
1695
+ const exitCode = result1?.exitCode;
1696
+ const output = result1?.output;
1697
+ if (exitCode !== 0) {
1698
+ warningLog(chalk4.yellow(" it seems that ") + chalk4.cyan("openssl") + chalk4.yellow(" is not installed on your computer "));
1699
+ warningLog(chalk4.yellow("Please install it before running this programs"));
1700
+ throw new Error("Cannot find openssl");
1701
+ }
1702
+ const opensslExecPath = output.replace(/\n\r/g, "").trim();
1703
+ return opensslExecPath;
1704
+ }
1705
+ async function check_system_openssl_version() {
1706
+ const opensslExecPath = await getopensslExecPath();
1707
+ const q_opensslExecPath = quote2(opensslExecPath);
1708
+ if (doDebug2) {
1709
+ warningLog(` OpenSSL found in : ${chalk4.yellow(opensslExecPath)}`);
1710
+ }
1711
+ const result = await execute(`${q_opensslExecPath} version`);
1712
+ const exitCode = result?.exitCode;
1713
+ const output = result?.output;
1714
+ const version = output.trim();
1715
+ const versionOK = exitCode === 0 && is_expected_openssl_version(version);
1716
+ if (!versionOK) {
1717
+ let message = chalk4.whiteBright("Warning !!!!!!!!!!!! ") + "\nyour version of openssl is " + version + ". It doesn't match the expected version";
1718
+ if (process.platform === "darwin") {
1719
+ message += chalk4.cyan("\nplease refer to :") + chalk4.yellow(" https://github.com/node-opcua/node-opcua/wiki/installing-node-opcua-or-node-red-on-MacOS");
1720
+ }
1721
+ console.log(message);
1722
+ }
1723
+ return output;
1724
+ }
1725
+ async function install_and_check_win32_openssl_version() {
1726
+ const downloadFolder = path3.join(os2.tmpdir(), ".");
1727
+ function get_openssl_folder_win32() {
1728
+ if (process.env.LOCALAPPDATA) {
1729
+ const userProgramFolder = path3.join(process.env.LOCALAPPDATA, "Programs");
1730
+ if (fs5.existsSync(userProgramFolder)) {
1731
+ return path3.join(userProgramFolder, "openssl");
1732
+ }
1733
+ }
1734
+ return path3.join(process.cwd(), "openssl");
1735
+ }
1736
+ function get_openssl_exec_path_win32() {
1737
+ const opensslFolder2 = get_openssl_folder_win32();
1738
+ return path3.join(opensslFolder2, "openssl.exe");
1739
+ }
1740
+ async function check_openssl_win32() {
1741
+ const opensslExecPath2 = get_openssl_exec_path_win32();
1742
+ const exists = fs5.existsSync(opensslExecPath2);
1743
+ if (!exists) {
1744
+ warningLog("checking presence of ", opensslExecPath2);
1745
+ warningLog(chalk4.red(" cannot find file ") + opensslExecPath2);
1746
+ return {
1747
+ opensslOk: false,
1748
+ version: `cannot find file ${opensslExecPath2}`
1749
+ };
1750
+ } else {
1751
+ const q_openssl_exe_path = quote2(opensslExecPath2);
1752
+ const cwd = ".";
1753
+ const { exitCode, output } = await execute(`${q_openssl_exe_path} version`, cwd);
1754
+ const version = output.trim();
1755
+ if (doDebug2) {
1756
+ warningLog(" Version = ", version);
1757
+ }
1758
+ return {
1759
+ opensslOk: exitCode === 0 && is_expected_openssl_version(version),
1760
+ version
1761
+ };
1762
+ }
1763
+ }
1764
+ function win32or64() {
1765
+ if (process.env.PROCESSOR_ARCHITECTURE === "x86" && process.env.PROCESSOR_ARCHITEW6432) {
1766
+ return 64;
1767
+ }
1768
+ if (process.env.PROCESSOR_ARCHITECTURE === "AMD64") {
1769
+ return 64;
1770
+ }
1771
+ if (process.env.CURRENT_CPU === "x64") {
1772
+ return 64;
1773
+ }
1774
+ return 32;
1775
+ }
1776
+ async function download_openssl() {
1777
+ const url2 = win32or64() === 64 ? "https://github.com/node-opcua/node-opcua-pki/releases/download/2.14.2/openssl-1.0.2u-x64_86-win64.zip" : "https://github.com/node-opcua/node-opcua-pki/releases/download/2.14.2/openssl-1.0.2u-i386-win32.zip";
1778
+ const outputFilename = path3.join(downloadFolder, path3.basename(url2));
1779
+ warningLog(`downloading ${chalk4.yellow(url2)} to ${outputFilename}`);
1780
+ if (fs5.existsSync(outputFilename)) {
1781
+ return { downloadedFile: outputFilename };
1782
+ }
1783
+ const options = makeOptions();
1784
+ const bar = new ProgressBar(chalk4.cyan("[:bar]") + chalk4.cyan(" :percent ") + chalk4.white(":etas"), {
1785
+ complete: "=",
1786
+ incomplete: " ",
1787
+ total: 100,
1788
+ width: 100
1789
+ });
1790
+ return await new Promise((resolve, reject) => {
1791
+ const download = wget.download(url2, outputFilename, options);
1792
+ download.on("error", (err) => {
1793
+ warningLog(err);
1794
+ setImmediate(() => {
1795
+ reject(err);
1796
+ });
1797
+ });
1798
+ download.on("end", (output) => {
1799
+ if (doDebug2) {
1800
+ warningLog(output);
1801
+ }
1802
+ resolve({ downloadedFile: outputFilename });
1803
+ });
1804
+ download.on("progress", (progress) => {
1805
+ bar.update(progress);
1806
+ });
1807
+ });
1808
+ }
1809
+ async function unzip_openssl(zipFilename) {
1810
+ const opensslFolder2 = get_openssl_folder_win32();
1811
+ const zipFile = await new Promise((resolve, reject) => {
1812
+ yauzl.open(zipFilename, { lazyEntries: true }, (err, zipfile) => {
1813
+ if (err) {
1814
+ reject(err);
1815
+ } else {
1816
+ if (!zipfile) {
1817
+ reject(new Error("zipfile is null"));
1818
+ } else {
1819
+ resolve(zipfile);
1820
+ }
1821
+ }
1822
+ });
1823
+ });
1824
+ zipFile.readEntry();
1825
+ await new Promise((resolve, reject) => {
1826
+ zipFile.on("end", (err) => {
1827
+ setImmediate(() => {
1828
+ if (doDebug2) {
1829
+ warningLog("unzip done");
1830
+ }
1831
+ if (err) {
1832
+ reject(err);
1833
+ } else {
1834
+ resolve();
1835
+ }
1836
+ });
1837
+ });
1838
+ zipFile.on("entry", (entry) => {
1839
+ zipFile.openReadStream(entry, (err, readStream) => {
1840
+ if (err) {
1841
+ return reject(err);
1842
+ }
1843
+ const file = path3.join(opensslFolder2, entry.fileName);
1844
+ if (doDebug2) {
1845
+ warningLog(" unzipping :", file);
1846
+ }
1847
+ const writeStream = fs5.createWriteStream(file, "binary");
1848
+ readStream?.pipe(writeStream);
1849
+ writeStream.on("close", () => {
1850
+ zipFile.readEntry();
1851
+ });
1852
+ });
1853
+ });
1854
+ });
1855
+ }
1856
+ const opensslFolder = get_openssl_folder_win32();
1857
+ const opensslExecPath = get_openssl_exec_path_win32();
1858
+ if (!fs5.existsSync(opensslFolder)) {
1859
+ if (doDebug2) {
1860
+ warningLog("creating openssl_folder", opensslFolder);
1861
+ }
1862
+ fs5.mkdirSync(opensslFolder);
1863
+ }
1864
+ const { opensslOk, version: _version } = await check_openssl_win32();
1865
+ if (!opensslOk) {
1866
+ warningLog(chalk4.yellow("openssl seems to be missing and need to be installed"));
1867
+ const { downloadedFile } = await download_openssl();
1868
+ if (doDebug2) {
1869
+ warningLog("deflating ", chalk4.yellow(downloadedFile));
1870
+ }
1871
+ await unzip_openssl(downloadedFile);
1872
+ const opensslExists = !!fs5.existsSync(opensslExecPath);
1873
+ if (doDebug2) {
1874
+ warningLog("verifying ", opensslExists, opensslExists ? chalk4.green("OK ") : chalk4.red(" Error"), opensslExecPath);
1875
+ }
1876
+ const _opensslExecPath2 = await check_openssl_win32();
1877
+ return opensslExecPath;
1878
+ } else {
1879
+ if (doDebug2) {
1880
+ warningLog(chalk4.green("openssl is already installed and have the expected version."));
1881
+ }
1882
+ return opensslExecPath;
1883
+ }
1884
+ }
1885
+ async function install_prerequisite() {
1886
+ if (process.platform !== "win32") {
1887
+ return await check_system_openssl_version();
1888
+ } else {
1889
+ return await install_and_check_win32_openssl_version();
1890
+ }
1891
+ }
1892
+ async function get_openssl_exec_path() {
1893
+ if (process.platform === "win32") {
1894
+ const opensslExecPath = await install_prerequisite();
1895
+ if (!fs5.existsSync(opensslExecPath)) {
1896
+ throw new Error(`internal error cannot find ${opensslExecPath}`);
1897
+ }
1898
+ return opensslExecPath;
1899
+ } else {
1900
+ return "openssl";
1901
+ }
1902
+ }
1903
+ var doDebug2;
1904
+ var init_install_prerequisite = __esm({
1905
+ "packages/node-opcua-pki/lib/toolbox/with_openssl/install_prerequisite.ts"() {
1906
+ "use strict";
1907
+ init_esm_shims();
1908
+ init_debug();
1909
+ doDebug2 = process.env.NODEOPCUAPKIDEBUG || false;
1910
+ }
1911
+ });
1912
+
1913
+ // packages/node-opcua-pki/lib/toolbox/with_openssl/execute_openssl.ts
1914
+ import assert6 from "assert";
1915
+ import child_process2 from "child_process";
1916
+ import fs6 from "fs";
1917
+ import os3 from "os";
1918
+ import byline2 from "byline";
1919
+ import chalk5 from "chalk";
1920
+ async function execute2(cmd, options) {
1921
+ const from = new Error();
1922
+ options.cwd = options.cwd || process.cwd();
1923
+ if (!g_config.silent) {
1924
+ warningLog(chalk5.cyan(" CWD "), options.cwd);
1925
+ }
1926
+ const outputs = [];
1927
+ return await new Promise((resolve, reject) => {
1928
+ const child = child_process2.exec(
1929
+ cmd,
1930
+ {
1931
+ cwd: options.cwd,
1932
+ windowsHide: true
1933
+ },
1934
+ (err) => {
1935
+ if (err) {
1936
+ if (!options.hideErrorMessage) {
1937
+ const fence = "###########################################";
1938
+ console.error(chalk5.bgWhiteBright.redBright(`${fence} OPENSSL ERROR ${fence}`));
1939
+ console.error(chalk5.bgWhiteBright.redBright(`CWD = ${options.cwd}`));
1940
+ console.error(chalk5.bgWhiteBright.redBright(err.message));
1941
+ console.error(chalk5.bgWhiteBright.redBright(`${fence} OPENSSL ERROR ${fence}`));
1942
+ console.error(from.stack);
1943
+ }
1944
+ reject(new Error(err.message));
1945
+ return;
1946
+ }
1947
+ resolve(outputs.join(""));
1948
+ }
1949
+ );
1950
+ if (child.stdout) {
1951
+ const stream2 = byline2(child.stdout);
1952
+ stream2.on("data", (line) => {
1953
+ outputs.push(`${line}
1954
+ `);
1955
+ });
1956
+ if (!g_config.silent) {
1957
+ stream2.on("data", (line) => {
1958
+ line = line.toString();
1959
+ if (doDebug) {
1960
+ process.stdout.write(`${chalk5.white(" stdout ") + chalk5.whiteBright(line)}
1961
+ `);
1962
+ }
1963
+ });
1964
+ }
1965
+ }
1966
+ if (!g_config.silent) {
1967
+ if (child.stderr) {
1968
+ const stream1 = byline2(child.stderr);
1969
+ stream1.on("data", (line) => {
1970
+ line = line.toString();
1971
+ if (displayError) {
1972
+ process.stdout.write(`${chalk5.white(" stderr ") + chalk5.red(line)}
1973
+ `);
1974
+ }
1975
+ });
1976
+ }
1977
+ }
1978
+ });
1979
+ }
1980
+ async function find_openssl() {
1981
+ return await get_openssl_exec_path();
1982
+ }
1983
+ async function ensure_openssl_installed() {
1984
+ if (!opensslPath) {
1985
+ opensslPath = await find_openssl();
1986
+ const outputs = await execute_openssl("version", { cwd: "." });
1987
+ g_config.opensslVersion = outputs.trim();
1988
+ if (doDebug) {
1989
+ warningLog("OpenSSL version : ", g_config.opensslVersion);
1990
+ }
1991
+ }
1992
+ }
1993
+ async function execute_openssl_no_failure(cmd, options) {
1994
+ options = options || {};
1995
+ options.hideErrorMessage = true;
1996
+ try {
1997
+ return await execute_openssl(cmd, options);
1998
+ } catch (err) {
1999
+ debugLog(" (ignored error = ERROR : )", err.message);
2000
+ }
2001
+ }
2002
+ function getTempFolder() {
2003
+ return os3.tmpdir();
2004
+ }
2005
+ async function execute_openssl(cmd, options) {
2006
+ debugLog("execute_openssl", cmd, options);
2007
+ const empty_config_file = n(getTempFolder(), "empty_config.cnf");
2008
+ if (!fs6.existsSync(empty_config_file)) {
2009
+ await fs6.promises.writeFile(empty_config_file, "# empty config file");
2010
+ }
2011
+ options = options || {};
2012
+ options.openssl_conf = options.openssl_conf || empty_config_file;
2013
+ assert6(options.openssl_conf);
2014
+ setEnv("OPENSSL_CONF", options.openssl_conf);
2015
+ if (!g_config.silent) {
2016
+ warningLog(chalk5.cyan(" OPENSSL_CONF"), process.env.OPENSSL_CONF);
2017
+ warningLog(chalk5.cyan(" RANDFILE "), process.env.RANDFILE);
2018
+ warningLog(chalk5.cyan(" CMD openssl "), chalk5.cyanBright(cmd));
2019
+ }
2020
+ await ensure_openssl_installed();
2021
+ return await execute2(`${quote(opensslPath)} ${cmd}`, options);
2022
+ }
2023
+ var opensslPath, n;
2024
+ var init_execute_openssl = __esm({
2025
+ "packages/node-opcua-pki/lib/toolbox/with_openssl/execute_openssl.ts"() {
2026
+ "use strict";
2027
+ init_esm_shims();
2028
+ init_common();
2029
+ init_common2();
2030
+ init_config();
2031
+ init_debug();
2032
+ init_env();
2033
+ init_install_prerequisite();
2034
+ n = makePath;
2035
+ }
2036
+ });
2037
+
2038
+ // packages/node-opcua-pki/lib/toolbox/with_openssl/toolbox.ts
2039
+ import assert7 from "assert";
2040
+ import fs7 from "fs";
2041
+ import path4 from "path";
2042
+ function openssl_require2DigitYearInDate() {
2043
+ if (!g_config.opensslVersion) {
2044
+ throw new Error(
2045
+ "openssl_require2DigitYearInDate : openssl version is not known: please call ensure_openssl_installed()"
2046
+ );
2047
+ }
2048
+ return g_config.opensslVersion.match(/OpenSSL 0\.9/);
2049
+ }
2050
+ function generateStaticConfig(configPath, options) {
2051
+ const prePath = options?.cwd || "";
2052
+ const originalFilename = !path4.isAbsolute(configPath) ? path4.join(prePath, configPath) : configPath;
2053
+ let staticConfig = fs7.readFileSync(originalFilename, { encoding: "utf8" });
2054
+ for (const envVar of getEnvironmentVarNames()) {
2055
+ staticConfig = staticConfig.replace(new RegExp(envVar.pattern, "gi"), getEnv(envVar.key));
2056
+ }
2057
+ const staticConfigPath = `${configPath}.${process.pid}-${_counter++}.tmp`;
2058
+ const temporaryConfigPath = !path4.isAbsolute(configPath) ? path4.join(prePath, staticConfigPath) : staticConfigPath;
2059
+ fs7.writeFileSync(temporaryConfigPath, staticConfig);
2060
+ if (options?.cwd) {
2061
+ return path4.relative(options.cwd, temporaryConfigPath);
2062
+ } else {
2063
+ return temporaryConfigPath;
2064
+ }
2065
+ }
2066
+ async function getPublicKeyFromPrivateKey(privateKeyFilename, publicKeyFilename) {
2067
+ assert7(fs7.existsSync(privateKeyFilename));
2068
+ await execute_openssl(`rsa -pubout -in ${q(n2(privateKeyFilename))} -out ${q(n2(publicKeyFilename))}`, {});
2069
+ }
2070
+ function x509Date(date) {
2071
+ date = date || /* @__PURE__ */ new Date();
2072
+ const Y = date.getUTCFullYear();
2073
+ const M = date.getUTCMonth() + 1;
2074
+ const D = date.getUTCDate();
2075
+ const h = date.getUTCHours();
2076
+ const m = date.getUTCMinutes();
2077
+ const s = date.getUTCSeconds();
2078
+ function w(s2, l) {
2079
+ return `${s2}`.padStart(l, "0");
2080
+ }
2081
+ if (openssl_require2DigitYearInDate()) {
2082
+ return `${w(Y, 2) + w(M, 2) + w(D, 2) + w(h, 2) + w(m, 2) + w(s, 2)}Z`;
2083
+ } else {
2084
+ return `${w(Y, 4) + w(M, 2) + w(D, 2) + w(h, 2) + w(m, 2) + w(s, 2)}Z`;
2085
+ }
2086
+ }
2087
+ async function dumpCertificate(certificate) {
2088
+ assert7(fs7.existsSync(certificate));
2089
+ return await execute_openssl(`x509 -in ${q(n2(certificate))} -text -noout`, {});
2090
+ }
2091
+ async function toDer(certificatePem) {
2092
+ assert7(fs7.existsSync(certificatePem));
2093
+ const certificateDer = certificatePem.replace(".pem", ".der");
2094
+ return await execute_openssl(`x509 -outform der -in ${certificatePem} -out ${certificateDer}`, {});
2095
+ }
2096
+ async function fingerprint(certificatePem) {
2097
+ assert7(fs7.existsSync(certificatePem));
2098
+ return await execute_openssl(`x509 -fingerprint -noout -in ${certificatePem}`, {});
2099
+ }
2100
+ var _counter, q, n2;
2101
+ var init_toolbox2 = __esm({
2102
+ "packages/node-opcua-pki/lib/toolbox/with_openssl/toolbox.ts"() {
2103
+ "use strict";
2104
+ init_esm_shims();
2105
+ init_common();
2106
+ init_common2();
2107
+ init_config();
2108
+ init_env();
2109
+ init_execute_openssl();
2110
+ g_config.opensslVersion = "";
2111
+ _counter = 0;
2112
+ q = quote;
2113
+ n2 = makePath;
2114
+ }
2115
+ });
2116
+
2117
+ // packages/node-opcua-pki/lib/toolbox/with_openssl/create_certificate_signing_request.ts
2118
+ import assert8 from "assert";
2119
+ import fs8 from "fs";
2120
+ import path5 from "path";
2121
+ async function createCertificateSigningRequestWithOpenSSL(certificateSigningRequestFilename, params) {
2122
+ assert8(params);
2123
+ assert8(params.rootDir);
2124
+ assert8(params.configFile);
2125
+ assert8(params.privateKey);
2126
+ assert8(typeof params.privateKey === "string");
2127
+ assert8(fs8.existsSync(params.configFile), `config file must exist ${params.configFile}`);
2128
+ assert8(fs8.existsSync(params.privateKey), `Private key must exist${params.privateKey}`);
2129
+ assert8(fs8.existsSync(params.rootDir), "RootDir key must exist");
2130
+ assert8(typeof certificateSigningRequestFilename === "string");
2131
+ processAltNames(params);
2132
+ const configFile = generateStaticConfig(params.configFile, { cwd: params.rootDir });
2133
+ const options = { cwd: params.rootDir, openssl_conf: path5.relative(params.rootDir, configFile) };
2134
+ const configOption = ` -config ${q2(n3(configFile))}`;
2135
+ const subject = params.subject ? new Subject3(params.subject).toString() : void 0;
2136
+ const subjectOptions = subject ? ` -subj "${subject}"` : "";
2137
+ displaySubtitle("- Creating a Certificate Signing Request with openssl");
2138
+ await execute_openssl(
2139
+ "req -new -sha256 -batch -text " + configOption + " -key " + q2(n3(params.privateKey)) + subjectOptions + " -out " + q2(n3(certificateSigningRequestFilename)),
2140
+ options
2141
+ );
2142
+ }
2143
+ var q2, n3;
2144
+ var init_create_certificate_signing_request2 = __esm({
2145
+ "packages/node-opcua-pki/lib/toolbox/with_openssl/create_certificate_signing_request.ts"() {
2146
+ "use strict";
2147
+ init_esm_shims();
2148
+ init_subject();
2149
+ init_common();
2150
+ init_common2();
2151
+ init_display();
2152
+ init_env();
2153
+ init_execute_openssl();
2154
+ init_toolbox2();
2155
+ q2 = quote;
2156
+ n3 = makePath;
2157
+ }
2158
+ });
2159
+
2160
+ // packages/node-opcua-pki/lib/toolbox/with_openssl/index.ts
2161
+ import exp from "constants";
2162
+ var init_with_openssl = __esm({
2163
+ "packages/node-opcua-pki/lib/toolbox/with_openssl/index.ts"() {
2164
+ "use strict";
2165
+ init_esm_shims();
2166
+ init_env();
2167
+ init_create_certificate_signing_request2();
2168
+ init_execute_openssl();
2169
+ init_install_prerequisite();
2170
+ init_toolbox2();
2171
+ }
2172
+ });
2173
+
2174
+ // packages/node-opcua-pki/lib/ca/templates/ca_config_template.cnf.ts
2175
+ var config2, ca_config_template_cnf_default;
2176
+ var init_ca_config_template_cnf = __esm({
2177
+ "packages/node-opcua-pki/lib/ca/templates/ca_config_template.cnf.ts"() {
2178
+ "use strict";
2179
+ init_esm_shims();
2180
+ config2 = `#.........DO NOT MODIFY BY HAND .........................
2181
+ [ ca ]
2182
+ default_ca = CA_default
2183
+ [ CA_default ]
2184
+ dir = %%ROOT_FOLDER%% # the main CA folder
2185
+ certs = $dir/certs # where to store certificates
2186
+ new_certs_dir = $dir/certs #
2187
+ database = $dir/index.txt # the certificate database
2188
+ serial = $dir/serial # the serial number counter
2189
+ certificate = $dir/public/cacert.pem # The root CA certificate
2190
+ private_key = $dir/private/cakey.pem # the CA private key
2191
+ x509_extensions = usr_cert #
2192
+ default_days = 3650 # default validity : 10 years
2193
+
2194
+ # default_md = sha1
2195
+
2196
+ default_md = sha256 # The default digest algorithm
2197
+
2198
+ preserve = no
2199
+ policy = policy_match
2200
+ # randfile = $dir/random.rnd
2201
+ # default_startdate = YYMMDDHHMMSSZ
2202
+ # default_enddate = YYMMDDHHMMSSZ
2203
+ crl_dir = $dir/crl
2204
+ crl_extensions = crl_ext
2205
+ crl = $dir/revocation_list.crl # the Revocation list
2206
+ crlnumber = $dir/crlnumber # CRL number file
2207
+ default_crl_days = 30
2208
+ default_crl_hours = 24
2209
+ #msie_hack
2210
+
2211
+ [ policy_match ]
2212
+ countryName = optional
2213
+ stateOrProvinceName = optional
2214
+ localityName = optional
2215
+ organizationName = optional
2216
+ organizationalUnitName = optional
2217
+ commonName = optional
2218
+ emailAddress = optional
2219
+
2220
+ [ req ]
2221
+ default_bits = 4096 # Size of keys
2222
+ default_keyfile = key.pem # name of generated keys
2223
+ distinguished_name = req_distinguished_name
2224
+ attributes = req_attributes
2225
+ x509_extensions = v3_ca
2226
+ #input_password
2227
+ #output_password
2228
+ string_mask = nombstr # permitted characters
2229
+ req_extensions = v3_req
2230
+
2231
+ [ req_distinguished_name ]
2232
+
2233
+ #0 countryName = Country Name (2 letter code)
2234
+ # countryName_default = FR
2235
+ # countryName_min = 2
2236
+ # countryName_max = 2
2237
+ # stateOrProvinceName = State or Province Name (full name)
2238
+ # stateOrProvinceName_default = Ile de France
2239
+ # localityName = Locality Name (city, district)
2240
+ # localityName_default = Paris
2241
+ organizationName = Organization Name (company)
2242
+ organizationName_default = NodeOPCUA
2243
+ # organizationalUnitName = Organizational Unit Name (department, division)
2244
+ # organizationalUnitName_default = R&D
2245
+ commonName = Common Name (hostname, FQDN, IP, or your name)
2246
+ commonName_max = 256
2247
+ commonName_default = NodeOPCUA
2248
+ # emailAddress = Email Address
2249
+ # emailAddress_max = 40
2250
+ # emailAddress_default = node-opcua (at) node-opcua (dot) com
2251
+
2252
+ [ req_attributes ]
2253
+ #challengePassword = A challenge password
2254
+ #challengePassword_min = 4
2255
+ #challengePassword_max = 20
2256
+ #unstructuredName = An optional company name
2257
+ [ usr_cert ]
2258
+ basicConstraints = critical, CA:FALSE
2259
+ subjectKeyIdentifier = hash
2260
+ authorityKeyIdentifier = keyid,issuer:always
2261
+ #authorityKeyIdentifier = keyid
2262
+ subjectAltName = $ENV::ALTNAME
2263
+ # issuerAltName = issuer:copy
2264
+ nsComment = ''OpenSSL Generated Certificate''
2265
+ #nsCertType = client, email, objsign for ''everything including object signing''
2266
+ #nsCaRevocationUrl = http://www.domain.dom/ca-crl.pem
2267
+ #nsBaseUrl =
2268
+ #nsRenewalUrl =
2269
+ #nsCaPolicyUrl =
2270
+ #nsSslServerName =
2271
+ keyUsage = critical, digitalSignature, nonRepudiation, keyEncipherment, dataEncipherment, keyAgreement, keyCertSign
2272
+ extendedKeyUsage = critical,serverAuth ,clientAuth
2273
+
2274
+ [ v3_req ]
2275
+ basicConstraints = critical, CA:FALSE
2276
+ keyUsage = nonRepudiation, digitalSignature, keyEncipherment, dataEncipherment, keyAgreement
2277
+ extendedKeyUsage = critical,serverAuth ,clientAuth
2278
+ subjectAltName = $ENV::ALTNAME
2279
+ nsComment = "CA Generated by Node-OPCUA Certificate utility using openssl"
2280
+ [ v3_ca ]
2281
+ subjectKeyIdentifier = hash
2282
+ authorityKeyIdentifier = keyid:always,issuer:always
2283
+ # authorityKeyIdentifier = keyid
2284
+ basicConstraints = CA:TRUE
2285
+ keyUsage = critical, cRLSign, keyCertSign
2286
+ nsComment = "CA Certificate generated by Node-OPCUA Certificate utility using openssl"
2287
+ #nsCertType = sslCA, emailCA
2288
+ #subjectAltName = email:copy
2289
+ #issuerAltName = issuer:copy
2290
+ #obj = DER:02:03
2291
+ crlDistributionPoints = @crl_info
2292
+ [ crl_info ]
2293
+ URI.0 = http://localhost:8900/crl.pem
2294
+ [ v3_selfsigned]
2295
+ basicConstraints = critical, CA:FALSE
2296
+ keyUsage = nonRepudiation, digitalSignature, keyEncipherment, dataEncipherment, keyAgreement
2297
+ extendedKeyUsage = critical,serverAuth ,clientAuth
2298
+ nsComment = "Self-signed certificate, generated by NodeOPCUA"
2299
+ subjectAltName = $ENV::ALTNAME
2300
+
2301
+ [ crl_ext ]
2302
+ #issuerAltName = issuer:copy
2303
+ authorityKeyIdentifier = keyid:always,issuer:always
2304
+ #authorityInfoAccess = @issuer_info`;
2305
+ ca_config_template_cnf_default = config2;
2306
+ }
2307
+ });
2308
+
2309
+ // packages/node-opcua-pki/lib/ca/certificate_authority.ts
2310
+ import assert9 from "assert";
2311
+ import fs9 from "fs";
2312
+ import path6 from "path";
2313
+ import chalk6 from "chalk";
2314
+ import {
2315
+ exploreCertificateSigningRequest,
2316
+ generatePrivateKeyFile as generatePrivateKeyFile2,
2317
+ readCertificateSigningRequest,
2318
+ Subject as Subject4
2319
+ } from "node-opcua-crypto";
2320
+ function octetStringToIpAddress(a) {
2321
+ return parseInt(a.substring(0, 2), 16).toString() + "." + parseInt(a.substring(2, 4), 16).toString() + "." + parseInt(a.substring(4, 6), 16).toString() + "." + parseInt(a.substring(6, 8), 16).toString();
2322
+ }
2323
+ async function construct_CertificateAuthority(certificateAuthority) {
2324
+ const subject = certificateAuthority.subject;
2325
+ const caRootDir = path6.resolve(certificateAuthority.rootDir);
2326
+ async function make_folders() {
2327
+ mkdirRecursiveSync(caRootDir);
2328
+ mkdirRecursiveSync(path6.join(caRootDir, "private"));
2329
+ mkdirRecursiveSync(path6.join(caRootDir, "public"));
2330
+ mkdirRecursiveSync(path6.join(caRootDir, "certs"));
2331
+ mkdirRecursiveSync(path6.join(caRootDir, "crl"));
2332
+ mkdirRecursiveSync(path6.join(caRootDir, "conf"));
2333
+ }
2334
+ await make_folders();
2335
+ async function construct_default_files() {
2336
+ const serial = path6.join(caRootDir, "serial");
2337
+ if (!fs9.existsSync(serial)) {
2338
+ await fs9.promises.writeFile(serial, "1000");
2339
+ }
2340
+ const crlNumber = path6.join(caRootDir, "crlnumber");
2341
+ if (!fs9.existsSync(crlNumber)) {
2342
+ await fs9.promises.writeFile(crlNumber, "1000");
2343
+ }
2344
+ const indexFile = path6.join(caRootDir, "index.txt");
2345
+ if (!fs9.existsSync(indexFile)) {
2346
+ await fs9.promises.writeFile(indexFile, "");
2347
+ }
2348
+ }
2349
+ await construct_default_files();
2350
+ if (fs9.existsSync(path6.join(caRootDir, "private/cakey.pem")) && !config3.forceCA) {
2351
+ debugLog("CA private key already exists ... skipping");
2352
+ return;
2353
+ }
2354
+ displayTitle("Create Certificate Authority (CA)");
2355
+ const indexFileAttr = path6.join(caRootDir, "index.txt.attr");
2356
+ if (!fs9.existsSync(indexFileAttr)) {
2357
+ await fs9.promises.writeFile(indexFileAttr, "unique_subject = no");
2358
+ }
2359
+ const caConfigFile = certificateAuthority.configFile;
2360
+ if (1) {
2361
+ let data = configurationFileTemplate;
2362
+ data = makePath(data.replace(/%%ROOT_FOLDER%%/, caRootDir));
2363
+ await fs9.promises.writeFile(caConfigFile, data);
2364
+ }
2365
+ const subjectOpt = ` -subj "${subject.toString()}" `;
2366
+ processAltNames({});
2367
+ const options = { cwd: caRootDir };
2368
+ const configFile = generateStaticConfig("conf/caconfig.cnf", options);
2369
+ const configOption = ` -config ${q3(n4(configFile))}`;
2370
+ const keySize = certificateAuthority.keySize;
2371
+ const privateKeyFilename = path6.join(caRootDir, "private/cakey.pem");
2372
+ const csrFilename = path6.join(caRootDir, "private/cakey.csr");
2373
+ displayTitle(`Generate the CA private Key - ${keySize}`);
2374
+ await generatePrivateKeyFile2(privateKeyFilename, keySize);
2375
+ displayTitle("Generate a certificate request for the CA key");
2376
+ await execute_openssl(
2377
+ "req -new -sha256 -text -extensions v3_ca" + configOption + " -key " + q3(n4(privateKeyFilename)) + " -out " + q3(n4(csrFilename)) + " " + subjectOpt,
2378
+ options
2379
+ );
2380
+ displayTitle("Generate CA Certificate (self-signed)");
2381
+ await execute_openssl(
2382
+ " x509 -sha256 -req -days 3650 -text -extensions v3_ca -extfile " + q3(n4(configFile)) + " -in private/cakey.csr -signkey " + q3(n4(privateKeyFilename)) + " -out public/cacert.pem",
2383
+ options
2384
+ );
2385
+ displaySubtitle("generate initial CRL (Certificate Revocation List)");
2386
+ await regenerateCrl(certificateAuthority.revocationList, configOption, options);
2387
+ displayTitle("Create Certificate Authority (CA) ---> DONE");
2388
+ }
2389
+ async function regenerateCrl(revocationList, configOption, options) {
2390
+ displaySubtitle("regenerate CRL (Certificate Revocation List)");
2391
+ await execute_openssl(`ca -gencrl ${configOption} -out crl/revocation_list.crl`, options);
2392
+ await execute_openssl("crl -in crl/revocation_list.crl -out crl/revocation_list.der -outform der", options);
2393
+ displaySubtitle("Display (Certificate Revocation List)");
2394
+ await execute_openssl(`crl -in ${q3(n4(revocationList))} -text -noout`, options);
2395
+ }
2396
+ var defaultSubject, configurationFileTemplate, config3, n4, q3, CertificateAuthority;
2397
+ var init_certificate_authority = __esm({
2398
+ "packages/node-opcua-pki/lib/ca/certificate_authority.ts"() {
2399
+ "use strict";
2400
+ init_esm_shims();
2401
+ init_toolbox();
2402
+ init_with_openssl();
2403
+ init_ca_config_template_cnf();
2404
+ defaultSubject = "/C=FR/ST=IDF/L=Paris/O=Local NODE-OPCUA Certificate Authority/CN=NodeOPCUA-CA";
2405
+ configurationFileTemplate = ca_config_template_cnf_default;
2406
+ config3 = {
2407
+ certificateDir: "INVALID",
2408
+ forceCA: false,
2409
+ pkiDir: "INVALID"
2410
+ };
2411
+ n4 = makePath;
2412
+ q3 = quote;
2413
+ assert9(octetStringToIpAddress("c07b9179") === "192.123.145.121");
2414
+ CertificateAuthority = class {
2415
+ /** RSA key size used when generating the CA private key. */
2416
+ keySize;
2417
+ /** Root filesystem path of the CA directory structure. */
2418
+ location;
2419
+ /** X.500 subject of the CA certificate. */
2420
+ subject;
2421
+ constructor(options) {
2422
+ assert9(Object.prototype.hasOwnProperty.call(options, "location"));
2423
+ assert9(Object.prototype.hasOwnProperty.call(options, "keySize"));
2424
+ this.location = options.location;
2425
+ this.keySize = options.keySize || 2048;
2426
+ this.subject = new Subject4(options.subject || defaultSubject);
2427
+ }
2428
+ /** Absolute path to the CA root directory (alias for {@link location}). */
2429
+ get rootDir() {
2430
+ return this.location;
2431
+ }
2432
+ /** Path to the OpenSSL configuration file (`conf/caconfig.cnf`). */
2433
+ get configFile() {
2434
+ return path6.normalize(path6.join(this.rootDir, "./conf/caconfig.cnf"));
2435
+ }
2436
+ /** Path to the CA certificate in PEM format (`public/cacert.pem`). */
2437
+ get caCertificate() {
2438
+ return makePath(this.rootDir, "./public/cacert.pem");
2439
+ }
2440
+ /**
2441
+ * Path to the current Certificate Revocation List in DER format.
2442
+ * (`crl/revocation_list.der`)
2443
+ */
2444
+ get revocationListDER() {
2445
+ return makePath(this.rootDir, "./crl/revocation_list.der");
2446
+ }
2447
+ /**
2448
+ * Path to the current Certificate Revocation List in PEM format.
2449
+ * (`crl/revocation_list.crl`)
2450
+ */
2451
+ get revocationList() {
2452
+ return makePath(this.rootDir, "./crl/revocation_list.crl");
2453
+ }
2454
+ /**
2455
+ * Path to the concatenated CA certificate + CRL file.
2456
+ * Used by OpenSSL for CRL-based verification.
2457
+ */
2458
+ get caCertificateWithCrl() {
2459
+ return makePath(this.rootDir, "./public/cacertificate_with_crl.pem");
2460
+ }
2461
+ /**
2462
+ * Initialize the CA directory structure, generate the CA
2463
+ * private key and self-signed certificate if they do not
2464
+ * already exist.
2465
+ */
2466
+ async initialize() {
2467
+ await construct_CertificateAuthority(this);
2468
+ }
2469
+ /**
2470
+ * Rebuild the combined CA certificate + CRL file.
2471
+ *
2472
+ * This concatenates the CA certificate with the current
2473
+ * revocation list so that OpenSSL can verify certificates
2474
+ * with CRL checking enabled.
2475
+ */
2476
+ async constructCACertificateWithCRL() {
2477
+ const cacertWithCRL = this.caCertificateWithCrl;
2478
+ if (fs9.existsSync(this.revocationList)) {
2479
+ await fs9.promises.writeFile(
2480
+ cacertWithCRL,
2481
+ fs9.readFileSync(this.caCertificate, "utf8") + fs9.readFileSync(this.revocationList, "utf8")
2482
+ );
2483
+ } else {
2484
+ await fs9.promises.writeFile(cacertWithCRL, fs9.readFileSync(this.caCertificate));
2485
+ }
2486
+ }
2487
+ /**
2488
+ * Append the CA certificate to a signed certificate file,
2489
+ * creating a PEM certificate chain.
2490
+ *
2491
+ * @param certificate - path to the certificate file to extend
2492
+ */
2493
+ async constructCertificateChain(certificate) {
2494
+ assert9(fs9.existsSync(certificate));
2495
+ assert9(fs9.existsSync(this.caCertificate));
2496
+ debugLog(chalk6.yellow(" certificate file :"), chalk6.cyan(certificate));
2497
+ await fs9.promises.writeFile(
2498
+ certificate,
2499
+ await fs9.promises.readFile(certificate, "utf8") + await fs9.promises.readFile(this.caCertificate, "utf8")
2500
+ // + fs.readFileSync(this.revocationList)
2501
+ );
2502
+ }
2503
+ /**
2504
+ * Create a self-signed certificate using OpenSSL.
2505
+ *
2506
+ * @param certificateFile - output path for the signed certificate
2507
+ * @param privateKey - path to the private key file
2508
+ * @param params - certificate parameters (subject, validity, SANs)
2509
+ */
2510
+ async createSelfSignedCertificate(certificateFile, privateKey, params) {
2511
+ assert9(typeof privateKey === "string");
2512
+ assert9(fs9.existsSync(privateKey));
2513
+ if (!certificateFileExist(certificateFile)) {
2514
+ return;
2515
+ }
2516
+ adjustDate(params);
2517
+ adjustApplicationUri(params);
2518
+ processAltNames(params);
2519
+ const csrFile = `${certificateFile}_csr`;
2520
+ assert9(csrFile);
2521
+ const configFile = generateStaticConfig(this.configFile, { cwd: this.rootDir });
2522
+ const options = {
2523
+ cwd: this.rootDir,
2524
+ openssl_conf: makePath(configFile)
2525
+ };
2526
+ const configOption = "";
2527
+ const subject = params.subject ? new Subject4(params.subject).toString() : "";
2528
+ const subjectOptions = subject && subject.length > 1 ? ` -subj ${subject} ` : "";
2529
+ displaySubtitle("- the certificate signing request");
2530
+ await execute_openssl(
2531
+ "req -new -sha256 -text " + configOption + subjectOptions + " -batch -key " + q3(n4(privateKey)) + " -out " + q3(n4(csrFile)),
2532
+ options
2533
+ );
2534
+ displaySubtitle("- creating the self-signed certificate");
2535
+ await execute_openssl(
2536
+ "ca -selfsign -keyfile " + q3(n4(privateKey)) + " -startdate " + x509Date(params.startDate) + " -enddate " + x509Date(params.endDate) + " -batch -out " + q3(n4(certificateFile)) + " -in " + q3(n4(csrFile)),
2537
+ options
2538
+ );
2539
+ displaySubtitle("- dump the certificate for a check");
2540
+ await execute_openssl(`x509 -in ${q3(n4(certificateFile))} -dates -fingerprint -purpose -noout`, {});
2541
+ displaySubtitle("- verify self-signed certificate");
2542
+ await execute_openssl_no_failure(`verify -verbose -CAfile ${q3(n4(certificateFile))} ${q3(n4(certificateFile))}`, options);
2543
+ await fs9.promises.unlink(csrFile);
2544
+ }
2545
+ /**
2546
+ * Revoke a certificate and regenerate the CRL.
2547
+ *
2548
+ * @param certificate - path to the certificate file to revoke
2549
+ * @param params - revocation parameters
2550
+ * @param params.reason - CRL reason code
2551
+ * (default `"keyCompromise"`)
2552
+ */
2553
+ async revokeCertificate(certificate, params) {
2554
+ const crlReasons = [
2555
+ "unspecified",
2556
+ "keyCompromise",
2557
+ "CACompromise",
2558
+ "affiliationChanged",
2559
+ "superseded",
2560
+ "cessationOfOperation",
2561
+ "certificateHold",
2562
+ "removeFromCRL"
2563
+ ];
2564
+ const configFile = generateStaticConfig("conf/caconfig.cnf", { cwd: this.rootDir });
2565
+ const options = {
2566
+ cwd: this.rootDir,
2567
+ openssl_conf: makePath(configFile)
2568
+ };
2569
+ setEnv("ALTNAME", "");
2570
+ const randomFile = path6.join(this.rootDir, "random.rnd");
2571
+ setEnv("RANDFILE", randomFile);
2572
+ const configOption = ` -config ${q3(n4(configFile))}`;
2573
+ const reason = params.reason || "keyCompromise";
2574
+ assert9(crlReasons.indexOf(reason) >= 0);
2575
+ displayTitle(`Revoking certificate ${certificate}`);
2576
+ displaySubtitle("Revoke certificate");
2577
+ await execute_openssl_no_failure(`ca -verbose ${configOption} -revoke ${q3(certificate)} -crl_reason ${reason}`, options);
2578
+ await regenerateCrl(this.revocationList, configOption, options);
2579
+ displaySubtitle("Verify that certificate is revoked");
2580
+ await execute_openssl_no_failure(
2581
+ "verify -verbose -CRLfile " + q3(n4(this.revocationList)) + " -CAfile " + q3(n4(this.caCertificate)) + " -crl_check " + q3(n4(certificate)),
2582
+ options
2583
+ );
2584
+ displaySubtitle("Produce CRL in DER form ");
2585
+ await execute_openssl(`crl -in ${q3(n4(this.revocationList))} -out crl/revocation_list.der -outform der`, options);
2586
+ displaySubtitle("Produce CRL in PEM form ");
2587
+ await execute_openssl(`crl -in ${q3(n4(this.revocationList))} -out crl/revocation_list.pem -outform pem -text `, options);
2588
+ }
2589
+ /**
2590
+ * Sign a Certificate Signing Request (CSR) with this CA.
2591
+ *
2592
+ * The signed certificate is written to `certificate`, and the
2593
+ * CA certificate chain plus CRL are appended to form a
2594
+ * complete certificate chain.
2595
+ *
2596
+ * @param certificate - output path for the signed certificate
2597
+ * @param certificateSigningRequestFilename - path to the CSR
2598
+ * @param params1 - signing parameters (validity, dates, SANs)
2599
+ * @returns the path to the signed certificate
2600
+ */
2601
+ async signCertificateRequest(certificate, certificateSigningRequestFilename, params1) {
2602
+ await ensure_openssl_installed();
2603
+ assert9(fs9.existsSync(certificateSigningRequestFilename));
2604
+ if (!certificateFileExist(certificate)) {
2605
+ return "";
2606
+ }
2607
+ adjustDate(params1);
2608
+ adjustApplicationUri(params1);
2609
+ processAltNames(params1);
2610
+ const options = { cwd: this.rootDir };
2611
+ const csr = await readCertificateSigningRequest(certificateSigningRequestFilename);
2612
+ const csrInfo = exploreCertificateSigningRequest(csr);
2613
+ const applicationUri = csrInfo.extensionRequest.subjectAltName.uniformResourceIdentifier ? csrInfo.extensionRequest.subjectAltName.uniformResourceIdentifier[0] : void 0;
2614
+ if (typeof applicationUri !== "string") {
2615
+ throw new Error("Cannot find applicationUri in CSR");
2616
+ }
2617
+ const dns2 = csrInfo.extensionRequest.subjectAltName.dNSName || [];
2618
+ let ip = csrInfo.extensionRequest.subjectAltName.iPAddress || [];
2619
+ ip = ip.map(octetStringToIpAddress);
2620
+ const params = {
2621
+ applicationUri,
2622
+ dns: dns2,
2623
+ ip
2624
+ };
2625
+ processAltNames(params);
2626
+ const configFile = generateStaticConfig("conf/caconfig.cnf", options);
2627
+ displaySubtitle("- then we ask the authority to sign the certificate signing request");
2628
+ const configOption = ` -config ${configFile}`;
2629
+ await execute_openssl(
2630
+ "ca " + configOption + " -startdate " + x509Date(params1.startDate) + " -enddate " + x509Date(params1.endDate) + " -batch -out " + q3(n4(certificate)) + " -in " + q3(n4(certificateSigningRequestFilename)),
2631
+ options
2632
+ );
2633
+ displaySubtitle("- dump the certificate for a check");
2634
+ await execute_openssl(`x509 -in ${q3(n4(certificate))} -dates -fingerprint -purpose -noout`, options);
2635
+ displaySubtitle("- construct CA certificate with CRL");
2636
+ await this.constructCACertificateWithCRL();
2637
+ displaySubtitle("- construct certificate chain");
2638
+ await this.constructCertificateChain(certificate);
2639
+ displaySubtitle("- verify certificate against the root CA");
2640
+ await this.verifyCertificate(certificate);
2641
+ return certificate;
2642
+ }
2643
+ /**
2644
+ * Verify a certificate against this CA.
2645
+ *
2646
+ * @param certificate - path to the certificate file to verify
2647
+ */
2648
+ async verifyCertificate(certificate) {
2649
+ const isImplemented = false;
2650
+ if (isImplemented) {
2651
+ const options = { cwd: this.rootDir };
2652
+ const configFile = generateStaticConfig("conf/caconfig.cnf", options);
2653
+ setEnv("OPENSSL_CONF", makePath(configFile));
2654
+ const _configOption = ` -config ${configFile}`;
2655
+ _configOption;
2656
+ await execute_openssl_no_failure(
2657
+ `verify -verbose -CAfile ${q3(n4(this.caCertificateWithCrl))} ${q3(n4(certificate))}`,
2658
+ options
2659
+ );
2660
+ }
2661
+ }
2662
+ };
2663
+ }
2664
+ });
2665
+
2666
+ // packages/node-opcua-pki/lib/ca/crypto_create_CA.ts
2667
+ import assert10 from "assert";
2668
+ import fs10 from "fs";
2669
+ import { createRequire } from "module";
2670
+ import os4 from "os";
2671
+ import path7 from "path";
2672
+ import chalk7 from "chalk";
2673
+ import { CertificatePurpose as CertificatePurpose2, generatePrivateKeyFile as generatePrivateKeyFile3, Subject as Subject5 } from "node-opcua-crypto";
2674
+ import commandLineArgs from "command-line-args";
2675
+ import commandLineUsage from "command-line-usage";
2676
+ function get_offset_date(date, nbDays) {
2677
+ const d = new Date(date.getTime());
2678
+ d.setDate(d.getDate() + nbDays);
2679
+ return d;
2680
+ }
2681
+ async function construct_CertificateAuthority2(subject) {
2682
+ assert10(typeof gLocalConfig.CAFolder === "string", "expecting a CAFolder in config");
2683
+ assert10(typeof gLocalConfig.keySize === "number", "expecting a keySize in config");
2684
+ if (!g_certificateAuthority) {
2685
+ g_certificateAuthority = new CertificateAuthority({
2686
+ keySize: gLocalConfig.keySize,
2687
+ location: gLocalConfig.CAFolder,
2688
+ subject
2689
+ });
2690
+ await g_certificateAuthority.initialize();
2691
+ }
2692
+ }
2693
+ async function construct_CertificateManager() {
2694
+ assert10(typeof gLocalConfig.PKIFolder === "string", "expecting a PKIFolder in config");
2695
+ if (!certificateManager) {
2696
+ certificateManager = new CertificateManager({
2697
+ keySize: gLocalConfig.keySize,
2698
+ location: gLocalConfig.PKIFolder
2699
+ });
2700
+ await certificateManager.initialize();
2701
+ }
2702
+ }
2703
+ function default_template_content() {
2704
+ if (process.pkg?.entrypoint) {
2705
+ const a = fs10.readFileSync(path7.join(__dirname, "../../bin/pki_config.example.js"), "utf8");
2706
+ return a;
2707
+ }
2708
+ function find_default_config_template() {
2709
+ const rootFolder = find_module_root_folder();
2710
+ const configName = "pki_config.example.js";
2711
+ let default_config_template2 = path7.join(rootFolder, "bin", configName);
2712
+ if (!fs10.existsSync(default_config_template2)) {
2713
+ default_config_template2 = path7.join(__dirname, "..", configName);
2714
+ if (!fs10.existsSync(default_config_template2)) {
2715
+ default_config_template2 = path7.join(__dirname, `../bin/${configName}`);
2716
+ }
2717
+ }
2718
+ return default_config_template2;
2719
+ }
2720
+ const default_config_template = find_default_config_template();
2721
+ assert10(fs10.existsSync(default_config_template));
2722
+ const default_config_template_content = fs10.readFileSync(default_config_template, "utf8");
2723
+ return default_config_template_content;
2724
+ }
2725
+ function find_module_root_folder() {
2726
+ let rootFolder = path7.join(__dirname);
2727
+ for (let i = 0; i < 4; i++) {
2728
+ if (fs10.existsSync(path7.join(rootFolder, "package.json"))) {
2729
+ return rootFolder;
2730
+ }
2731
+ rootFolder = path7.join(rootFolder, "..");
2732
+ }
2733
+ assert10(fs10.existsSync(path7.join(rootFolder, "package.json")), "root folder must have a package.json file");
2734
+ return rootFolder;
2735
+ }
2736
+ async function readConfiguration(argv) {
2737
+ if (argv.silent) {
2738
+ g_config.silent = true;
2739
+ } else {
2740
+ g_config.silent = false;
2741
+ }
2742
+ const fqdn2 = await extractFullyQualifiedDomainName();
2743
+ const hostname = os4.hostname();
2744
+ let certificateDir;
2745
+ function performSubstitution(str) {
2746
+ str = str.replace("{CWD}", process.cwd());
2747
+ if (certificateDir) {
2748
+ str = str.replace("{root}", certificateDir);
2749
+ }
2750
+ if (gLocalConfig?.PKIFolder) {
2751
+ str = str.replace("{PKIFolder}", gLocalConfig.PKIFolder);
2752
+ }
2753
+ str = str.replace("{hostname}", hostname);
2754
+ str = str.replace("%FQDN%", fqdn2);
2755
+ return str;
2756
+ }
2757
+ function prepare(file) {
2758
+ const tmp = path7.resolve(performSubstitution(file));
2759
+ return makePath(tmp);
2760
+ }
2761
+ certificateDir = argv.root;
2762
+ assert10(typeof certificateDir === "string");
2763
+ certificateDir = prepare(certificateDir);
2764
+ mkdirRecursiveSync(certificateDir);
2765
+ assert10(fs10.existsSync(certificateDir));
2766
+ const default_config = path7.join(certificateDir, "config.js");
2767
+ if (!fs10.existsSync(default_config)) {
2768
+ debugLog(chalk7.yellow(" Creating default g_config file "), chalk7.cyan(default_config));
2769
+ const default_config_template_content = default_template_content();
2770
+ fs10.writeFileSync(default_config, default_config_template_content);
2771
+ } else {
2772
+ debugLog(chalk7.yellow(" using g_config file "), chalk7.cyan(default_config));
2773
+ }
2774
+ if (!fs10.existsSync(default_config)) {
2775
+ debugLog(chalk7.redBright(" cannot find config file ", default_config));
2776
+ }
2777
+ const defaultRandomFile = path7.join(path7.dirname(default_config), "random.rnd");
2778
+ setEnv("RANDFILE", defaultRandomFile);
2779
+ const _require = createRequire(__filename);
2780
+ gLocalConfig = _require(default_config);
2781
+ gLocalConfig.subject = new Subject5(gLocalConfig.subject || "");
2782
+ if (argv.subject) {
2783
+ gLocalConfig.subject = new Subject5(argv.subject);
2784
+ }
2785
+ if (!gLocalConfig.subject.commonName) {
2786
+ throw new Error("subject must have a Common Name");
2787
+ }
2788
+ gLocalConfig.certificateDir = certificateDir;
2789
+ let CAFolder = argv.CAFolder || path7.join(certificateDir, "CA");
2790
+ CAFolder = prepare(CAFolder);
2791
+ gLocalConfig.CAFolder = CAFolder;
2792
+ gLocalConfig.PKIFolder = path7.join(gLocalConfig.certificateDir, "PKI");
2793
+ if (argv.PKIFolder) {
2794
+ gLocalConfig.PKIFolder = prepare(argv.PKIFolder);
2795
+ }
2796
+ gLocalConfig.PKIFolder = prepare(gLocalConfig.PKIFolder);
2797
+ if (argv.privateKey) {
2798
+ gLocalConfig.privateKey = prepare(argv.privateKey);
2799
+ }
2800
+ if (argv.applicationUri) {
2801
+ gLocalConfig.applicationUri = performSubstitution(argv.applicationUri);
2802
+ }
2803
+ if (argv.output) {
2804
+ gLocalConfig.outputFile = argv.output;
2805
+ }
2806
+ gLocalConfig.altNames = [];
2807
+ if (argv.altNames) {
2808
+ gLocalConfig.altNames = argv.altNames.split(";");
2809
+ }
2810
+ gLocalConfig.dns = [getFullyQualifiedDomainName()];
2811
+ if (argv.dns) {
2812
+ gLocalConfig.dns = argv.dns.split(",").map(performSubstitution);
2813
+ }
2814
+ gLocalConfig.ip = [];
2815
+ if (argv.ip) {
2816
+ gLocalConfig.ip = argv.ip.split(",");
2817
+ }
2818
+ if (argv.keySize) {
2819
+ const v = argv.keySize;
2820
+ if (v !== 1024 && v !== 2048 && v !== 3072 && v !== 4096) {
2821
+ throw new Error(`invalid keysize specified ${v} should be 1024,2048,3072 or 4096`);
2822
+ }
2823
+ gLocalConfig.keySize = argv.keySize;
2824
+ }
2825
+ if (argv.validity) {
2826
+ gLocalConfig.validity = argv.validity;
2827
+ }
2828
+ }
2829
+ async function createDefaultCertificate(base_name, prefix, key_length, applicationUri, dev) {
2830
+ assert10(key_length === 1024 || key_length === 2048 || key_length === 3072 || key_length === 4096);
2831
+ const private_key_file = makePath(base_name, `${prefix}key_${key_length}.pem`);
2832
+ const public_key_file = makePath(base_name, `${prefix}public_key_${key_length}.pub`);
2833
+ const certificate_file = makePath(base_name, `${prefix}cert_${key_length}.pem`);
2834
+ const certificate_file_outofdate = makePath(base_name, `${prefix}cert_${key_length}_outofdate.pem`);
2835
+ const certificate_file_not_active_yet = makePath(base_name, `${prefix}cert_${key_length}_not_active_yet.pem`);
2836
+ const certificate_revoked = makePath(base_name, `${prefix}cert_${key_length}_revoked.pem`);
2837
+ const self_signed_certificate_file = makePath(base_name, `${prefix}selfsigned_cert_${key_length}.pem`);
2838
+ const fqdn2 = getFullyQualifiedDomainName();
2839
+ const hostname = os4.hostname();
2840
+ const dns2 = [
2841
+ // for conformance reason, localhost shall not be present in the DNS field of COP
2842
+ // ***FORBIDEN** "localhost",
2843
+ getFullyQualifiedDomainName()
2844
+ ];
2845
+ if (hostname !== fqdn2) {
2846
+ dns2.push(hostname);
2847
+ }
2848
+ const ip = [];
2849
+ async function createCertificateIfNotExist(certificate, private_key, applicationUri2, startDate, validity) {
2850
+ if (fs10.existsSync(certificate)) {
2851
+ warningLog(chalk7.yellow(" certificate"), chalk7.cyan(certificate), chalk7.yellow(" already exists => skipping"));
2852
+ return "";
2853
+ } else {
2854
+ return await createCertificate(certificate, private_key, applicationUri2, startDate, validity);
2855
+ }
2856
+ }
2857
+ async function createCertificate(certificate, privateKey, applicationUri2, startDate, validity) {
2858
+ const certificateSigningRequestFile = `${certificate}.csr`;
2859
+ const configFile = makePath(base_name, "../certificates/PKI/own/openssl.cnf");
2860
+ const dns3 = [os4.hostname()];
2861
+ const ip2 = ["127.0.0.1"];
2862
+ const params = {
2863
+ applicationUri: applicationUri2,
2864
+ privateKey,
2865
+ rootDir: ".",
2866
+ configFile,
2867
+ dns: dns3,
2868
+ ip: ip2,
2869
+ purpose: CertificatePurpose2.ForApplication
2870
+ };
2871
+ await createCertificateSigningRequestWithOpenSSL(certificateSigningRequestFile, params);
2872
+ return await g_certificateAuthority.signCertificateRequest(certificate, certificateSigningRequestFile, {
2873
+ applicationUri: applicationUri2,
2874
+ dns: dns3,
2875
+ ip: ip2,
2876
+ startDate,
2877
+ validity
2878
+ });
2879
+ }
2880
+ async function createSelfSignedCertificate2(certificate, private_key, applicationUri2, startDate, validity) {
2881
+ await g_certificateAuthority.createSelfSignedCertificate(certificate, private_key, {
2882
+ applicationUri: applicationUri2,
2883
+ dns: dns2,
2884
+ ip,
2885
+ startDate,
2886
+ validity
2887
+ });
2888
+ }
2889
+ async function revoke_certificate(certificate) {
2890
+ await g_certificateAuthority.revokeCertificate(certificate, {});
2891
+ }
2892
+ async function createPrivateKeyIfNotExist(privateKey, keyLength) {
2893
+ if (fs10.existsSync(privateKey)) {
2894
+ warningLog(chalk7.yellow(" privateKey"), chalk7.cyan(privateKey), chalk7.yellow(" already exists => skipping"));
2895
+ return;
2896
+ } else {
2897
+ await generatePrivateKeyFile3(privateKey, keyLength);
2898
+ }
2899
+ }
2900
+ displaySubtitle(` create private key :${private_key_file}`);
2901
+ await createPrivateKeyIfNotExist(private_key_file, key_length);
2902
+ displaySubtitle(` extract public key ${public_key_file} from private key `);
2903
+ await getPublicKeyFromPrivateKey(private_key_file, public_key_file);
2904
+ displaySubtitle(` create Certificate ${certificate_file}`);
2905
+ await createCertificateIfNotExist(certificate_file, private_key_file, applicationUri, yesterday, 365);
2906
+ displaySubtitle(` create self signed Certificate ${self_signed_certificate_file}`);
2907
+ if (fs10.existsSync(self_signed_certificate_file)) {
2908
+ return;
2909
+ }
2910
+ await createSelfSignedCertificate2(self_signed_certificate_file, private_key_file, applicationUri, yesterday, 365);
2911
+ if (dev) {
2912
+ await createCertificateIfNotExist(certificate_file_outofdate, private_key_file, applicationUri, two_years_ago, 365);
2913
+ await createCertificateIfNotExist(certificate_file_not_active_yet, private_key_file, applicationUri, next_year, 365);
2914
+ if (!fs10.existsSync(certificate_revoked)) {
2915
+ const certificate = await createCertificateIfNotExist(
2916
+ certificate_revoked,
2917
+ private_key_file,
2918
+ `${applicationUri}Revoked`,
2919
+ // make sure we used a uniq URI here
2920
+ yesterday,
2921
+ 365
2922
+ );
2923
+ warningLog(" certificate to revoke => ", certificate);
2924
+ revoke_certificate(certificate_revoked);
2925
+ }
2926
+ }
2927
+ }
2928
+ async function wrap(func) {
2929
+ try {
2930
+ await func();
2931
+ } catch (err) {
2932
+ console.log(err.message);
2933
+ }
2934
+ }
2935
+ async function create_default_certificates(dev) {
2936
+ assert10(gLocalConfig);
2937
+ const base_name = gLocalConfig.certificateDir || "";
2938
+ assert10(fs10.existsSync(base_name));
2939
+ let clientURN;
2940
+ let serverURN;
2941
+ let discoveryServerURN;
2942
+ wrap(async () => {
2943
+ await extractFullyQualifiedDomainName();
2944
+ const hostname = os4.hostname();
2945
+ const fqdn2 = getFullyQualifiedDomainName();
2946
+ warningLog(chalk7.yellow(" hostname = "), chalk7.cyan(hostname));
2947
+ warningLog(chalk7.yellow(" fqdn = "), chalk7.cyan(fqdn2));
2948
+ clientURN = makeApplicationUrn(hostname, "NodeOPCUA-Client");
2949
+ serverURN = makeApplicationUrn(hostname, "NodeOPCUA-Server");
2950
+ discoveryServerURN = makeApplicationUrn(hostname, "NodeOPCUA-DiscoveryServer");
2951
+ displayTitle("Create Application Certificate for Server & its private key");
2952
+ await createDefaultCertificate(base_name, "client_", 1024, clientURN, dev);
2953
+ await createDefaultCertificate(base_name, "client_", 2048, clientURN, dev);
2954
+ await createDefaultCertificate(base_name, "client_", 3072, clientURN, dev);
2955
+ await createDefaultCertificate(base_name, "client_", 4096, clientURN, dev);
2956
+ displayTitle("Create Application Certificate for Client & its private key");
2957
+ await createDefaultCertificate(base_name, "server_", 1024, serverURN, dev);
2958
+ await createDefaultCertificate(base_name, "server_", 2048, serverURN, dev);
2959
+ await createDefaultCertificate(base_name, "server_", 3072, serverURN, dev);
2960
+ await createDefaultCertificate(base_name, "server_", 4096, serverURN, dev);
2961
+ displayTitle("Create Application Certificate for DiscoveryServer & its private key");
2962
+ await createDefaultCertificate(base_name, "discoveryServer_", 1024, discoveryServerURN, dev);
2963
+ await createDefaultCertificate(base_name, "discoveryServer_", 2048, discoveryServerURN, dev);
2964
+ await createDefaultCertificate(base_name, "discoveryServer_", 3072, discoveryServerURN, dev);
2965
+ await createDefaultCertificate(base_name, "discoveryServer_", 4096, discoveryServerURN, dev);
2966
+ });
2967
+ }
2968
+ async function createDefaultCertificates(dev) {
2969
+ await construct_CertificateAuthority2("");
2970
+ await construct_CertificateManager();
2971
+ await create_default_certificates(dev);
2972
+ }
2973
+ function getOptions(names) {
2974
+ return commonOptions.filter((o) => names.includes(o.name) || o.name === "help" || o.name === "silent");
2975
+ }
2976
+ function showHelp(command, description, options, usage) {
2977
+ const sections = [
2978
+ {
2979
+ header: `Command: ${command}`,
2980
+ content: description
2981
+ },
2982
+ {
2983
+ header: "Usage",
2984
+ content: usage || `$0 ${command} [options]`
2985
+ },
2986
+ {
2987
+ header: "Options",
2988
+ optionList: options
2989
+ }
2990
+ ];
2991
+ console.log(commandLineUsage(sections));
2992
+ }
2993
+ async function main(argumentsList) {
2994
+ const mainDefinitions = [{ name: "command", defaultOption: true }];
2995
+ let mainOptions;
2996
+ try {
2997
+ mainOptions = commandLineArgs(mainDefinitions, { argv: argumentsList, stopAtFirstUnknown: true });
2998
+ } catch (err) {
2999
+ console.log(err.message);
3000
+ return;
3001
+ }
3002
+ const argv = mainOptions._unknown || [];
3003
+ const command = mainOptions.command;
3004
+ if (!command || command === "help") {
3005
+ console.log(
3006
+ commandLineUsage([
3007
+ {
3008
+ header: "node-opcua-pki",
3009
+ content: `PKI management for node-opcua
3010
+
3011
+ ${epilog}`
3012
+ },
3013
+ {
3014
+ header: "Commands",
3015
+ content: [
3016
+ { name: "demo", summary: "create default certificate for node-opcua demos" },
3017
+ { name: "createCA", summary: "create a Certificate Authority" },
3018
+ { name: "createPKI", summary: "create a Public Key Infrastructure" },
3019
+ { name: "certificate", summary: "create a new certificate" },
3020
+ { name: "revoke <certificateFile>", summary: "revoke a existing certificate" },
3021
+ { name: "csr", summary: "create a certificate signing request" },
3022
+ { name: "sign", summary: "validate a certificate signing request and generate a certificate" },
3023
+ { name: "dump <certificateFile>", summary: "display a certificate" },
3024
+ { name: "toder <pemCertificate>", summary: "convert a certificate to a DER format with finger print" },
3025
+ { name: "fingerprint <certificateFile>", summary: "print the certificate fingerprint" },
3026
+ { name: "version", summary: "display the version number" }
3027
+ ]
3028
+ }
3029
+ ])
3030
+ );
3031
+ return;
3032
+ }
3033
+ if (command === "version") {
3034
+ const rootFolder = find_module_root_folder();
3035
+ const pkg = JSON.parse(fs10.readFileSync(path7.join(rootFolder, "package.json"), "utf-8"));
3036
+ console.log(pkg.version);
3037
+ return;
3038
+ }
3039
+ if (command === "demo") {
3040
+ const optionsDef = [
3041
+ ...getOptions(["root", "silent"]),
3042
+ { name: "dev", type: Boolean, description: "create all sort of fancy certificates for dev testing purposes" },
3043
+ { name: "clean", type: Boolean, description: "Purge existing directory [use with care!]" }
3044
+ ];
3045
+ const local_argv = commandLineArgs(optionsDef, { argv });
3046
+ if (local_argv.help)
3047
+ return showHelp(
3048
+ "demo",
3049
+ "create default certificate for node-opcua demos",
3050
+ optionsDef,
3051
+ "$0 demo [--dev] [--silent] [--clean]"
3052
+ );
3053
+ await wrap(async () => {
3054
+ await ensure_openssl_installed();
3055
+ displayChapter("Create Demo certificates");
3056
+ displayTitle("reading configuration");
3057
+ await readConfiguration(local_argv);
3058
+ if (local_argv.clean) {
3059
+ displayTitle("Cleaning old certificates");
3060
+ assert10(gLocalConfig);
3061
+ const certificateDir = gLocalConfig.certificateDir || "";
3062
+ const files = await fs10.promises.readdir(certificateDir);
3063
+ for (const file of files) {
3064
+ if (file.includes(".pem") || file.includes(".pub")) {
3065
+ await fs10.promises.unlink(path7.join(certificateDir, file));
3066
+ }
3067
+ }
3068
+ mkdirRecursiveSync(certificateDir);
3069
+ }
3070
+ displayTitle("create certificates");
3071
+ await createDefaultCertificates(local_argv.dev);
3072
+ displayChapter("Demo certificates CREATED");
3073
+ });
3074
+ return;
3075
+ }
3076
+ if (command === "createCA") {
3077
+ const optionsDef = [
3078
+ ...getOptions(["root", "CAFolder", "keySize", "silent"]),
3079
+ { name: "subject", type: String, defaultValue: defaultSubject, description: "the CA certificate subject" }
3080
+ ];
3081
+ const local_argv = commandLineArgs(optionsDef, { argv });
3082
+ if (local_argv.help) return showHelp("createCA", "create a Certificate Authority", optionsDef);
3083
+ await wrap(async () => {
3084
+ await ensure_openssl_installed();
3085
+ await readConfiguration(local_argv);
3086
+ await construct_CertificateAuthority2(local_argv.subject);
3087
+ });
3088
+ return;
3089
+ }
3090
+ if (command === "createPKI") {
3091
+ const optionsDef = getOptions(["root", "PKIFolder", "keySize", "silent"]);
3092
+ const local_argv = commandLineArgs(optionsDef, { argv });
3093
+ if (local_argv.help) return showHelp("createPKI", "create a Public Key Infrastructure", optionsDef);
3094
+ await wrap(async () => {
3095
+ await readConfiguration(local_argv);
3096
+ await construct_CertificateManager();
3097
+ });
3098
+ return;
3099
+ }
3100
+ if (command === "certificate") {
3101
+ const optionsDef = [
3102
+ ...getOptions(["root", "CAFolder", "PKIFolder", "privateKey", "silent"]),
3103
+ {
3104
+ name: "applicationUri",
3105
+ alias: "a",
3106
+ type: String,
3107
+ defaultValue: "urn:{hostname}:Node-OPCUA-Server",
3108
+ description: "the application URI"
3109
+ },
3110
+ {
3111
+ name: "output",
3112
+ alias: "o",
3113
+ type: String,
3114
+ defaultValue: "my_certificate.pem",
3115
+ description: "the name of the generated certificate =>"
3116
+ },
3117
+ {
3118
+ name: "selfSigned",
3119
+ alias: "s",
3120
+ type: Boolean,
3121
+ defaultValue: false,
3122
+ description: "if true, certificate will be self-signed"
3123
+ },
3124
+ { name: "validity", alias: "v", type: Number, description: "the certificate validity in days" },
3125
+ {
3126
+ name: "dns",
3127
+ type: String,
3128
+ defaultValue: "{hostname}",
3129
+ description: "the list of valid domain name (comma separated)"
3130
+ },
3131
+ { name: "ip", type: String, defaultValue: "", description: "the list of valid IPs (comma separated)" },
3132
+ {
3133
+ name: "subject",
3134
+ type: String,
3135
+ defaultValue: "",
3136
+ description: "the certificate subject ( for instance C=FR/ST=Centre/L=Orleans/O=SomeOrganization/CN=Hello )"
3137
+ }
3138
+ ];
3139
+ const local_argv = commandLineArgs(optionsDef, { argv });
3140
+ if (local_argv.help || !local_argv.applicationUri || !local_argv.output)
3141
+ return showHelp("certificate", "create a new certificate", optionsDef);
3142
+ async function command_certificate(local_argv2) {
3143
+ const selfSigned = !!local_argv2.selfSigned;
3144
+ if (!selfSigned) {
3145
+ await command_full_certificate(local_argv2);
3146
+ } else {
3147
+ await command_selfsigned_certificate(local_argv2);
3148
+ }
3149
+ }
3150
+ async function command_selfsigned_certificate(local_argv2) {
3151
+ const _fqdn = await extractFullyQualifiedDomainName();
3152
+ await readConfiguration(local_argv2);
3153
+ await construct_CertificateManager();
3154
+ displaySubtitle(` create self signed Certificate ${gLocalConfig.outputFile}`);
3155
+ let subject = local_argv2.subject && local_argv2.subject.length > 1 ? new Subject5(local_argv2.subject) : gLocalConfig.subject || "";
3156
+ subject = JSON.parse(JSON.stringify(subject));
3157
+ const params = {
3158
+ applicationUri: gLocalConfig.applicationUri || "",
3159
+ dns: gLocalConfig.dns || [],
3160
+ ip: gLocalConfig.ip || [],
3161
+ outputFile: gLocalConfig.outputFile || "self_signed_certificate.pem",
3162
+ startDate: gLocalConfig.startDate || /* @__PURE__ */ new Date(),
3163
+ subject,
3164
+ validity: gLocalConfig.validity || 365
3165
+ };
3166
+ await certificateManager.createSelfSignedCertificate(params);
3167
+ }
3168
+ async function command_full_certificate(local_argv2) {
3169
+ await readConfiguration(local_argv2);
3170
+ await construct_CertificateManager();
3171
+ await construct_CertificateAuthority2("");
3172
+ assert10(fs10.existsSync(gLocalConfig.CAFolder || ""), " CA folder must exist");
3173
+ gLocalConfig.privateKey = void 0;
3174
+ gLocalConfig.subject = local_argv2.subject && local_argv2.subject.length > 1 ? local_argv2.subject : gLocalConfig.subject;
3175
+ const csr_file = await certificateManager.createCertificateRequest(
3176
+ gLocalConfig
3177
+ );
3178
+ if (!csr_file) {
3179
+ return;
3180
+ }
3181
+ warningLog(" csr_file = ", csr_file);
3182
+ const certificate = csr_file.replace(".csr", ".pem");
3183
+ if (fs10.existsSync(certificate)) {
3184
+ throw new Error(` File ${certificate} already exist`);
3185
+ }
3186
+ await g_certificateAuthority.signCertificateRequest(
3187
+ certificate,
3188
+ csr_file,
3189
+ gLocalConfig
3190
+ );
3191
+ assert10(typeof gLocalConfig.outputFile === "string");
3192
+ fs10.writeFileSync(gLocalConfig.outputFile || "", fs10.readFileSync(certificate, "ascii"));
3193
+ }
3194
+ await wrap(async () => await command_certificate(local_argv));
3195
+ return;
3196
+ }
3197
+ if (command === "revoke") {
3198
+ const optionsDef = [{ name: "certificateFile", type: String, defaultOption: true }, ...getOptions(["root", "CAFolder"])];
3199
+ const local_argv = commandLineArgs(optionsDef, { argv });
3200
+ if (local_argv.help || !local_argv.certificateFile)
3201
+ return showHelp(
3202
+ "revoke <certificateFile>",
3203
+ "revoke a existing certificate",
3204
+ optionsDef,
3205
+ "$0 revoke my_certificate.pem"
3206
+ );
3207
+ async function revoke_certificate(certificate) {
3208
+ await g_certificateAuthority.revokeCertificate(certificate, {});
3209
+ }
3210
+ await wrap(async () => {
3211
+ const certificate = path7.resolve(local_argv.certificateFile);
3212
+ warningLog(chalk7.yellow(" Certificate to revoke : "), chalk7.cyan(certificate));
3213
+ if (!fs10.existsSync(certificate)) {
3214
+ throw new Error(`cannot find certificate to revoke ${certificate}`);
3215
+ }
3216
+ await readConfiguration(local_argv);
3217
+ await construct_CertificateAuthority2("");
3218
+ await revoke_certificate(certificate);
3219
+ warningLog("done ... ");
3220
+ warningLog(" crl = ", g_certificateAuthority.revocationList);
3221
+ warningLog("\nyou should now publish the new Certificate Revocation List");
3222
+ });
3223
+ return;
3224
+ }
3225
+ if (command === "csr") {
3226
+ const optionsDef = [
3227
+ ...getOptions(["root", "PKIFolder", "privateKey", "silent"]),
3228
+ {
3229
+ name: "applicationUri",
3230
+ alias: "a",
3231
+ type: String,
3232
+ defaultValue: "urn:{hostname}:Node-OPCUA-Server",
3233
+ description: "the application URI"
3234
+ },
3235
+ {
3236
+ name: "output",
3237
+ alias: "o",
3238
+ type: String,
3239
+ defaultValue: "my_certificate_signing_request.csr",
3240
+ description: "the name of the generated signing_request"
3241
+ },
3242
+ {
3243
+ name: "dns",
3244
+ type: String,
3245
+ defaultValue: "{hostname}",
3246
+ description: "the list of valid domain name (comma separated)"
3247
+ },
3248
+ { name: "ip", type: String, defaultValue: "", description: "the list of valid IPs (comma separated)" },
3249
+ {
3250
+ name: "subject",
3251
+ type: String,
3252
+ defaultValue: "/CN=Certificate",
3253
+ description: "the certificate subject ( for instance /C=FR/ST=Centre/L=Orleans/O=SomeOrganization/CN=Hello )"
3254
+ }
3255
+ ];
3256
+ const local_argv = commandLineArgs(optionsDef, { argv });
3257
+ if (local_argv.help) return showHelp("csr", "create a certificate signing request", optionsDef);
3258
+ await wrap(async () => {
3259
+ await readConfiguration(local_argv);
3260
+ if (!fs10.existsSync(gLocalConfig.PKIFolder || "")) {
3261
+ warningLog("PKI folder must exist");
3262
+ }
3263
+ await construct_CertificateManager();
3264
+ if (!gLocalConfig.outputFile || fs10.existsSync(gLocalConfig.outputFile)) {
3265
+ throw new Error(` File ${gLocalConfig.outputFile} already exist`);
3266
+ }
3267
+ gLocalConfig.privateKey = void 0;
3268
+ gLocalConfig.subject = local_argv.subject && local_argv.subject.length > 1 ? local_argv.subject : gLocalConfig.subject;
3269
+ const internal_csr_file = await certificateManager.createCertificateRequest(
3270
+ gLocalConfig
3271
+ );
3272
+ if (!internal_csr_file) {
3273
+ return;
3274
+ }
3275
+ if (!gLocalConfig.outputFile) {
3276
+ warningLog("please specify a output file");
3277
+ return;
3278
+ }
3279
+ const csr = await fs10.promises.readFile(internal_csr_file, "utf-8");
3280
+ fs10.writeFileSync(gLocalConfig.outputFile || "", csr, "utf-8");
3281
+ warningLog("Subject = ", gLocalConfig.subject);
3282
+ warningLog("applicationUri = ", gLocalConfig.applicationUri);
3283
+ warningLog("altNames = ", gLocalConfig.altNames);
3284
+ warningLog("dns = ", gLocalConfig.dns);
3285
+ warningLog("ip = ", gLocalConfig.ip);
3286
+ warningLog("CSR file = ", gLocalConfig.outputFile);
3287
+ });
3288
+ return;
3289
+ }
3290
+ if (command === "sign") {
3291
+ const optionsDef = [
3292
+ ...getOptions(["root", "CAFolder", "silent"]),
3293
+ { name: "csr", alias: "i", type: String, defaultValue: "my_certificate_signing_request.csr", description: "the csr" },
3294
+ {
3295
+ name: "output",
3296
+ alias: "o",
3297
+ type: String,
3298
+ defaultValue: "my_certificate.pem",
3299
+ description: "the name of the generated certificate"
3300
+ },
3301
+ { name: "validity", alias: "v", type: Number, defaultValue: 365, description: "the certificate validity in days" }
3302
+ ];
3303
+ const local_argv = commandLineArgs(optionsDef, { argv });
3304
+ if (local_argv.help || !local_argv.csr || !local_argv.output)
3305
+ return showHelp("sign", "validate a certificate signing request and generate a certificate", optionsDef);
3306
+ await wrap(async () => {
3307
+ await readConfiguration(local_argv);
3308
+ if (!fs10.existsSync(gLocalConfig.CAFolder || "")) {
3309
+ throw new Error(`CA folder must exist:${gLocalConfig.CAFolder}`);
3310
+ }
3311
+ await construct_CertificateAuthority2("");
3312
+ const csr_file = path7.resolve(local_argv.csr || "");
3313
+ if (!fs10.existsSync(csr_file)) {
3314
+ throw new Error(`Certificate signing request doesn't exist: ${csr_file}`);
3315
+ }
3316
+ const certificate = path7.resolve(local_argv.output || csr_file.replace(".csr", ".pem"));
3317
+ if (fs10.existsSync(certificate)) {
3318
+ throw new Error(` File ${certificate} already exist`);
3319
+ }
3320
+ await g_certificateAuthority.signCertificateRequest(
3321
+ certificate,
3322
+ csr_file,
3323
+ gLocalConfig
3324
+ );
3325
+ assert10(typeof gLocalConfig.outputFile === "string");
3326
+ fs10.writeFileSync(gLocalConfig.outputFile || "", fs10.readFileSync(certificate, "ascii"));
3327
+ });
3328
+ return;
3329
+ }
3330
+ if (command === "dump") {
3331
+ const optionsDef = [
3332
+ { name: "certificateFile", type: String, defaultOption: true },
3333
+ { name: "help", alias: "h", type: Boolean }
3334
+ ];
3335
+ const local_argv = commandLineArgs(optionsDef, { argv });
3336
+ if (local_argv.help || !local_argv.certificateFile)
3337
+ return showHelp("dump <certificateFile>", "display a certificate", optionsDef);
3338
+ await wrap(async () => {
3339
+ const data = await dumpCertificate(local_argv.certificateFile);
3340
+ warningLog(data);
3341
+ });
3342
+ return;
3343
+ }
3344
+ if (command === "toder") {
3345
+ const optionsDef = [
3346
+ { name: "pemCertificate", type: String, defaultOption: true },
3347
+ { name: "help", alias: "h", type: Boolean }
3348
+ ];
3349
+ const local_argv = commandLineArgs(optionsDef, { argv });
3350
+ if (local_argv.help || !local_argv.pemCertificate)
3351
+ return showHelp("toder <pemCertificate>", "convert a certificate to a DER format with finger print", optionsDef);
3352
+ await wrap(async () => {
3353
+ await toDer(local_argv.pemCertificate);
3354
+ });
3355
+ return;
3356
+ }
3357
+ if (command === "fingerprint") {
3358
+ const optionsDef = [
3359
+ { name: "certificateFile", type: String, defaultOption: true },
3360
+ { name: "help", alias: "h", type: Boolean }
3361
+ ];
3362
+ const local_argv = commandLineArgs(optionsDef, { argv });
3363
+ if (local_argv.help || !local_argv.certificateFile)
3364
+ return showHelp("fingerprint <certificateFile>", "print the certificate fingerprint", optionsDef);
3365
+ await wrap(async () => {
3366
+ const certificate = local_argv.certificateFile;
3367
+ const data = await fingerprint(certificate);
3368
+ if (!data) return;
3369
+ const s = data.split("=")[1].split(":").join("").trim();
3370
+ warningLog(s);
3371
+ });
3372
+ return;
3373
+ }
3374
+ console.log(`Unknown command: ${command}`);
3375
+ }
3376
+ var epilog, today, yesterday, two_years_ago, next_year, gLocalConfig, g_certificateAuthority, certificateManager, commonOptions;
3377
+ var init_crypto_create_CA = __esm({
3378
+ "packages/node-opcua-pki/lib/ca/crypto_create_CA.ts"() {
3379
+ "use strict";
3380
+ init_esm_shims();
3381
+ init_applicationurn();
3382
+ init_hostname();
3383
+ init_certificate_manager();
3384
+ init_toolbox();
3385
+ init_with_openssl();
3386
+ init_certificate_authority();
3387
+ epilog = "Copyright (c) sterfive - node-opcua - 2017-2026";
3388
+ today = /* @__PURE__ */ new Date();
3389
+ yesterday = get_offset_date(today, -1);
3390
+ two_years_ago = get_offset_date(today, -2 * 365);
3391
+ next_year = get_offset_date(today, 365);
3392
+ gLocalConfig = {};
3393
+ commonOptions = [
3394
+ {
3395
+ name: "root",
3396
+ alias: "r",
3397
+ type: String,
3398
+ defaultValue: "{CWD}/certificates",
3399
+ description: "the location of the Certificate folder"
3400
+ },
3401
+ {
3402
+ name: "CAFolder",
3403
+ alias: "c",
3404
+ type: String,
3405
+ defaultValue: "{root}/CA",
3406
+ description: "the location of the Certificate Authority folder"
3407
+ },
3408
+ { name: "PKIFolder", type: String, defaultValue: "{root}/PKI", description: "the location of the Public Key Infrastructure" },
3409
+ { name: "silent", type: Boolean, defaultValue: false, description: "minimize output" },
3410
+ {
3411
+ name: "privateKey",
3412
+ alias: "p",
3413
+ type: String,
3414
+ defaultValue: "{PKIFolder}/own/private_key.pem",
3415
+ description: "the private key to use to generate certificate"
3416
+ },
3417
+ {
3418
+ name: "keySize",
3419
+ alias: "k",
3420
+ type: Number,
3421
+ defaultValue: 2048,
3422
+ description: "the private key size in bits (1024|2048|3072|4096)"
3423
+ },
3424
+ { name: "help", alias: "h", type: Boolean, description: "display this help" }
3425
+ ];
3426
+ }
3427
+ });
6
3428
 
7
3429
  // packages/node-opcua-pki/bin/pki.ts
8
- import { pki_main } from "node-opcua-pki";
9
3430
  var require_pki = __commonJS({
10
3431
  "packages/node-opcua-pki/bin/pki.ts"() {
11
3432
  init_esm_shims();
12
- pki_main(process.argv.splice(2));
3433
+ init_crypto_create_CA();
3434
+ main(process.argv.splice(2));
13
3435
  }
14
3436
  });
15
3437
  export default require_pki();