node-opcua-pki 6.4.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/bin/pki.ts +1 -1
- package/dist/bin/install_prerequisite.mjs +1 -1
- package/dist/bin/pki.mjs +3425 -3
- package/dist/bin/pki.mjs.map +1 -1
- package/dist/{chunk-VXGTT7QM.mjs → chunk-GCHH54PS.mjs} +9 -1
- package/dist/{chunk-VXGTT7QM.mjs.map → chunk-GCHH54PS.mjs.map} +1 -1
- package/dist/index.d.mts +493 -69
- package/dist/index.d.ts +493 -69
- package/dist/index.js +564 -1193
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +618 -1248
- package/dist/index.mjs.map +1 -1
- package/package.json +3 -3
- package/readme.md +1 -2
package/dist/index.mjs
CHANGED
|
@@ -1,15 +1,7 @@
|
|
|
1
|
-
// node_modules/tsup/assets/esm_shims.js
|
|
2
|
-
import path from "path";
|
|
3
|
-
import { fileURLToPath } from "url";
|
|
4
|
-
var getFilename = () => fileURLToPath(import.meta.url);
|
|
5
|
-
var getDirname = () => path.dirname(getFilename());
|
|
6
|
-
var __dirname = /* @__PURE__ */ getDirname();
|
|
7
|
-
var __filename = /* @__PURE__ */ getFilename();
|
|
8
|
-
|
|
9
1
|
// packages/node-opcua-pki/lib/ca/certificate_authority.ts
|
|
10
2
|
import assert6 from "assert";
|
|
11
3
|
import fs6 from "fs";
|
|
12
|
-
import
|
|
4
|
+
import path5 from "path";
|
|
13
5
|
import chalk5 from "chalk";
|
|
14
6
|
import {
|
|
15
7
|
exploreCertificateSigningRequest,
|
|
@@ -43,7 +35,7 @@ function adjustApplicationUri(params) {
|
|
|
43
35
|
// packages/node-opcua-pki/lib/toolbox/common2.ts
|
|
44
36
|
import assert2 from "assert";
|
|
45
37
|
import fs from "fs";
|
|
46
|
-
import
|
|
38
|
+
import path from "path";
|
|
47
39
|
import chalk from "chalk";
|
|
48
40
|
|
|
49
41
|
// packages/node-opcua-pki/lib/toolbox/config.ts
|
|
@@ -85,7 +77,7 @@ function mkdirRecursiveSync(folder) {
|
|
|
85
77
|
function makePath(folderName, filename) {
|
|
86
78
|
let s;
|
|
87
79
|
if (filename) {
|
|
88
|
-
s =
|
|
80
|
+
s = path.join(path.normalize(folderName), filename);
|
|
89
81
|
} else {
|
|
90
82
|
assert2(folderName);
|
|
91
83
|
s = folderName;
|
|
@@ -96,13 +88,6 @@ function makePath(folderName, filename) {
|
|
|
96
88
|
|
|
97
89
|
// packages/node-opcua-pki/lib/toolbox/display.ts
|
|
98
90
|
import chalk2 from "chalk";
|
|
99
|
-
function displayChapter(str) {
|
|
100
|
-
const l = " ";
|
|
101
|
-
warningLog(`${chalk2.bgWhite(l)} `);
|
|
102
|
-
str = ` ${str}${l}`.substring(0, l.length);
|
|
103
|
-
warningLog(chalk2.bgWhite.cyan(str));
|
|
104
|
-
warningLog(`${chalk2.bgWhite(l)} `);
|
|
105
|
-
}
|
|
106
91
|
function displayTitle(str) {
|
|
107
92
|
if (!g_config.silent) {
|
|
108
93
|
warningLog("");
|
|
@@ -168,7 +153,7 @@ function processAltNames(params) {
|
|
|
168
153
|
// packages/node-opcua-pki/lib/toolbox/with_openssl/create_certificate_signing_request.ts
|
|
169
154
|
import assert5 from "assert";
|
|
170
155
|
import fs5 from "fs";
|
|
171
|
-
import
|
|
156
|
+
import path4 from "path";
|
|
172
157
|
|
|
173
158
|
// packages/node-opcua-pki/lib/misc/subject.ts
|
|
174
159
|
import { Subject } from "node-opcua-crypto";
|
|
@@ -185,7 +170,7 @@ import chalk4 from "chalk";
|
|
|
185
170
|
import child_process from "child_process";
|
|
186
171
|
import fs2 from "fs";
|
|
187
172
|
import os from "os";
|
|
188
|
-
import
|
|
173
|
+
import path2 from "path";
|
|
189
174
|
import url from "url";
|
|
190
175
|
import byline from "byline";
|
|
191
176
|
import chalk3 from "chalk";
|
|
@@ -286,19 +271,19 @@ async function check_system_openssl_version() {
|
|
|
286
271
|
return output;
|
|
287
272
|
}
|
|
288
273
|
async function install_and_check_win32_openssl_version() {
|
|
289
|
-
const downloadFolder =
|
|
274
|
+
const downloadFolder = path2.join(os.tmpdir(), ".");
|
|
290
275
|
function get_openssl_folder_win32() {
|
|
291
276
|
if (process.env.LOCALAPPDATA) {
|
|
292
|
-
const userProgramFolder =
|
|
277
|
+
const userProgramFolder = path2.join(process.env.LOCALAPPDATA, "Programs");
|
|
293
278
|
if (fs2.existsSync(userProgramFolder)) {
|
|
294
|
-
return
|
|
279
|
+
return path2.join(userProgramFolder, "openssl");
|
|
295
280
|
}
|
|
296
281
|
}
|
|
297
|
-
return
|
|
282
|
+
return path2.join(process.cwd(), "openssl");
|
|
298
283
|
}
|
|
299
284
|
function get_openssl_exec_path_win32() {
|
|
300
285
|
const opensslFolder2 = get_openssl_folder_win32();
|
|
301
|
-
return
|
|
286
|
+
return path2.join(opensslFolder2, "openssl.exe");
|
|
302
287
|
}
|
|
303
288
|
async function check_openssl_win32() {
|
|
304
289
|
const opensslExecPath2 = get_openssl_exec_path_win32();
|
|
@@ -338,7 +323,7 @@ async function install_and_check_win32_openssl_version() {
|
|
|
338
323
|
}
|
|
339
324
|
async function download_openssl() {
|
|
340
325
|
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";
|
|
341
|
-
const outputFilename =
|
|
326
|
+
const outputFilename = path2.join(downloadFolder, path2.basename(url2));
|
|
342
327
|
warningLog(`downloading ${chalk3.yellow(url2)} to ${outputFilename}`);
|
|
343
328
|
if (fs2.existsSync(outputFilename)) {
|
|
344
329
|
return { downloadedFile: outputFilename };
|
|
@@ -403,7 +388,7 @@ async function install_and_check_win32_openssl_version() {
|
|
|
403
388
|
if (err) {
|
|
404
389
|
return reject(err);
|
|
405
390
|
}
|
|
406
|
-
const file =
|
|
391
|
+
const file = path2.join(opensslFolder2, entry.fileName);
|
|
407
392
|
if (doDebug2) {
|
|
408
393
|
warningLog(" unzipping :", file);
|
|
409
394
|
}
|
|
@@ -574,7 +559,7 @@ async function execute_openssl(cmd, options) {
|
|
|
574
559
|
// packages/node-opcua-pki/lib/toolbox/with_openssl/toolbox.ts
|
|
575
560
|
import assert4 from "assert";
|
|
576
561
|
import fs4 from "fs";
|
|
577
|
-
import
|
|
562
|
+
import path3 from "path";
|
|
578
563
|
function openssl_require2DigitYearInDate() {
|
|
579
564
|
if (!g_config.opensslVersion) {
|
|
580
565
|
throw new Error(
|
|
@@ -587,26 +572,20 @@ g_config.opensslVersion = "";
|
|
|
587
572
|
var _counter = 0;
|
|
588
573
|
function generateStaticConfig(configPath, options) {
|
|
589
574
|
const prePath = options?.cwd || "";
|
|
590
|
-
const originalFilename = !
|
|
575
|
+
const originalFilename = !path3.isAbsolute(configPath) ? path3.join(prePath, configPath) : configPath;
|
|
591
576
|
let staticConfig = fs4.readFileSync(originalFilename, { encoding: "utf8" });
|
|
592
577
|
for (const envVar of getEnvironmentVarNames()) {
|
|
593
578
|
staticConfig = staticConfig.replace(new RegExp(envVar.pattern, "gi"), getEnv(envVar.key));
|
|
594
579
|
}
|
|
595
580
|
const staticConfigPath = `${configPath}.${process.pid}-${_counter++}.tmp`;
|
|
596
|
-
const temporaryConfigPath = !
|
|
581
|
+
const temporaryConfigPath = !path3.isAbsolute(configPath) ? path3.join(prePath, staticConfigPath) : staticConfigPath;
|
|
597
582
|
fs4.writeFileSync(temporaryConfigPath, staticConfig);
|
|
598
583
|
if (options?.cwd) {
|
|
599
|
-
return
|
|
584
|
+
return path3.relative(options.cwd, temporaryConfigPath);
|
|
600
585
|
} else {
|
|
601
586
|
return temporaryConfigPath;
|
|
602
587
|
}
|
|
603
588
|
}
|
|
604
|
-
var q = quote;
|
|
605
|
-
var n2 = makePath;
|
|
606
|
-
async function getPublicKeyFromPrivateKey(privateKeyFilename, publicKeyFilename) {
|
|
607
|
-
assert4(fs4.existsSync(privateKeyFilename));
|
|
608
|
-
await execute_openssl(`rsa -pubout -in ${q(n2(privateKeyFilename))} -out ${q(n2(publicKeyFilename))}`, {});
|
|
609
|
-
}
|
|
610
589
|
function x509Date(date) {
|
|
611
590
|
date = date || /* @__PURE__ */ new Date();
|
|
612
591
|
const Y = date.getUTCFullYear();
|
|
@@ -624,45 +603,6 @@ function x509Date(date) {
|
|
|
624
603
|
return `${w(Y, 4) + w(M, 2) + w(D, 2) + w(h, 2) + w(m, 2) + w(s, 2)}Z`;
|
|
625
604
|
}
|
|
626
605
|
}
|
|
627
|
-
async function dumpCertificate(certificate) {
|
|
628
|
-
assert4(fs4.existsSync(certificate));
|
|
629
|
-
return await execute_openssl(`x509 -in ${q(n2(certificate))} -text -noout`, {});
|
|
630
|
-
}
|
|
631
|
-
async function toDer(certificatePem) {
|
|
632
|
-
assert4(fs4.existsSync(certificatePem));
|
|
633
|
-
const certificateDer = certificatePem.replace(".pem", ".der");
|
|
634
|
-
return await execute_openssl(`x509 -outform der -in ${certificatePem} -out ${certificateDer}`, {});
|
|
635
|
-
}
|
|
636
|
-
async function fingerprint(certificatePem) {
|
|
637
|
-
assert4(fs4.existsSync(certificatePem));
|
|
638
|
-
return await execute_openssl(`x509 -fingerprint -noout -in ${certificatePem}`, {});
|
|
639
|
-
}
|
|
640
|
-
|
|
641
|
-
// packages/node-opcua-pki/lib/toolbox/with_openssl/create_certificate_signing_request.ts
|
|
642
|
-
var q2 = quote;
|
|
643
|
-
var n3 = makePath;
|
|
644
|
-
async function createCertificateSigningRequestWithOpenSSL(certificateSigningRequestFilename, params) {
|
|
645
|
-
assert5(params);
|
|
646
|
-
assert5(params.rootDir);
|
|
647
|
-
assert5(params.configFile);
|
|
648
|
-
assert5(params.privateKey);
|
|
649
|
-
assert5(typeof params.privateKey === "string");
|
|
650
|
-
assert5(fs5.existsSync(params.configFile), `config file must exist ${params.configFile}`);
|
|
651
|
-
assert5(fs5.existsSync(params.privateKey), `Private key must exist${params.privateKey}`);
|
|
652
|
-
assert5(fs5.existsSync(params.rootDir), "RootDir key must exist");
|
|
653
|
-
assert5(typeof certificateSigningRequestFilename === "string");
|
|
654
|
-
processAltNames(params);
|
|
655
|
-
const configFile = generateStaticConfig(params.configFile, { cwd: params.rootDir });
|
|
656
|
-
const options = { cwd: params.rootDir, openssl_conf: path5.relative(params.rootDir, configFile) };
|
|
657
|
-
const configOption = ` -config ${q2(n3(configFile))}`;
|
|
658
|
-
const subject = params.subject ? new Subject(params.subject).toString() : void 0;
|
|
659
|
-
const subjectOptions = subject ? ` -subj "${subject}"` : "";
|
|
660
|
-
displaySubtitle("- Creating a Certificate Signing Request with openssl");
|
|
661
|
-
await execute_openssl(
|
|
662
|
-
"req -new -sha256 -batch -text " + configOption + " -key " + q2(n3(params.privateKey)) + subjectOptions + " -out " + q2(n3(certificateSigningRequestFilename)),
|
|
663
|
-
options
|
|
664
|
-
);
|
|
665
|
-
}
|
|
666
606
|
|
|
667
607
|
// packages/node-opcua-pki/lib/ca/templates/ca_config_template.cnf.ts
|
|
668
608
|
var config = `#.........DO NOT MODIFY BY HAND .........................
|
|
@@ -800,45 +740,45 @@ var config2 = {
|
|
|
800
740
|
forceCA: false,
|
|
801
741
|
pkiDir: "INVALID"
|
|
802
742
|
};
|
|
803
|
-
var
|
|
804
|
-
var
|
|
743
|
+
var n2 = makePath;
|
|
744
|
+
var q = quote;
|
|
805
745
|
function octetStringToIpAddress(a) {
|
|
806
746
|
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();
|
|
807
747
|
}
|
|
808
748
|
assert6(octetStringToIpAddress("c07b9179") === "192.123.145.121");
|
|
809
749
|
async function construct_CertificateAuthority(certificateAuthority) {
|
|
810
750
|
const subject = certificateAuthority.subject;
|
|
811
|
-
const caRootDir =
|
|
751
|
+
const caRootDir = path5.resolve(certificateAuthority.rootDir);
|
|
812
752
|
async function make_folders() {
|
|
813
753
|
mkdirRecursiveSync(caRootDir);
|
|
814
|
-
mkdirRecursiveSync(
|
|
815
|
-
mkdirRecursiveSync(
|
|
816
|
-
mkdirRecursiveSync(
|
|
817
|
-
mkdirRecursiveSync(
|
|
818
|
-
mkdirRecursiveSync(
|
|
754
|
+
mkdirRecursiveSync(path5.join(caRootDir, "private"));
|
|
755
|
+
mkdirRecursiveSync(path5.join(caRootDir, "public"));
|
|
756
|
+
mkdirRecursiveSync(path5.join(caRootDir, "certs"));
|
|
757
|
+
mkdirRecursiveSync(path5.join(caRootDir, "crl"));
|
|
758
|
+
mkdirRecursiveSync(path5.join(caRootDir, "conf"));
|
|
819
759
|
}
|
|
820
760
|
await make_folders();
|
|
821
761
|
async function construct_default_files() {
|
|
822
|
-
const serial =
|
|
762
|
+
const serial = path5.join(caRootDir, "serial");
|
|
823
763
|
if (!fs6.existsSync(serial)) {
|
|
824
764
|
await fs6.promises.writeFile(serial, "1000");
|
|
825
765
|
}
|
|
826
|
-
const crlNumber =
|
|
766
|
+
const crlNumber = path5.join(caRootDir, "crlnumber");
|
|
827
767
|
if (!fs6.existsSync(crlNumber)) {
|
|
828
768
|
await fs6.promises.writeFile(crlNumber, "1000");
|
|
829
769
|
}
|
|
830
|
-
const indexFile =
|
|
770
|
+
const indexFile = path5.join(caRootDir, "index.txt");
|
|
831
771
|
if (!fs6.existsSync(indexFile)) {
|
|
832
772
|
await fs6.promises.writeFile(indexFile, "");
|
|
833
773
|
}
|
|
834
774
|
}
|
|
835
775
|
await construct_default_files();
|
|
836
|
-
if (fs6.existsSync(
|
|
776
|
+
if (fs6.existsSync(path5.join(caRootDir, "private/cakey.pem")) && !config2.forceCA) {
|
|
837
777
|
debugLog("CA private key already exists ... skipping");
|
|
838
778
|
return;
|
|
839
779
|
}
|
|
840
780
|
displayTitle("Create Certificate Authority (CA)");
|
|
841
|
-
const indexFileAttr =
|
|
781
|
+
const indexFileAttr = path5.join(caRootDir, "index.txt.attr");
|
|
842
782
|
if (!fs6.existsSync(indexFileAttr)) {
|
|
843
783
|
await fs6.promises.writeFile(indexFileAttr, "unique_subject = no");
|
|
844
784
|
}
|
|
@@ -852,20 +792,20 @@ async function construct_CertificateAuthority(certificateAuthority) {
|
|
|
852
792
|
processAltNames({});
|
|
853
793
|
const options = { cwd: caRootDir };
|
|
854
794
|
const configFile = generateStaticConfig("conf/caconfig.cnf", options);
|
|
855
|
-
const configOption = ` -config ${
|
|
795
|
+
const configOption = ` -config ${q(n2(configFile))}`;
|
|
856
796
|
const keySize = certificateAuthority.keySize;
|
|
857
|
-
const privateKeyFilename =
|
|
858
|
-
const csrFilename =
|
|
797
|
+
const privateKeyFilename = path5.join(caRootDir, "private/cakey.pem");
|
|
798
|
+
const csrFilename = path5.join(caRootDir, "private/cakey.csr");
|
|
859
799
|
displayTitle(`Generate the CA private Key - ${keySize}`);
|
|
860
800
|
await generatePrivateKeyFile(privateKeyFilename, keySize);
|
|
861
801
|
displayTitle("Generate a certificate request for the CA key");
|
|
862
802
|
await execute_openssl(
|
|
863
|
-
"req -new -sha256 -text -extensions v3_ca" + configOption + " -key " +
|
|
803
|
+
"req -new -sha256 -text -extensions v3_ca" + configOption + " -key " + q(n2(privateKeyFilename)) + " -out " + q(n2(csrFilename)) + " " + subjectOpt,
|
|
864
804
|
options
|
|
865
805
|
);
|
|
866
806
|
displayTitle("Generate CA Certificate (self-signed)");
|
|
867
807
|
await execute_openssl(
|
|
868
|
-
" x509 -sha256 -req -days 3650 -text -extensions v3_ca -extfile " +
|
|
808
|
+
" x509 -sha256 -req -days 3650 -text -extensions v3_ca -extfile " + q(n2(configFile)) + " -in private/cakey.csr -signkey " + q(n2(privateKeyFilename)) + " -out public/cacert.pem",
|
|
869
809
|
options
|
|
870
810
|
);
|
|
871
811
|
displaySubtitle("generate initial CRL (Certificate Revocation List)");
|
|
@@ -877,11 +817,14 @@ async function regenerateCrl(revocationList, configOption, options) {
|
|
|
877
817
|
await execute_openssl(`ca -gencrl ${configOption} -out crl/revocation_list.crl`, options);
|
|
878
818
|
await execute_openssl("crl -in crl/revocation_list.crl -out crl/revocation_list.der -outform der", options);
|
|
879
819
|
displaySubtitle("Display (Certificate Revocation List)");
|
|
880
|
-
await execute_openssl(`crl -in ${
|
|
820
|
+
await execute_openssl(`crl -in ${q(n2(revocationList))} -text -noout`, options);
|
|
881
821
|
}
|
|
882
822
|
var CertificateAuthority = class {
|
|
823
|
+
/** RSA key size used when generating the CA private key. */
|
|
883
824
|
keySize;
|
|
825
|
+
/** Root filesystem path of the CA directory structure. */
|
|
884
826
|
location;
|
|
827
|
+
/** X.500 subject of the CA certificate. */
|
|
885
828
|
subject;
|
|
886
829
|
constructor(options) {
|
|
887
830
|
assert6(Object.prototype.hasOwnProperty.call(options, "location"));
|
|
@@ -890,33 +833,54 @@ var CertificateAuthority = class {
|
|
|
890
833
|
this.keySize = options.keySize || 2048;
|
|
891
834
|
this.subject = new Subject2(options.subject || defaultSubject);
|
|
892
835
|
}
|
|
836
|
+
/** Absolute path to the CA root directory (alias for {@link location}). */
|
|
893
837
|
get rootDir() {
|
|
894
838
|
return this.location;
|
|
895
839
|
}
|
|
840
|
+
/** Path to the OpenSSL configuration file (`conf/caconfig.cnf`). */
|
|
896
841
|
get configFile() {
|
|
897
|
-
return
|
|
842
|
+
return path5.normalize(path5.join(this.rootDir, "./conf/caconfig.cnf"));
|
|
898
843
|
}
|
|
844
|
+
/** Path to the CA certificate in PEM format (`public/cacert.pem`). */
|
|
899
845
|
get caCertificate() {
|
|
900
846
|
return makePath(this.rootDir, "./public/cacert.pem");
|
|
901
847
|
}
|
|
902
848
|
/**
|
|
903
|
-
*
|
|
849
|
+
* Path to the current Certificate Revocation List in DER format.
|
|
850
|
+
* (`crl/revocation_list.der`)
|
|
904
851
|
*/
|
|
905
852
|
get revocationListDER() {
|
|
906
853
|
return makePath(this.rootDir, "./crl/revocation_list.der");
|
|
907
854
|
}
|
|
908
855
|
/**
|
|
909
|
-
*
|
|
856
|
+
* Path to the current Certificate Revocation List in PEM format.
|
|
857
|
+
* (`crl/revocation_list.crl`)
|
|
910
858
|
*/
|
|
911
859
|
get revocationList() {
|
|
912
860
|
return makePath(this.rootDir, "./crl/revocation_list.crl");
|
|
913
861
|
}
|
|
862
|
+
/**
|
|
863
|
+
* Path to the concatenated CA certificate + CRL file.
|
|
864
|
+
* Used by OpenSSL for CRL-based verification.
|
|
865
|
+
*/
|
|
914
866
|
get caCertificateWithCrl() {
|
|
915
867
|
return makePath(this.rootDir, "./public/cacertificate_with_crl.pem");
|
|
916
868
|
}
|
|
869
|
+
/**
|
|
870
|
+
* Initialize the CA directory structure, generate the CA
|
|
871
|
+
* private key and self-signed certificate if they do not
|
|
872
|
+
* already exist.
|
|
873
|
+
*/
|
|
917
874
|
async initialize() {
|
|
918
875
|
await construct_CertificateAuthority(this);
|
|
919
876
|
}
|
|
877
|
+
/**
|
|
878
|
+
* Rebuild the combined CA certificate + CRL file.
|
|
879
|
+
*
|
|
880
|
+
* This concatenates the CA certificate with the current
|
|
881
|
+
* revocation list so that OpenSSL can verify certificates
|
|
882
|
+
* with CRL checking enabled.
|
|
883
|
+
*/
|
|
920
884
|
async constructCACertificateWithCRL() {
|
|
921
885
|
const cacertWithCRL = this.caCertificateWithCrl;
|
|
922
886
|
if (fs6.existsSync(this.revocationList)) {
|
|
@@ -928,6 +892,12 @@ var CertificateAuthority = class {
|
|
|
928
892
|
await fs6.promises.writeFile(cacertWithCRL, fs6.readFileSync(this.caCertificate));
|
|
929
893
|
}
|
|
930
894
|
}
|
|
895
|
+
/**
|
|
896
|
+
* Append the CA certificate to a signed certificate file,
|
|
897
|
+
* creating a PEM certificate chain.
|
|
898
|
+
*
|
|
899
|
+
* @param certificate - path to the certificate file to extend
|
|
900
|
+
*/
|
|
931
901
|
async constructCertificateChain(certificate) {
|
|
932
902
|
assert6(fs6.existsSync(certificate));
|
|
933
903
|
assert6(fs6.existsSync(this.caCertificate));
|
|
@@ -938,6 +908,13 @@ var CertificateAuthority = class {
|
|
|
938
908
|
// + fs.readFileSync(this.revocationList)
|
|
939
909
|
);
|
|
940
910
|
}
|
|
911
|
+
/**
|
|
912
|
+
* Create a self-signed certificate using OpenSSL.
|
|
913
|
+
*
|
|
914
|
+
* @param certificateFile - output path for the signed certificate
|
|
915
|
+
* @param privateKey - path to the private key file
|
|
916
|
+
* @param params - certificate parameters (subject, validity, SANs)
|
|
917
|
+
*/
|
|
941
918
|
async createSelfSignedCertificate(certificateFile, privateKey, params) {
|
|
942
919
|
assert6(typeof privateKey === "string");
|
|
943
920
|
assert6(fs6.existsSync(privateKey));
|
|
@@ -959,28 +936,27 @@ var CertificateAuthority = class {
|
|
|
959
936
|
const subjectOptions = subject && subject.length > 1 ? ` -subj ${subject} ` : "";
|
|
960
937
|
displaySubtitle("- the certificate signing request");
|
|
961
938
|
await execute_openssl(
|
|
962
|
-
"req -new -sha256 -text " + configOption + subjectOptions + " -batch -key " +
|
|
939
|
+
"req -new -sha256 -text " + configOption + subjectOptions + " -batch -key " + q(n2(privateKey)) + " -out " + q(n2(csrFile)),
|
|
963
940
|
options
|
|
964
941
|
);
|
|
965
942
|
displaySubtitle("- creating the self-signed certificate");
|
|
966
943
|
await execute_openssl(
|
|
967
|
-
"ca -selfsign -keyfile " +
|
|
944
|
+
"ca -selfsign -keyfile " + q(n2(privateKey)) + " -startdate " + x509Date(params.startDate) + " -enddate " + x509Date(params.endDate) + " -batch -out " + q(n2(certificateFile)) + " -in " + q(n2(csrFile)),
|
|
968
945
|
options
|
|
969
946
|
);
|
|
970
947
|
displaySubtitle("- dump the certificate for a check");
|
|
971
|
-
await execute_openssl(`x509 -in ${
|
|
948
|
+
await execute_openssl(`x509 -in ${q(n2(certificateFile))} -dates -fingerprint -purpose -noout`, {});
|
|
972
949
|
displaySubtitle("- verify self-signed certificate");
|
|
973
|
-
await execute_openssl_no_failure(`verify -verbose -CAfile ${
|
|
950
|
+
await execute_openssl_no_failure(`verify -verbose -CAfile ${q(n2(certificateFile))} ${q(n2(certificateFile))}`, options);
|
|
974
951
|
await fs6.promises.unlink(csrFile);
|
|
975
952
|
}
|
|
976
953
|
/**
|
|
977
|
-
*
|
|
954
|
+
* Revoke a certificate and regenerate the CRL.
|
|
978
955
|
*
|
|
979
|
-
* @
|
|
980
|
-
* @param
|
|
981
|
-
* @param params
|
|
982
|
-
*
|
|
983
|
-
* @async
|
|
956
|
+
* @param certificate - path to the certificate file to revoke
|
|
957
|
+
* @param params - revocation parameters
|
|
958
|
+
* @param params.reason - CRL reason code
|
|
959
|
+
* (default `"keyCompromise"`)
|
|
984
960
|
*/
|
|
985
961
|
async revokeCertificate(certificate, params) {
|
|
986
962
|
const crlReasons = [
|
|
@@ -999,33 +975,36 @@ var CertificateAuthority = class {
|
|
|
999
975
|
openssl_conf: makePath(configFile)
|
|
1000
976
|
};
|
|
1001
977
|
setEnv("ALTNAME", "");
|
|
1002
|
-
const randomFile =
|
|
978
|
+
const randomFile = path5.join(this.rootDir, "random.rnd");
|
|
1003
979
|
setEnv("RANDFILE", randomFile);
|
|
1004
|
-
const configOption = ` -config ${
|
|
980
|
+
const configOption = ` -config ${q(n2(configFile))}`;
|
|
1005
981
|
const reason = params.reason || "keyCompromise";
|
|
1006
982
|
assert6(crlReasons.indexOf(reason) >= 0);
|
|
1007
983
|
displayTitle(`Revoking certificate ${certificate}`);
|
|
1008
984
|
displaySubtitle("Revoke certificate");
|
|
1009
|
-
await execute_openssl_no_failure(`ca -verbose ${configOption} -revoke ${
|
|
985
|
+
await execute_openssl_no_failure(`ca -verbose ${configOption} -revoke ${q(certificate)} -crl_reason ${reason}`, options);
|
|
1010
986
|
await regenerateCrl(this.revocationList, configOption, options);
|
|
1011
987
|
displaySubtitle("Verify that certificate is revoked");
|
|
1012
988
|
await execute_openssl_no_failure(
|
|
1013
|
-
"verify -verbose -CRLfile " +
|
|
989
|
+
"verify -verbose -CRLfile " + q(n2(this.revocationList)) + " -CAfile " + q(n2(this.caCertificate)) + " -crl_check " + q(n2(certificate)),
|
|
1014
990
|
options
|
|
1015
991
|
);
|
|
1016
992
|
displaySubtitle("Produce CRL in DER form ");
|
|
1017
|
-
await execute_openssl(`crl -in ${
|
|
993
|
+
await execute_openssl(`crl -in ${q(n2(this.revocationList))} -out crl/revocation_list.der -outform der`, options);
|
|
1018
994
|
displaySubtitle("Produce CRL in PEM form ");
|
|
1019
|
-
await execute_openssl(`crl -in ${
|
|
995
|
+
await execute_openssl(`crl -in ${q(n2(this.revocationList))} -out crl/revocation_list.pem -outform pem -text `, options);
|
|
1020
996
|
}
|
|
1021
997
|
/**
|
|
998
|
+
* Sign a Certificate Signing Request (CSR) with this CA.
|
|
999
|
+
*
|
|
1000
|
+
* The signed certificate is written to `certificate`, and the
|
|
1001
|
+
* CA certificate chain plus CRL are appended to form a
|
|
1002
|
+
* complete certificate chain.
|
|
1022
1003
|
*
|
|
1023
|
-
* @param certificate
|
|
1024
|
-
* @param certificateSigningRequestFilename
|
|
1025
|
-
* @param
|
|
1026
|
-
* @
|
|
1027
|
-
* @param params.startDate - startDate of the certificate
|
|
1028
|
-
* @param params.validity - number of day of validity of the certificate
|
|
1004
|
+
* @param certificate - output path for the signed certificate
|
|
1005
|
+
* @param certificateSigningRequestFilename - path to the CSR
|
|
1006
|
+
* @param params1 - signing parameters (validity, dates, SANs)
|
|
1007
|
+
* @returns the path to the signed certificate
|
|
1029
1008
|
*/
|
|
1030
1009
|
async signCertificateRequest(certificate, certificateSigningRequestFilename, params1) {
|
|
1031
1010
|
await ensure_openssl_installed();
|
|
@@ -1043,12 +1022,12 @@ var CertificateAuthority = class {
|
|
|
1043
1022
|
if (typeof applicationUri !== "string") {
|
|
1044
1023
|
throw new Error("Cannot find applicationUri in CSR");
|
|
1045
1024
|
}
|
|
1046
|
-
const
|
|
1025
|
+
const dns = csrInfo.extensionRequest.subjectAltName.dNSName || [];
|
|
1047
1026
|
let ip = csrInfo.extensionRequest.subjectAltName.iPAddress || [];
|
|
1048
1027
|
ip = ip.map(octetStringToIpAddress);
|
|
1049
1028
|
const params = {
|
|
1050
1029
|
applicationUri,
|
|
1051
|
-
dns
|
|
1030
|
+
dns,
|
|
1052
1031
|
ip
|
|
1053
1032
|
};
|
|
1054
1033
|
processAltNames(params);
|
|
@@ -1056,11 +1035,11 @@ var CertificateAuthority = class {
|
|
|
1056
1035
|
displaySubtitle("- then we ask the authority to sign the certificate signing request");
|
|
1057
1036
|
const configOption = ` -config ${configFile}`;
|
|
1058
1037
|
await execute_openssl(
|
|
1059
|
-
"ca " + configOption + " -startdate " + x509Date(params1.startDate) + " -enddate " + x509Date(params1.endDate) + " -batch -out " +
|
|
1038
|
+
"ca " + configOption + " -startdate " + x509Date(params1.startDate) + " -enddate " + x509Date(params1.endDate) + " -batch -out " + q(n2(certificate)) + " -in " + q(n2(certificateSigningRequestFilename)),
|
|
1060
1039
|
options
|
|
1061
1040
|
);
|
|
1062
1041
|
displaySubtitle("- dump the certificate for a check");
|
|
1063
|
-
await execute_openssl(`x509 -in ${
|
|
1042
|
+
await execute_openssl(`x509 -in ${q(n2(certificate))} -dates -fingerprint -purpose -noout`, options);
|
|
1064
1043
|
displaySubtitle("- construct CA certificate with CRL");
|
|
1065
1044
|
await this.constructCACertificateWithCRL();
|
|
1066
1045
|
displaySubtitle("- construct certificate chain");
|
|
@@ -1069,6 +1048,11 @@ var CertificateAuthority = class {
|
|
|
1069
1048
|
await this.verifyCertificate(certificate);
|
|
1070
1049
|
return certificate;
|
|
1071
1050
|
}
|
|
1051
|
+
/**
|
|
1052
|
+
* Verify a certificate against this CA.
|
|
1053
|
+
*
|
|
1054
|
+
* @param certificate - path to the certificate file to verify
|
|
1055
|
+
*/
|
|
1072
1056
|
async verifyCertificate(certificate) {
|
|
1073
1057
|
const isImplemented = false;
|
|
1074
1058
|
if (isImplemented) {
|
|
@@ -1078,97 +1062,16 @@ var CertificateAuthority = class {
|
|
|
1078
1062
|
const _configOption = ` -config ${configFile}`;
|
|
1079
1063
|
_configOption;
|
|
1080
1064
|
await execute_openssl_no_failure(
|
|
1081
|
-
`verify -verbose -CAfile ${
|
|
1065
|
+
`verify -verbose -CAfile ${q(n2(this.caCertificateWithCrl))} ${q(n2(certificate))}`,
|
|
1082
1066
|
options
|
|
1083
1067
|
);
|
|
1084
1068
|
}
|
|
1085
1069
|
}
|
|
1086
1070
|
};
|
|
1087
1071
|
|
|
1088
|
-
// packages/node-opcua-pki/lib/ca/crypto_create_CA.ts
|
|
1089
|
-
import assert10 from "assert";
|
|
1090
|
-
import fs10 from "fs";
|
|
1091
|
-
import { createRequire } from "module";
|
|
1092
|
-
import os4 from "os";
|
|
1093
|
-
import path8 from "path";
|
|
1094
|
-
import chalk7 from "chalk";
|
|
1095
|
-
import { CertificatePurpose as CertificatePurpose2, generatePrivateKeyFile as generatePrivateKeyFile3, Subject as Subject5 } from "node-opcua-crypto";
|
|
1096
|
-
|
|
1097
|
-
// packages/node-opcua-pki/lib/misc/applicationurn.ts
|
|
1098
|
-
import assert7 from "assert";
|
|
1099
|
-
import { createHash } from "crypto";
|
|
1100
|
-
function makeApplicationUrn(hostname, suffix) {
|
|
1101
|
-
let hostnameHash = hostname;
|
|
1102
|
-
if (hostnameHash.length + 7 + suffix.length >= 64) {
|
|
1103
|
-
hostnameHash = createHash("md5").update(hostname).digest("hex").substring(0, 16);
|
|
1104
|
-
}
|
|
1105
|
-
const applicationUrn = `urn:${hostnameHash}:${suffix}`;
|
|
1106
|
-
assert7(applicationUrn.length <= 64);
|
|
1107
|
-
return applicationUrn;
|
|
1108
|
-
}
|
|
1109
|
-
|
|
1110
|
-
// packages/node-opcua-pki/lib/misc/hostname.ts
|
|
1111
|
-
import dns from "dns";
|
|
1112
|
-
import os3 from "os";
|
|
1113
|
-
import { promisify } from "util";
|
|
1114
|
-
function trim(str, length) {
|
|
1115
|
-
if (!length) {
|
|
1116
|
-
return str;
|
|
1117
|
-
}
|
|
1118
|
-
return str.substring(0, Math.min(str.length, length));
|
|
1119
|
-
}
|
|
1120
|
-
function fqdn(callback) {
|
|
1121
|
-
const uqdn = os3.hostname();
|
|
1122
|
-
dns.lookup(uqdn, { hints: dns.ADDRCONFIG }, (err1, ip) => {
|
|
1123
|
-
if (err1) {
|
|
1124
|
-
return callback(err1);
|
|
1125
|
-
}
|
|
1126
|
-
dns.lookupService(ip, 0, (err2, _fqdn) => {
|
|
1127
|
-
if (err2) {
|
|
1128
|
-
return callback(err2);
|
|
1129
|
-
}
|
|
1130
|
-
_fqdn = _fqdn.replace(".localdomain", "");
|
|
1131
|
-
callback(null, _fqdn);
|
|
1132
|
-
});
|
|
1133
|
-
});
|
|
1134
|
-
}
|
|
1135
|
-
var _fullyQualifiedDomainNameInCache;
|
|
1136
|
-
async function extractFullyQualifiedDomainName() {
|
|
1137
|
-
if (_fullyQualifiedDomainNameInCache) {
|
|
1138
|
-
return _fullyQualifiedDomainNameInCache;
|
|
1139
|
-
}
|
|
1140
|
-
if (process.platform === "win32") {
|
|
1141
|
-
const env = process.env;
|
|
1142
|
-
_fullyQualifiedDomainNameInCache = env.COMPUTERNAME + (env.USERDNSDOMAIN && env.USERDNSDOMAIN?.length > 0 ? `.${env.USERDNSDOMAIN}` : "");
|
|
1143
|
-
} else {
|
|
1144
|
-
try {
|
|
1145
|
-
_fullyQualifiedDomainNameInCache = await promisify(fqdn)();
|
|
1146
|
-
if (_fullyQualifiedDomainNameInCache === "localhost") {
|
|
1147
|
-
throw new Error("localhost not expected");
|
|
1148
|
-
}
|
|
1149
|
-
if (/sethostname/.test(_fullyQualifiedDomainNameInCache)) {
|
|
1150
|
-
throw new Error("Detecting fqdn on windows !!!");
|
|
1151
|
-
}
|
|
1152
|
-
} catch (_err) {
|
|
1153
|
-
_fullyQualifiedDomainNameInCache = os3.hostname();
|
|
1154
|
-
}
|
|
1155
|
-
}
|
|
1156
|
-
return _fullyQualifiedDomainNameInCache;
|
|
1157
|
-
}
|
|
1158
|
-
async function prepareFQDN() {
|
|
1159
|
-
_fullyQualifiedDomainNameInCache = await extractFullyQualifiedDomainName();
|
|
1160
|
-
}
|
|
1161
|
-
function getFullyQualifiedDomainName(optional_max_length) {
|
|
1162
|
-
if (!_fullyQualifiedDomainNameInCache) {
|
|
1163
|
-
throw new Error("FullyQualifiedDomainName computation is not completed yet");
|
|
1164
|
-
}
|
|
1165
|
-
return _fullyQualifiedDomainNameInCache ? trim(_fullyQualifiedDomainNameInCache, optional_max_length) : "%FQDN%";
|
|
1166
|
-
}
|
|
1167
|
-
prepareFQDN();
|
|
1168
|
-
|
|
1169
1072
|
// packages/node-opcua-pki/lib/pki/certificate_manager.ts
|
|
1170
1073
|
import fs9 from "fs";
|
|
1171
|
-
import
|
|
1074
|
+
import path6 from "path";
|
|
1172
1075
|
import { withLock } from "@ster5/global-mutex";
|
|
1173
1076
|
import chalk6 from "chalk";
|
|
1174
1077
|
import chokidar from "chokidar";
|
|
@@ -1187,18 +1090,18 @@ import {
|
|
|
1187
1090
|
} from "node-opcua-crypto";
|
|
1188
1091
|
|
|
1189
1092
|
// packages/node-opcua-pki/lib/toolbox/without_openssl/create_certificate_signing_request.ts
|
|
1190
|
-
import
|
|
1093
|
+
import assert7 from "assert";
|
|
1191
1094
|
import fs7 from "fs";
|
|
1192
1095
|
import { createCertificateSigningRequest, pemToPrivateKey, Subject as Subject3 } from "node-opcua-crypto";
|
|
1193
1096
|
async function createCertificateSigningRequestAsync(certificateSigningRequestFilename, params) {
|
|
1194
|
-
|
|
1195
|
-
|
|
1196
|
-
|
|
1197
|
-
|
|
1198
|
-
|
|
1199
|
-
|
|
1200
|
-
|
|
1201
|
-
|
|
1097
|
+
assert7(params);
|
|
1098
|
+
assert7(params.rootDir);
|
|
1099
|
+
assert7(params.configFile);
|
|
1100
|
+
assert7(params.privateKey);
|
|
1101
|
+
assert7(typeof params.privateKey === "string");
|
|
1102
|
+
assert7(fs7.existsSync(params.privateKey), `Private key must exist${params.privateKey}`);
|
|
1103
|
+
assert7(fs7.existsSync(params.rootDir), "RootDir key must exist");
|
|
1104
|
+
assert7(typeof certificateSigningRequestFilename === "string");
|
|
1202
1105
|
const subject = params.subject ? new Subject3(params.subject).toString() : void 0;
|
|
1203
1106
|
displaySubtitle("- Creating a Certificate Signing Request with subtile");
|
|
1204
1107
|
const privateKeyPem = await fs7.promises.readFile(params.privateKey, "utf-8");
|
|
@@ -1217,7 +1120,7 @@ async function createCertificateSigningRequestAsync(certificateSigningRequestFil
|
|
|
1217
1120
|
}
|
|
1218
1121
|
|
|
1219
1122
|
// packages/node-opcua-pki/lib/toolbox/without_openssl/create_self_signed_certificate.ts
|
|
1220
|
-
import
|
|
1123
|
+
import assert8 from "assert";
|
|
1221
1124
|
import fs8 from "fs";
|
|
1222
1125
|
import {
|
|
1223
1126
|
CertificatePurpose,
|
|
@@ -1227,17 +1130,17 @@ import {
|
|
|
1227
1130
|
} from "node-opcua-crypto";
|
|
1228
1131
|
async function createSelfSignedCertificateAsync(certificate, params) {
|
|
1229
1132
|
params.purpose = params.purpose || CertificatePurpose.ForApplication;
|
|
1230
|
-
|
|
1231
|
-
|
|
1232
|
-
|
|
1233
|
-
|
|
1133
|
+
assert8(params.purpose, "Please provide a Certificate Purpose");
|
|
1134
|
+
assert8(fs8.existsSync(params.configFile));
|
|
1135
|
+
assert8(fs8.existsSync(params.rootDir));
|
|
1136
|
+
assert8(fs8.existsSync(params.privateKey));
|
|
1234
1137
|
if (!params.subject) {
|
|
1235
1138
|
throw Error("Missing subject");
|
|
1236
1139
|
}
|
|
1237
|
-
|
|
1238
|
-
|
|
1140
|
+
assert8(typeof params.applicationUri === "string");
|
|
1141
|
+
assert8(Array.isArray(params.dns));
|
|
1239
1142
|
adjustDate(params);
|
|
1240
|
-
|
|
1143
|
+
assert8(Object.prototype.hasOwnProperty.call(params, "validity"));
|
|
1241
1144
|
let subject = new Subject4(params.subject);
|
|
1242
1145
|
subject = subject.toString();
|
|
1243
1146
|
const purpose = params.purpose;
|
|
@@ -1294,20 +1197,21 @@ var VerificationStatus = /* @__PURE__ */ ((VerificationStatus2) => {
|
|
|
1294
1197
|
return VerificationStatus2;
|
|
1295
1198
|
})(VerificationStatus || {});
|
|
1296
1199
|
function makeFingerprint(certificate) {
|
|
1297
|
-
|
|
1200
|
+
const chain = split_der(certificate);
|
|
1201
|
+
return makeSHA1Thumbprint(chain[0]).toString("hex");
|
|
1298
1202
|
}
|
|
1299
1203
|
function short(stringToShorten) {
|
|
1300
1204
|
return stringToShorten.substring(0, 10);
|
|
1301
1205
|
}
|
|
1302
1206
|
var forbiddenChars = /[\x00-\x1F<>:"/\\|?*]/g;
|
|
1303
1207
|
function buildIdealCertificateName(certificate) {
|
|
1304
|
-
const
|
|
1208
|
+
const fingerprint = makeFingerprint(certificate);
|
|
1305
1209
|
try {
|
|
1306
1210
|
const commonName = exploreCertificate(certificate).tbsCertificate.subject.commonName || "";
|
|
1307
1211
|
const sanitizedCommonName = commonName.replace(forbiddenChars, "_");
|
|
1308
|
-
return `${sanitizedCommonName}[${
|
|
1212
|
+
return `${sanitizedCommonName}[${fingerprint}]`;
|
|
1309
1213
|
} catch (_err) {
|
|
1310
|
-
return `invalid_certificate_[${
|
|
1214
|
+
return `invalid_certificate_[${fingerprint}]`;
|
|
1311
1215
|
}
|
|
1312
1216
|
}
|
|
1313
1217
|
function findMatchingIssuerKey(entries, wantedIssuerKey) {
|
|
@@ -1357,8 +1261,79 @@ var CertificateManagerState = /* @__PURE__ */ ((CertificateManagerState2) => {
|
|
|
1357
1261
|
CertificateManagerState2[CertificateManagerState2["Disposed"] = 4] = "Disposed";
|
|
1358
1262
|
return CertificateManagerState2;
|
|
1359
1263
|
})(CertificateManagerState || {});
|
|
1360
|
-
var CertificateManager = class {
|
|
1264
|
+
var CertificateManager = class _CertificateManager {
|
|
1265
|
+
// ── Global instance registry ─────────────────────────────────
|
|
1266
|
+
// Tracks all initialized CertificateManager instances so their
|
|
1267
|
+
// file watchers can be closed automatically on process exit,
|
|
1268
|
+
// even if the consumer forgets to call dispose().
|
|
1269
|
+
static #activeInstances = /* @__PURE__ */ new Set();
|
|
1270
|
+
static #cleanupInstalled = false;
|
|
1271
|
+
static #installProcessCleanup() {
|
|
1272
|
+
if (_CertificateManager.#cleanupInstalled) return;
|
|
1273
|
+
_CertificateManager.#cleanupInstalled = true;
|
|
1274
|
+
const closeDanglingWatchers = () => {
|
|
1275
|
+
for (const cm of _CertificateManager.#activeInstances) {
|
|
1276
|
+
for (const w of cm.#watchers) {
|
|
1277
|
+
try {
|
|
1278
|
+
w.close();
|
|
1279
|
+
} catch {
|
|
1280
|
+
}
|
|
1281
|
+
}
|
|
1282
|
+
cm.#watchers.splice(0);
|
|
1283
|
+
cm.state = 4 /* Disposed */;
|
|
1284
|
+
}
|
|
1285
|
+
_CertificateManager.#activeInstances.clear();
|
|
1286
|
+
};
|
|
1287
|
+
process.on("beforeExit", closeDanglingWatchers);
|
|
1288
|
+
for (const signal of ["SIGINT", "SIGTERM"]) {
|
|
1289
|
+
process.once(signal, () => {
|
|
1290
|
+
closeDanglingWatchers();
|
|
1291
|
+
process.exit();
|
|
1292
|
+
});
|
|
1293
|
+
}
|
|
1294
|
+
}
|
|
1295
|
+
/**
|
|
1296
|
+
* Dispose **all** active CertificateManager instances,
|
|
1297
|
+
* closing their file watchers and freeing resources.
|
|
1298
|
+
*
|
|
1299
|
+
* This is mainly useful in test tear-down to ensure the
|
|
1300
|
+
* Node.js process can exit cleanly.
|
|
1301
|
+
*/
|
|
1302
|
+
static async disposeAll() {
|
|
1303
|
+
const instances = [..._CertificateManager.#activeInstances];
|
|
1304
|
+
await Promise.all(instances.map((cm) => cm.dispose()));
|
|
1305
|
+
}
|
|
1306
|
+
/**
|
|
1307
|
+
* Assert that all CertificateManager instances have been
|
|
1308
|
+
* properly disposed. Throws an Error listing the locations
|
|
1309
|
+
* of any leaked instances.
|
|
1310
|
+
*
|
|
1311
|
+
* Intended for use in test `afterAll()` / `afterEach()`
|
|
1312
|
+
* hooks to catch missing `dispose()` calls early.
|
|
1313
|
+
*
|
|
1314
|
+
* @example
|
|
1315
|
+
* ```ts
|
|
1316
|
+
* after(() => {
|
|
1317
|
+
* CertificateManager.checkAllDisposed();
|
|
1318
|
+
* });
|
|
1319
|
+
* ```
|
|
1320
|
+
*/
|
|
1321
|
+
static checkAllDisposed() {
|
|
1322
|
+
if (_CertificateManager.#activeInstances.size === 0) return;
|
|
1323
|
+
const locations = [..._CertificateManager.#activeInstances].map((cm) => cm.rootDir);
|
|
1324
|
+
throw new Error(
|
|
1325
|
+
`${_CertificateManager.#activeInstances.size} CertificateManager instance(s) not disposed:
|
|
1326
|
+
- ${locations.join("\n - ")}`
|
|
1327
|
+
);
|
|
1328
|
+
}
|
|
1329
|
+
// ─────────────────────────────────────────────────────────────
|
|
1330
|
+
/**
|
|
1331
|
+
* When `true` (the default), any certificate that is not
|
|
1332
|
+
* already in the trusted or rejected store is automatically
|
|
1333
|
+
* written to the rejected folder the first time it is seen.
|
|
1334
|
+
*/
|
|
1361
1335
|
untrustUnknownCertificate = true;
|
|
1336
|
+
/** Current lifecycle state of this instance. */
|
|
1362
1337
|
state = 0 /* Uninitialized */;
|
|
1363
1338
|
/** @deprecated Use {@link folderPollingInterval} instead (typo fix). */
|
|
1364
1339
|
folderPoolingInterval = 5e3;
|
|
@@ -1369,13 +1344,14 @@ var CertificateManager = class {
|
|
|
1369
1344
|
set folderPollingInterval(value) {
|
|
1370
1345
|
this.folderPoolingInterval = value;
|
|
1371
1346
|
}
|
|
1347
|
+
/** RSA key size used when generating the private key. */
|
|
1372
1348
|
keySize;
|
|
1373
|
-
location;
|
|
1374
|
-
|
|
1375
|
-
|
|
1376
|
-
|
|
1377
|
-
|
|
1378
|
-
|
|
1349
|
+
#location;
|
|
1350
|
+
#watchers = [];
|
|
1351
|
+
#readCertificatesCalled = false;
|
|
1352
|
+
#filenameToHash = /* @__PURE__ */ new Map();
|
|
1353
|
+
#initializingPromise;
|
|
1354
|
+
#thumbs = {
|
|
1379
1355
|
rejected: /* @__PURE__ */ new Map(),
|
|
1380
1356
|
trusted: /* @__PURE__ */ new Map(),
|
|
1381
1357
|
issuers: {
|
|
@@ -1384,82 +1360,85 @@ var CertificateManager = class {
|
|
|
1384
1360
|
crl: /* @__PURE__ */ new Map(),
|
|
1385
1361
|
issuersCrl: /* @__PURE__ */ new Map()
|
|
1386
1362
|
};
|
|
1363
|
+
/**
|
|
1364
|
+
* Create a new CertificateManager.
|
|
1365
|
+
*
|
|
1366
|
+
* The constructor creates the root directory if it does not
|
|
1367
|
+
* exist but does **not** initialise the PKI store — call
|
|
1368
|
+
* {@link initialize} before using any other method.
|
|
1369
|
+
*
|
|
1370
|
+
* @param options - configuration options
|
|
1371
|
+
*/
|
|
1387
1372
|
constructor(options) {
|
|
1388
1373
|
options.keySize = options.keySize || 2048;
|
|
1389
1374
|
if (!options.location) {
|
|
1390
1375
|
throw new Error("CertificateManager: missing 'location' option");
|
|
1391
1376
|
}
|
|
1392
|
-
this
|
|
1377
|
+
this.#location = makePath(options.location, "");
|
|
1393
1378
|
this.keySize = options.keySize;
|
|
1394
1379
|
mkdirRecursiveSync(options.location);
|
|
1395
|
-
if (!fs9.existsSync(this
|
|
1396
|
-
throw new Error(`CertificateManager cannot access location ${this
|
|
1380
|
+
if (!fs9.existsSync(this.#location)) {
|
|
1381
|
+
throw new Error(`CertificateManager cannot access location ${this.#location}`);
|
|
1397
1382
|
}
|
|
1398
1383
|
}
|
|
1384
|
+
/** Path to the OpenSSL configuration file. */
|
|
1399
1385
|
get configFile() {
|
|
1400
|
-
return
|
|
1386
|
+
return path6.join(this.rootDir, "own/openssl.cnf");
|
|
1401
1387
|
}
|
|
1388
|
+
/** Root directory of the PKI store. */
|
|
1402
1389
|
get rootDir() {
|
|
1403
|
-
return this
|
|
1390
|
+
return this.#location;
|
|
1404
1391
|
}
|
|
1392
|
+
/** Path to the private key file (`own/private/private_key.pem`). */
|
|
1405
1393
|
get privateKey() {
|
|
1406
|
-
return
|
|
1394
|
+
return path6.join(this.rootDir, "own/private/private_key.pem");
|
|
1407
1395
|
}
|
|
1396
|
+
/** Path to the OpenSSL random seed file. */
|
|
1408
1397
|
get randomFile() {
|
|
1409
|
-
return
|
|
1410
|
-
}
|
|
1411
|
-
/**
|
|
1412
|
-
* returns the certificate status trusted/rejected
|
|
1413
|
-
* @param certificate
|
|
1414
|
-
*/
|
|
1415
|
-
async getCertificateStatus(certificate) {
|
|
1416
|
-
await this.initialize();
|
|
1417
|
-
let status = await this._checkRejectedOrTrusted(certificate);
|
|
1418
|
-
if (status === "unknown") {
|
|
1419
|
-
const pem = toPem(certificate, "CERTIFICATE");
|
|
1420
|
-
const fingerprint2 = makeFingerprint(certificate);
|
|
1421
|
-
const filename = path7.join(this.rejectedFolder, `${buildIdealCertificateName(certificate)}.pem`);
|
|
1422
|
-
await fs9.promises.writeFile(filename, pem);
|
|
1423
|
-
this._thumbs.rejected.set(fingerprint2, { certificate, filename });
|
|
1424
|
-
status = "rejected";
|
|
1425
|
-
}
|
|
1426
|
-
return status;
|
|
1398
|
+
return path6.join(this.rootDir, "./random.rnd");
|
|
1427
1399
|
}
|
|
1428
1400
|
/**
|
|
1429
1401
|
* Move a certificate to the rejected store.
|
|
1430
|
-
* If the certificate was previously trusted, it will be
|
|
1402
|
+
* If the certificate was previously trusted, it will be removed from the trusted folder.
|
|
1431
1403
|
* @param certificate - the DER-encoded certificate
|
|
1432
1404
|
*/
|
|
1433
1405
|
async rejectCertificate(certificate) {
|
|
1434
|
-
await this
|
|
1406
|
+
await this.#moveCertificate(certificate, "rejected");
|
|
1435
1407
|
}
|
|
1436
1408
|
/**
|
|
1437
1409
|
* Move a certificate to the trusted store.
|
|
1438
|
-
* If the certificate was previously rejected, it will be
|
|
1410
|
+
* If the certificate was previously rejected, it will be removed from the rejected folder.
|
|
1439
1411
|
* @param certificate - the DER-encoded certificate
|
|
1440
1412
|
*/
|
|
1441
1413
|
async trustCertificate(certificate) {
|
|
1442
|
-
await this
|
|
1414
|
+
await this.#moveCertificate(certificate, "trusted");
|
|
1443
1415
|
}
|
|
1444
1416
|
/** Path to the rejected certificates folder. */
|
|
1445
1417
|
get rejectedFolder() {
|
|
1446
|
-
return
|
|
1418
|
+
return path6.join(this.rootDir, "rejected");
|
|
1447
1419
|
}
|
|
1448
1420
|
/** Path to the trusted certificates folder. */
|
|
1449
1421
|
get trustedFolder() {
|
|
1450
|
-
return
|
|
1422
|
+
return path6.join(this.rootDir, "trusted/certs");
|
|
1451
1423
|
}
|
|
1452
1424
|
/** Path to the trusted CRL folder. */
|
|
1453
1425
|
get crlFolder() {
|
|
1454
|
-
return
|
|
1426
|
+
return path6.join(this.rootDir, "trusted/crl");
|
|
1455
1427
|
}
|
|
1456
1428
|
/** Path to the issuer (CA) certificates folder. */
|
|
1457
1429
|
get issuersCertFolder() {
|
|
1458
|
-
return
|
|
1430
|
+
return path6.join(this.rootDir, "issuers/certs");
|
|
1459
1431
|
}
|
|
1460
1432
|
/** Path to the issuer CRL folder. */
|
|
1461
1433
|
get issuersCrlFolder() {
|
|
1462
|
-
return
|
|
1434
|
+
return path6.join(this.rootDir, "issuers/crl");
|
|
1435
|
+
}
|
|
1436
|
+
/** Path to the own certificate folder. */
|
|
1437
|
+
get ownCertFolder() {
|
|
1438
|
+
return path6.join(this.rootDir, "own/certs");
|
|
1439
|
+
}
|
|
1440
|
+
get ownPrivateFolder() {
|
|
1441
|
+
return path6.join(this.rootDir, "own/private");
|
|
1463
1442
|
}
|
|
1464
1443
|
/**
|
|
1465
1444
|
* Check if a certificate is in the trusted store.
|
|
@@ -1470,11 +1449,11 @@ var CertificateManager = class {
|
|
|
1470
1449
|
* or `"BadCertificateInvalid"` if the certificate cannot be parsed.
|
|
1471
1450
|
*/
|
|
1472
1451
|
async isCertificateTrusted(certificate) {
|
|
1473
|
-
const
|
|
1474
|
-
if (this.
|
|
1452
|
+
const fingerprint = makeFingerprint(certificate);
|
|
1453
|
+
if (this.#thumbs.trusted.has(fingerprint)) {
|
|
1475
1454
|
return "Good";
|
|
1476
1455
|
}
|
|
1477
|
-
if (!this.
|
|
1456
|
+
if (!this.#thumbs.rejected.has(fingerprint)) {
|
|
1478
1457
|
if (!this.untrustUnknownCertificate) {
|
|
1479
1458
|
return "Good";
|
|
1480
1459
|
}
|
|
@@ -1483,17 +1462,14 @@ var CertificateManager = class {
|
|
|
1483
1462
|
} catch (_err) {
|
|
1484
1463
|
return "BadCertificateInvalid";
|
|
1485
1464
|
}
|
|
1486
|
-
const filename =
|
|
1487
|
-
this.rejectedFolder,
|
|
1488
|
-
`${buildIdealCertificateName(certificate)}.pem`
|
|
1489
|
-
);
|
|
1465
|
+
const filename = path6.join(this.rejectedFolder, `${buildIdealCertificateName(certificate)}.pem`);
|
|
1490
1466
|
debugLog("certificate has never been seen before and is now rejected (untrusted) ", filename);
|
|
1491
1467
|
await fsWriteFile(filename, toPem(certificate, "CERTIFICATE"));
|
|
1492
|
-
this.
|
|
1468
|
+
this.#thumbs.rejected.set(fingerprint, { certificate, filename });
|
|
1493
1469
|
}
|
|
1494
1470
|
return "BadCertificateUntrusted";
|
|
1495
1471
|
}
|
|
1496
|
-
async
|
|
1472
|
+
async #innerVerifyCertificateAsync(certificate, _isIssuer, level, options) {
|
|
1497
1473
|
if (level >= 5) {
|
|
1498
1474
|
return "BadSecurityChecksFailed" /* BadSecurityChecksFailed */;
|
|
1499
1475
|
}
|
|
@@ -1528,7 +1504,7 @@ var CertificateManager = class {
|
|
|
1528
1504
|
} else {
|
|
1529
1505
|
debugLog(" the issuer certificate has been found in the issuer.cert folder !");
|
|
1530
1506
|
}
|
|
1531
|
-
const issuerStatus = await this
|
|
1507
|
+
const issuerStatus = await this.#innerVerifyCertificateAsync(issuerCertificate, true, level + 1, options);
|
|
1532
1508
|
if (issuerStatus === "BadCertificateRevocationUnknown" /* BadCertificateRevocationUnknown */) {
|
|
1533
1509
|
return "BadCertificateIssuerRevocationUnknown" /* BadCertificateIssuerRevocationUnknown */;
|
|
1534
1510
|
}
|
|
@@ -1562,7 +1538,7 @@ var CertificateManager = class {
|
|
|
1562
1538
|
debugLog("revokedStatus", revokedStatus);
|
|
1563
1539
|
return revokedStatus;
|
|
1564
1540
|
}
|
|
1565
|
-
const issuerTrustedStatus = await this
|
|
1541
|
+
const issuerTrustedStatus = await this.#checkRejectedOrTrusted(issuerCertificate);
|
|
1566
1542
|
debugLog("issuerTrustedStatus", issuerTrustedStatus);
|
|
1567
1543
|
if (issuerTrustedStatus === "unknown") {
|
|
1568
1544
|
hasTrustedIssuer = false;
|
|
@@ -1581,9 +1557,11 @@ var CertificateManager = class {
|
|
|
1581
1557
|
debugLog("revokedStatus of self signed certificate:", revokedStatus);
|
|
1582
1558
|
}
|
|
1583
1559
|
}
|
|
1584
|
-
const status = await this
|
|
1560
|
+
const status = await this.#checkRejectedOrTrusted(certificate);
|
|
1585
1561
|
if (status === "rejected") {
|
|
1586
|
-
|
|
1562
|
+
if (!(options.acceptCertificateWithValidIssuerChain && hasValidIssuer && hasTrustedIssuer)) {
|
|
1563
|
+
return "BadCertificateUntrusted" /* BadCertificateUntrusted */;
|
|
1564
|
+
}
|
|
1587
1565
|
}
|
|
1588
1566
|
const _c2 = chain[1] ? exploreCertificateInfo(chain[1]) : "non";
|
|
1589
1567
|
debugLog("chain[1] info=", _c2);
|
|
@@ -1616,60 +1594,85 @@ var CertificateManager = class {
|
|
|
1616
1594
|
if (!hasValidIssuer) {
|
|
1617
1595
|
return "BadCertificateUntrusted" /* BadCertificateUntrusted */;
|
|
1618
1596
|
}
|
|
1597
|
+
if (!options.acceptCertificateWithValidIssuerChain) {
|
|
1598
|
+
return "BadCertificateUntrusted" /* BadCertificateUntrusted */;
|
|
1599
|
+
}
|
|
1619
1600
|
return isTimeInvalid ? "BadCertificateTimeInvalid" /* BadCertificateTimeInvalid */ : "Good" /* Good */;
|
|
1620
1601
|
} else {
|
|
1621
1602
|
return "BadCertificateUntrusted" /* BadCertificateUntrusted */;
|
|
1622
1603
|
}
|
|
1623
1604
|
}
|
|
1605
|
+
/**
|
|
1606
|
+
* Internal verification hook called by {@link verifyCertificate}.
|
|
1607
|
+
*
|
|
1608
|
+
* Subclasses can override this to inject additional validation
|
|
1609
|
+
* logic (e.g. application-level policy checks) while still
|
|
1610
|
+
* delegating to the default chain/CRL/trust verification.
|
|
1611
|
+
*
|
|
1612
|
+
* @param certificate - the DER-encoded certificate to verify
|
|
1613
|
+
* @param options - verification options forwarded from the
|
|
1614
|
+
* public API
|
|
1615
|
+
* @returns the verification status code
|
|
1616
|
+
*/
|
|
1624
1617
|
async verifyCertificateAsync(certificate, options) {
|
|
1625
|
-
const status1 = await this
|
|
1618
|
+
const status1 = await this.#innerVerifyCertificateAsync(certificate, false, 0, options);
|
|
1626
1619
|
return status1;
|
|
1627
1620
|
}
|
|
1628
1621
|
/**
|
|
1629
|
-
* Verify certificate
|
|
1630
|
-
*
|
|
1631
|
-
*
|
|
1622
|
+
* Verify a certificate against the PKI trust store.
|
|
1623
|
+
*
|
|
1624
|
+
* This performs a full validation including trust status,
|
|
1625
|
+
* issuer chain, CRL revocation checks, and time validity.
|
|
1626
|
+
*
|
|
1627
|
+
* @param certificate - the DER-encoded certificate to verify
|
|
1628
|
+
* @param options - optional flags to relax validation rules
|
|
1629
|
+
* @returns the verification status code
|
|
1632
1630
|
*/
|
|
1633
1631
|
async verifyCertificate(certificate, options) {
|
|
1634
1632
|
if (!certificate) {
|
|
1635
1633
|
return "BadSecurityChecksFailed" /* BadSecurityChecksFailed */;
|
|
1636
1634
|
}
|
|
1637
|
-
|
|
1635
|
+
try {
|
|
1636
|
+
const status = await this.verifyCertificateAsync(certificate, options || {});
|
|
1637
|
+
return status;
|
|
1638
|
+
} catch (error) {
|
|
1639
|
+
warningLog(`verifyCertificate error: ${error.message}`);
|
|
1640
|
+
return "BadCertificateInvalid" /* BadCertificateInvalid */;
|
|
1641
|
+
}
|
|
1638
1642
|
}
|
|
1639
|
-
|
|
1640
|
-
*
|
|
1641
|
-
*
|
|
1642
|
-
* +---> trusted
|
|
1643
|
-
* +---> rejected
|
|
1644
|
-
* +---> own
|
|
1645
|
-
* +---> cert
|
|
1646
|
-
* +---> own
|
|
1643
|
+
/**
|
|
1644
|
+
* Initialize the PKI directory structure, generate the
|
|
1645
|
+
* private key (if missing), and start file-system watchers.
|
|
1647
1646
|
*
|
|
1647
|
+
* This method is idempotent — subsequent calls are no-ops.
|
|
1648
|
+
* It must be called before any certificate operations.
|
|
1648
1649
|
*/
|
|
1649
1650
|
async initialize() {
|
|
1650
1651
|
if (this.state !== 0 /* Uninitialized */) {
|
|
1651
1652
|
return;
|
|
1652
1653
|
}
|
|
1653
1654
|
this.state = 1 /* Initializing */;
|
|
1654
|
-
this
|
|
1655
|
-
await this
|
|
1656
|
-
this
|
|
1655
|
+
this.#initializingPromise = this.#initialize();
|
|
1656
|
+
await this.#initializingPromise;
|
|
1657
|
+
this.#initializingPromise = void 0;
|
|
1657
1658
|
this.state = 2 /* Initialized */;
|
|
1659
|
+
_CertificateManager.#activeInstances.add(this);
|
|
1660
|
+
_CertificateManager.#installProcessCleanup();
|
|
1658
1661
|
}
|
|
1659
|
-
async
|
|
1662
|
+
async #initialize() {
|
|
1660
1663
|
this.state = 1 /* Initializing */;
|
|
1661
|
-
const pkiDir = this
|
|
1664
|
+
const pkiDir = this.#location;
|
|
1662
1665
|
mkdirRecursiveSync(pkiDir);
|
|
1663
|
-
mkdirRecursiveSync(
|
|
1664
|
-
mkdirRecursiveSync(
|
|
1665
|
-
mkdirRecursiveSync(
|
|
1666
|
-
mkdirRecursiveSync(
|
|
1667
|
-
mkdirRecursiveSync(
|
|
1668
|
-
mkdirRecursiveSync(
|
|
1669
|
-
mkdirRecursiveSync(
|
|
1670
|
-
mkdirRecursiveSync(
|
|
1671
|
-
mkdirRecursiveSync(
|
|
1672
|
-
mkdirRecursiveSync(
|
|
1666
|
+
mkdirRecursiveSync(path6.join(pkiDir, "own"));
|
|
1667
|
+
mkdirRecursiveSync(path6.join(pkiDir, "own/certs"));
|
|
1668
|
+
mkdirRecursiveSync(path6.join(pkiDir, "own/private"));
|
|
1669
|
+
mkdirRecursiveSync(path6.join(pkiDir, "rejected"));
|
|
1670
|
+
mkdirRecursiveSync(path6.join(pkiDir, "trusted"));
|
|
1671
|
+
mkdirRecursiveSync(path6.join(pkiDir, "trusted/certs"));
|
|
1672
|
+
mkdirRecursiveSync(path6.join(pkiDir, "trusted/crl"));
|
|
1673
|
+
mkdirRecursiveSync(path6.join(pkiDir, "issuers"));
|
|
1674
|
+
mkdirRecursiveSync(path6.join(pkiDir, "issuers/certs"));
|
|
1675
|
+
mkdirRecursiveSync(path6.join(pkiDir, "issuers/crl"));
|
|
1673
1676
|
if (!fs9.existsSync(this.configFile) || !fs9.existsSync(this.privateKey)) {
|
|
1674
1677
|
return await this.withLock2(async () => {
|
|
1675
1678
|
if (this.state === 3 /* Disposing */ || this.state === 4 /* Disposed */) {
|
|
@@ -1681,13 +1684,13 @@ var CertificateManager = class {
|
|
|
1681
1684
|
if (!fs9.existsSync(this.privateKey)) {
|
|
1682
1685
|
debugLog("generating private key ...");
|
|
1683
1686
|
await generatePrivateKeyFile2(this.privateKey, this.keySize);
|
|
1684
|
-
await this
|
|
1687
|
+
await this.#readCertificates();
|
|
1685
1688
|
} else {
|
|
1686
|
-
await this
|
|
1689
|
+
await this.#readCertificates();
|
|
1687
1690
|
}
|
|
1688
1691
|
});
|
|
1689
1692
|
} else {
|
|
1690
|
-
await this
|
|
1693
|
+
await this.#readCertificates();
|
|
1691
1694
|
}
|
|
1692
1695
|
}
|
|
1693
1696
|
/**
|
|
@@ -1704,19 +1707,20 @@ var CertificateManager = class {
|
|
|
1704
1707
|
return;
|
|
1705
1708
|
}
|
|
1706
1709
|
if (this.state === 1 /* Initializing */) {
|
|
1707
|
-
if (this
|
|
1708
|
-
await this
|
|
1710
|
+
if (this.#initializingPromise) {
|
|
1711
|
+
await this.#initializingPromise;
|
|
1709
1712
|
}
|
|
1710
1713
|
}
|
|
1711
1714
|
try {
|
|
1712
1715
|
this.state = 3 /* Disposing */;
|
|
1713
|
-
await Promise.all(this.
|
|
1714
|
-
this.
|
|
1716
|
+
await Promise.all(this.#watchers.map((w) => w.close()));
|
|
1717
|
+
this.#watchers.forEach((w) => {
|
|
1715
1718
|
w.removeAllListeners();
|
|
1716
1719
|
});
|
|
1717
|
-
this.
|
|
1720
|
+
this.#watchers.splice(0);
|
|
1718
1721
|
} finally {
|
|
1719
1722
|
this.state = 4 /* Disposed */;
|
|
1723
|
+
_CertificateManager.#activeInstances.delete(this);
|
|
1720
1724
|
}
|
|
1721
1725
|
}
|
|
1722
1726
|
/**
|
|
@@ -1729,30 +1733,34 @@ var CertificateManager = class {
|
|
|
1729
1733
|
* state without waiting for file-system events.
|
|
1730
1734
|
*/
|
|
1731
1735
|
async reloadCertificates() {
|
|
1732
|
-
await Promise.all(this.
|
|
1733
|
-
for (const w of this
|
|
1736
|
+
await Promise.all(this.#watchers.map((w) => w.close()));
|
|
1737
|
+
for (const w of this.#watchers) {
|
|
1734
1738
|
w.removeAllListeners();
|
|
1735
1739
|
}
|
|
1736
|
-
this.
|
|
1737
|
-
this.
|
|
1738
|
-
this.
|
|
1739
|
-
this.
|
|
1740
|
-
this.
|
|
1741
|
-
this.
|
|
1742
|
-
this.
|
|
1743
|
-
this
|
|
1744
|
-
await this
|
|
1740
|
+
this.#watchers.splice(0);
|
|
1741
|
+
this.#thumbs.rejected.clear();
|
|
1742
|
+
this.#thumbs.trusted.clear();
|
|
1743
|
+
this.#thumbs.issuers.certs.clear();
|
|
1744
|
+
this.#thumbs.crl.clear();
|
|
1745
|
+
this.#thumbs.issuersCrl.clear();
|
|
1746
|
+
this.#filenameToHash.clear();
|
|
1747
|
+
this.#readCertificatesCalled = false;
|
|
1748
|
+
await this.#readCertificates();
|
|
1745
1749
|
}
|
|
1746
1750
|
async withLock2(action) {
|
|
1747
|
-
const lockFileName =
|
|
1751
|
+
const lockFileName = path6.join(this.rootDir, "mutex.lock");
|
|
1748
1752
|
return withLock({ fileToLock: lockFileName }, async () => {
|
|
1749
1753
|
return await action();
|
|
1750
1754
|
});
|
|
1751
1755
|
}
|
|
1752
1756
|
/**
|
|
1757
|
+
* Create a self-signed certificate for this PKI's private key.
|
|
1753
1758
|
*
|
|
1754
|
-
*
|
|
1759
|
+
* The certificate is written to `params.outputFile` or
|
|
1760
|
+
* `own/certs/self_signed_certificate.pem` by default.
|
|
1755
1761
|
*
|
|
1762
|
+
* @param params - certificate parameters (subject, SANs,
|
|
1763
|
+
* validity, etc.)
|
|
1756
1764
|
*/
|
|
1757
1765
|
async createSelfSignedCertificate(params) {
|
|
1758
1766
|
if (typeof params.applicationUri !== "string") {
|
|
@@ -1761,7 +1769,7 @@ var CertificateManager = class {
|
|
|
1761
1769
|
if (!fs9.existsSync(this.privateKey)) {
|
|
1762
1770
|
throw new Error(`Cannot find private key ${this.privateKey}`);
|
|
1763
1771
|
}
|
|
1764
|
-
let certificateFilename =
|
|
1772
|
+
let certificateFilename = path6.join(this.rootDir, "own/certs/self_signed_certificate.pem");
|
|
1765
1773
|
certificateFilename = params.outputFile || certificateFilename;
|
|
1766
1774
|
const _params = params;
|
|
1767
1775
|
_params.rootDir = this.rootDir;
|
|
@@ -1772,6 +1780,16 @@ var CertificateManager = class {
|
|
|
1772
1780
|
await createSelfSignedCertificate(certificateFilename, _params);
|
|
1773
1781
|
});
|
|
1774
1782
|
}
|
|
1783
|
+
/**
|
|
1784
|
+
* Create a Certificate Signing Request (CSR) using this
|
|
1785
|
+
* PKI's private key and configuration.
|
|
1786
|
+
*
|
|
1787
|
+
* The CSR file is written to `own/certs/` with a timestamped
|
|
1788
|
+
* filename.
|
|
1789
|
+
*
|
|
1790
|
+
* @param params - CSR parameters (subject, SANs)
|
|
1791
|
+
* @returns the filesystem path to the generated CSR file
|
|
1792
|
+
*/
|
|
1775
1793
|
async createCertificateRequest(params) {
|
|
1776
1794
|
if (!params) {
|
|
1777
1795
|
throw new Error("params is required");
|
|
@@ -1780,13 +1798,13 @@ var CertificateManager = class {
|
|
|
1780
1798
|
if (Object.prototype.hasOwnProperty.call(_params, "rootDir")) {
|
|
1781
1799
|
throw new Error("rootDir should not be specified ");
|
|
1782
1800
|
}
|
|
1783
|
-
_params.rootDir =
|
|
1784
|
-
_params.configFile =
|
|
1785
|
-
_params.privateKey =
|
|
1801
|
+
_params.rootDir = path6.resolve(this.rootDir);
|
|
1802
|
+
_params.configFile = path6.resolve(this.configFile);
|
|
1803
|
+
_params.privateKey = path6.resolve(this.privateKey);
|
|
1786
1804
|
return await this.withLock2(async () => {
|
|
1787
1805
|
const now = /* @__PURE__ */ new Date();
|
|
1788
|
-
const
|
|
1789
|
-
const certificateSigningRequestFilename =
|
|
1806
|
+
const today = `${now.toISOString().slice(0, 10)}_${now.getTime()}`;
|
|
1807
|
+
const certificateSigningRequestFilename = path6.join(this.rootDir, "own/certs", `certificate_${today}.csr`);
|
|
1790
1808
|
await createCertificateSigningRequestAsync(certificateSigningRequestFilename, _params);
|
|
1791
1809
|
return certificateSigningRequestFilename;
|
|
1792
1810
|
});
|
|
@@ -1807,13 +1825,13 @@ var CertificateManager = class {
|
|
|
1807
1825
|
}
|
|
1808
1826
|
}
|
|
1809
1827
|
const pemCertificate = toPem(certificate, "CERTIFICATE");
|
|
1810
|
-
const
|
|
1811
|
-
if (this.
|
|
1828
|
+
const fingerprint = makeFingerprint(certificate);
|
|
1829
|
+
if (this.#thumbs.issuers.certs.has(fingerprint)) {
|
|
1812
1830
|
return "Good" /* Good */;
|
|
1813
1831
|
}
|
|
1814
|
-
const filename =
|
|
1832
|
+
const filename = path6.join(this.issuersCertFolder, `issuer_${buildIdealCertificateName(certificate)}.pem`);
|
|
1815
1833
|
await fs9.promises.writeFile(filename, pemCertificate, "ascii");
|
|
1816
|
-
this.
|
|
1834
|
+
this.#thumbs.issuers.certs.set(fingerprint, { certificate, filename });
|
|
1817
1835
|
if (addInTrustList) {
|
|
1818
1836
|
await this.trustCertificate(certificate);
|
|
1819
1837
|
}
|
|
@@ -1827,7 +1845,7 @@ var CertificateManager = class {
|
|
|
1827
1845
|
async addRevocationList(crl, target = "issuers") {
|
|
1828
1846
|
return await this.withLock2(async () => {
|
|
1829
1847
|
try {
|
|
1830
|
-
const index = target === "trusted" ? this.
|
|
1848
|
+
const index = target === "trusted" ? this.#thumbs.crl : this.#thumbs.issuersCrl;
|
|
1831
1849
|
const folder = target === "trusted" ? this.crlFolder : this.issuersCrlFolder;
|
|
1832
1850
|
const crlInfo = exploreCertificateRevocationList(crl);
|
|
1833
1851
|
const key = crlInfo.tbsCertList.issuerFingerprint;
|
|
@@ -1835,10 +1853,10 @@ var CertificateManager = class {
|
|
|
1835
1853
|
index.set(key, { crls: [], serialNumbers: {} });
|
|
1836
1854
|
}
|
|
1837
1855
|
const pemCertificate = toPem(crl, "X509 CRL");
|
|
1838
|
-
const filename =
|
|
1856
|
+
const filename = path6.join(folder, `crl_${buildIdealCertificateName(crl)}.pem`);
|
|
1839
1857
|
await fs9.promises.writeFile(filename, pemCertificate, "ascii");
|
|
1840
|
-
await this
|
|
1841
|
-
await this
|
|
1858
|
+
await this.#onCrlFileAdded(index, filename);
|
|
1859
|
+
await this.#waitAndCheckCRLProcessingStatus();
|
|
1842
1860
|
return "Good" /* Good */;
|
|
1843
1861
|
} catch (err) {
|
|
1844
1862
|
debugLog(err);
|
|
@@ -1857,9 +1875,9 @@ var CertificateManager = class {
|
|
|
1857
1875
|
try {
|
|
1858
1876
|
const files = await fs9.promises.readdir(folder);
|
|
1859
1877
|
for (const file of files) {
|
|
1860
|
-
const ext =
|
|
1878
|
+
const ext = path6.extname(file).toLowerCase();
|
|
1861
1879
|
if (ext === ".crl" || ext === ".pem" || ext === ".der") {
|
|
1862
|
-
await fs9.promises.unlink(
|
|
1880
|
+
await fs9.promises.unlink(path6.join(folder, file));
|
|
1863
1881
|
}
|
|
1864
1882
|
}
|
|
1865
1883
|
} catch (err) {
|
|
@@ -1870,10 +1888,10 @@ var CertificateManager = class {
|
|
|
1870
1888
|
index.clear();
|
|
1871
1889
|
};
|
|
1872
1890
|
if (target === "issuers" || target === "all") {
|
|
1873
|
-
await clearFolder(this.issuersCrlFolder, this.
|
|
1891
|
+
await clearFolder(this.issuersCrlFolder, this.#thumbs.issuersCrl);
|
|
1874
1892
|
}
|
|
1875
1893
|
if (target === "trusted" || target === "all") {
|
|
1876
|
-
await clearFolder(this.crlFolder, this.
|
|
1894
|
+
await clearFolder(this.crlFolder, this.#thumbs.crl);
|
|
1877
1895
|
}
|
|
1878
1896
|
}
|
|
1879
1897
|
/**
|
|
@@ -1882,9 +1900,9 @@ var CertificateManager = class {
|
|
|
1882
1900
|
* @param thumbprint - hex-encoded SHA-1 thumbprint (lowercase)
|
|
1883
1901
|
*/
|
|
1884
1902
|
async hasIssuer(thumbprint) {
|
|
1885
|
-
await this
|
|
1903
|
+
await this.#readCertificates();
|
|
1886
1904
|
const normalized = thumbprint.toLowerCase();
|
|
1887
|
-
return this.
|
|
1905
|
+
return this.#thumbs.issuers.certs.has(normalized);
|
|
1888
1906
|
}
|
|
1889
1907
|
/**
|
|
1890
1908
|
* Remove a trusted certificate identified by its SHA-1 thumbprint.
|
|
@@ -1894,9 +1912,9 @@ var CertificateManager = class {
|
|
|
1894
1912
|
* @returns the removed certificate buffer, or `null` if not found
|
|
1895
1913
|
*/
|
|
1896
1914
|
async removeTrustedCertificate(thumbprint) {
|
|
1897
|
-
await this
|
|
1915
|
+
await this.#readCertificates();
|
|
1898
1916
|
const normalized = thumbprint.toLowerCase();
|
|
1899
|
-
const entry = this.
|
|
1917
|
+
const entry = this.#thumbs.trusted.get(normalized);
|
|
1900
1918
|
if (!entry) {
|
|
1901
1919
|
return null;
|
|
1902
1920
|
}
|
|
@@ -1907,7 +1925,7 @@ var CertificateManager = class {
|
|
|
1907
1925
|
throw err;
|
|
1908
1926
|
}
|
|
1909
1927
|
}
|
|
1910
|
-
this.
|
|
1928
|
+
this.#thumbs.trusted.delete(normalized);
|
|
1911
1929
|
return entry.certificate;
|
|
1912
1930
|
}
|
|
1913
1931
|
/**
|
|
@@ -1918,9 +1936,9 @@ var CertificateManager = class {
|
|
|
1918
1936
|
* @returns the removed certificate buffer, or `null` if not found
|
|
1919
1937
|
*/
|
|
1920
1938
|
async removeIssuer(thumbprint) {
|
|
1921
|
-
await this
|
|
1939
|
+
await this.#readCertificates();
|
|
1922
1940
|
const normalized = thumbprint.toLowerCase();
|
|
1923
|
-
const entry = this.
|
|
1941
|
+
const entry = this.#thumbs.issuers.certs.get(normalized);
|
|
1924
1942
|
if (!entry) {
|
|
1925
1943
|
return null;
|
|
1926
1944
|
}
|
|
@@ -1931,7 +1949,7 @@ var CertificateManager = class {
|
|
|
1931
1949
|
throw err;
|
|
1932
1950
|
}
|
|
1933
1951
|
}
|
|
1934
|
-
this.
|
|
1952
|
+
this.#thumbs.issuers.certs.delete(normalized);
|
|
1935
1953
|
return entry.certificate;
|
|
1936
1954
|
}
|
|
1937
1955
|
/**
|
|
@@ -1958,10 +1976,10 @@ var CertificateManager = class {
|
|
|
1958
1976
|
index.delete(issuerFingerprint);
|
|
1959
1977
|
};
|
|
1960
1978
|
if (target === "issuers" || target === "all") {
|
|
1961
|
-
await processIndex(this.
|
|
1979
|
+
await processIndex(this.#thumbs.issuersCrl);
|
|
1962
1980
|
}
|
|
1963
1981
|
if (target === "trusted" || target === "all") {
|
|
1964
|
-
await processIndex(this.
|
|
1982
|
+
await processIndex(this.#thumbs.crl);
|
|
1965
1983
|
}
|
|
1966
1984
|
}
|
|
1967
1985
|
/**
|
|
@@ -1997,10 +2015,10 @@ var CertificateManager = class {
|
|
|
1997
2015
|
const issuerThumbprints = /* @__PURE__ */ new Set();
|
|
1998
2016
|
const files = await fs9.promises.readdir(issuerFolder);
|
|
1999
2017
|
for (const file of files) {
|
|
2000
|
-
const ext =
|
|
2018
|
+
const ext = path6.extname(file).toLowerCase();
|
|
2001
2019
|
if (ext === ".pem" || ext === ".der") {
|
|
2002
2020
|
try {
|
|
2003
|
-
const issuerCert = readCertificate(
|
|
2021
|
+
const issuerCert = readCertificate(path6.join(issuerFolder, file));
|
|
2004
2022
|
const fp = makeFingerprint(issuerCert);
|
|
2005
2023
|
issuerThumbprints.add(fp);
|
|
2006
2024
|
} catch (_err) {
|
|
@@ -2030,8 +2048,8 @@ var CertificateManager = class {
|
|
|
2030
2048
|
* signed by this issuer.
|
|
2031
2049
|
*/
|
|
2032
2050
|
async isIssuerInUseByTrustedCertificate(issuerCertificate) {
|
|
2033
|
-
await this
|
|
2034
|
-
for (const entry of this.
|
|
2051
|
+
await this.#readCertificates();
|
|
2052
|
+
for (const entry of this.#thumbs.trusted.values()) {
|
|
2035
2053
|
if (!entry.certificate) continue;
|
|
2036
2054
|
try {
|
|
2037
2055
|
if (verifyCertificateSignature(entry.certificate, issuerCertificate)) {
|
|
@@ -2066,7 +2084,7 @@ var CertificateManager = class {
|
|
|
2066
2084
|
debugLog("Certificate has no extension 3");
|
|
2067
2085
|
return null;
|
|
2068
2086
|
}
|
|
2069
|
-
const issuerCertificates = [...this.
|
|
2087
|
+
const issuerCertificates = [...this.#thumbs.issuers.certs.values()];
|
|
2070
2088
|
const selectedIssuerCertificates = findMatchingIssuerKey(issuerCertificates, wantedIssuerKey);
|
|
2071
2089
|
if (selectedIssuerCertificates.length > 0) {
|
|
2072
2090
|
if (selectedIssuerCertificates.length > 1) {
|
|
@@ -2074,7 +2092,7 @@ var CertificateManager = class {
|
|
|
2074
2092
|
}
|
|
2075
2093
|
return selectedIssuerCertificates[0].certificate || null;
|
|
2076
2094
|
}
|
|
2077
|
-
const trustedCertificates = [...this.
|
|
2095
|
+
const trustedCertificates = [...this.#thumbs.trusted.values()];
|
|
2078
2096
|
const selectedTrustedCertificates = findMatchingIssuerKey(trustedCertificates, wantedIssuerKey);
|
|
2079
2097
|
if (selectedTrustedCertificates.length > 1) {
|
|
2080
2098
|
warningLog(
|
|
@@ -2086,52 +2104,78 @@ var CertificateManager = class {
|
|
|
2086
2104
|
return selectedTrustedCertificates.length > 0 ? selectedTrustedCertificates[0].certificate : null;
|
|
2087
2105
|
}
|
|
2088
2106
|
/**
|
|
2107
|
+
*
|
|
2108
|
+
* check if the certificate explicitly appear in the trust list, the reject list or none.
|
|
2109
|
+
* In case of being in the reject and trusted list at the same time is consider: rejected.
|
|
2089
2110
|
* @internal
|
|
2090
2111
|
* @private
|
|
2091
2112
|
*/
|
|
2092
|
-
async
|
|
2093
|
-
const
|
|
2094
|
-
debugLog("
|
|
2095
|
-
await this
|
|
2096
|
-
if (this.
|
|
2113
|
+
async #checkRejectedOrTrusted(certificate) {
|
|
2114
|
+
const fingerprint = makeFingerprint(certificate);
|
|
2115
|
+
debugLog("#checkRejectedOrTrusted fingerprint ", short(fingerprint));
|
|
2116
|
+
await this.#readCertificates();
|
|
2117
|
+
if (this.#thumbs.rejected.has(fingerprint)) {
|
|
2097
2118
|
return "rejected";
|
|
2098
2119
|
}
|
|
2099
|
-
if (this.
|
|
2120
|
+
if (this.#thumbs.trusted.has(fingerprint)) {
|
|
2100
2121
|
return "trusted";
|
|
2101
2122
|
}
|
|
2102
2123
|
return "unknown";
|
|
2103
2124
|
}
|
|
2104
|
-
async
|
|
2125
|
+
async #moveCertificate(certificate, newStatus) {
|
|
2105
2126
|
await this.withLock2(async () => {
|
|
2106
|
-
const
|
|
2107
|
-
|
|
2108
|
-
|
|
2127
|
+
const fingerprint = makeFingerprint(certificate);
|
|
2128
|
+
let status = await this.#checkRejectedOrTrusted(certificate);
|
|
2129
|
+
if (status === "unknown") {
|
|
2130
|
+
const pem = toPem(certificate, "CERTIFICATE");
|
|
2131
|
+
const filename = path6.join(this.rejectedFolder, `${buildIdealCertificateName(certificate)}.pem`);
|
|
2132
|
+
await fs9.promises.writeFile(filename, pem);
|
|
2133
|
+
this.#thumbs.rejected.set(fingerprint, { certificate, filename });
|
|
2134
|
+
status = "rejected";
|
|
2135
|
+
}
|
|
2136
|
+
debugLog("#moveCertificate", fingerprint.substring(0, 10), "from", status, "to", newStatus);
|
|
2109
2137
|
if (status !== "rejected" && status !== "trusted") {
|
|
2110
|
-
throw new Error(
|
|
2138
|
+
throw new Error(`#moveCertificate: unexpected status '${status}' for certificate ${fingerprint.substring(0, 10)}`);
|
|
2111
2139
|
}
|
|
2112
2140
|
if (status !== newStatus) {
|
|
2113
|
-
const indexSrc = status === "rejected" ? this.
|
|
2114
|
-
const srcEntry = indexSrc.get(
|
|
2141
|
+
const indexSrc = status === "rejected" ? this.#thumbs.rejected : this.#thumbs.trusted;
|
|
2142
|
+
const srcEntry = indexSrc.get(fingerprint);
|
|
2115
2143
|
if (!srcEntry) {
|
|
2116
|
-
debugLog(" cannot find certificate ",
|
|
2117
|
-
throw new Error(
|
|
2144
|
+
debugLog(" cannot find certificate ", fingerprint.substring(0, 10), " in", status);
|
|
2145
|
+
throw new Error(`#moveCertificate: certificate ${fingerprint.substring(0, 10)} not found in ${status} index`);
|
|
2118
2146
|
}
|
|
2119
2147
|
const destFolder = newStatus === "trusted" ? this.trustedFolder : this.rejectedFolder;
|
|
2120
|
-
const certificateDest =
|
|
2121
|
-
debugLog("
|
|
2122
|
-
debugLog("
|
|
2148
|
+
const certificateDest = path6.join(destFolder, path6.basename(srcEntry.filename));
|
|
2149
|
+
debugLog("#moveCertificate", fingerprint.substring(0, 10), "old name", srcEntry.filename);
|
|
2150
|
+
debugLog("#moveCertificate", fingerprint.substring(0, 10), "new name", certificateDest);
|
|
2123
2151
|
await fs9.promises.rename(srcEntry.filename, certificateDest);
|
|
2124
|
-
indexSrc.delete(
|
|
2125
|
-
const indexDest = newStatus === "trusted" ? this.
|
|
2126
|
-
indexDest.set(
|
|
2152
|
+
indexSrc.delete(fingerprint);
|
|
2153
|
+
const indexDest = newStatus === "trusted" ? this.#thumbs.trusted : this.#thumbs.rejected;
|
|
2154
|
+
indexDest.set(fingerprint, { certificate, filename: certificateDest });
|
|
2127
2155
|
}
|
|
2128
2156
|
});
|
|
2129
2157
|
}
|
|
2130
|
-
|
|
2158
|
+
#findAssociatedCRLs(issuerCertificate) {
|
|
2131
2159
|
const issuerCertificateInfo = exploreCertificate(issuerCertificate);
|
|
2132
2160
|
const key = issuerCertificateInfo.tbsCertificate.subjectFingerPrint;
|
|
2133
|
-
return this.
|
|
2161
|
+
return this.#thumbs.issuersCrl.get(key) ?? this.#thumbs.crl.get(key) ?? null;
|
|
2134
2162
|
}
|
|
2163
|
+
/**
|
|
2164
|
+
* Check whether a certificate has been revoked by its issuer's CRL.
|
|
2165
|
+
*
|
|
2166
|
+
* - Self-signed certificates are never considered revoked.
|
|
2167
|
+
* - If no `issuerCertificate` is provided, the method attempts
|
|
2168
|
+
* to find it via {@link findIssuerCertificate}.
|
|
2169
|
+
*
|
|
2170
|
+
* @param certificate - the DER-encoded certificate to check
|
|
2171
|
+
* @param issuerCertificate - optional issuer certificate; looked
|
|
2172
|
+
* up automatically when omitted
|
|
2173
|
+
* @returns `Good` if not revoked, `BadCertificateRevoked` if the
|
|
2174
|
+
* serial number appears in a CRL,
|
|
2175
|
+
* `BadCertificateRevocationUnknown` if no CRL is available,
|
|
2176
|
+
* or `BadCertificateChainIncomplete` if the issuer cannot be
|
|
2177
|
+
* found.
|
|
2178
|
+
*/
|
|
2135
2179
|
async isCertificateRevoked(certificate, issuerCertificate) {
|
|
2136
2180
|
if (isSelfSigned3(certificate)) {
|
|
2137
2181
|
return "Good" /* Good */;
|
|
@@ -2142,42 +2186,42 @@ var CertificateManager = class {
|
|
|
2142
2186
|
if (!issuerCertificate) {
|
|
2143
2187
|
return "BadCertificateChainIncomplete" /* BadCertificateChainIncomplete */;
|
|
2144
2188
|
}
|
|
2145
|
-
const crls = this
|
|
2189
|
+
const crls = this.#findAssociatedCRLs(issuerCertificate);
|
|
2146
2190
|
if (!crls) {
|
|
2147
2191
|
return "BadCertificateRevocationUnknown" /* BadCertificateRevocationUnknown */;
|
|
2148
2192
|
}
|
|
2149
2193
|
const certInfo = exploreCertificate(certificate);
|
|
2150
2194
|
const serialNumber = certInfo.tbsCertificate.serialNumber || certInfo.tbsCertificate.extensions?.authorityKeyIdentifier?.serial || "";
|
|
2151
2195
|
const key = certInfo.tbsCertificate.extensions?.authorityKeyIdentifier?.authorityCertIssuerFingerPrint || "<unknown>";
|
|
2152
|
-
const crl2 = this.
|
|
2196
|
+
const crl2 = this.#thumbs.crl.get(key) ?? null;
|
|
2153
2197
|
if (crls.serialNumbers[serialNumber] || crl2?.serialNumbers[serialNumber]) {
|
|
2154
2198
|
return "BadCertificateRevoked" /* BadCertificateRevoked */;
|
|
2155
2199
|
}
|
|
2156
2200
|
return "Good" /* Good */;
|
|
2157
2201
|
}
|
|
2158
|
-
|
|
2159
|
-
|
|
2160
|
-
queue = [];
|
|
2161
|
-
|
|
2162
|
-
this
|
|
2163
|
-
this
|
|
2164
|
-
if (this
|
|
2165
|
-
this
|
|
2202
|
+
#pendingCrlToProcess = 0;
|
|
2203
|
+
#onCrlProcess;
|
|
2204
|
+
#queue = [];
|
|
2205
|
+
#onCrlFileAdded(index, filename) {
|
|
2206
|
+
this.#queue.push({ index, filename });
|
|
2207
|
+
this.#pendingCrlToProcess += 1;
|
|
2208
|
+
if (this.#pendingCrlToProcess === 1) {
|
|
2209
|
+
this.#processNextCrl();
|
|
2166
2210
|
}
|
|
2167
2211
|
}
|
|
2168
|
-
async
|
|
2212
|
+
async #processNextCrl() {
|
|
2169
2213
|
try {
|
|
2170
|
-
const nextCRL = this
|
|
2214
|
+
const nextCRL = this.#queue.shift();
|
|
2171
2215
|
if (!nextCRL) return;
|
|
2172
2216
|
const { index, filename } = nextCRL;
|
|
2173
2217
|
const crl = await readCertificateRevocationList(filename);
|
|
2174
2218
|
const crlInfo = exploreCertificateRevocationList(crl);
|
|
2175
2219
|
debugLog(chalk6.cyan("add CRL in folder "), filename);
|
|
2176
|
-
const
|
|
2177
|
-
if (!index.has(
|
|
2178
|
-
index.set(
|
|
2220
|
+
const fingerprint = crlInfo.tbsCertList.issuerFingerprint;
|
|
2221
|
+
if (!index.has(fingerprint)) {
|
|
2222
|
+
index.set(fingerprint, { crls: [], serialNumbers: {} });
|
|
2179
2223
|
}
|
|
2180
|
-
const data = index.get(
|
|
2224
|
+
const data = index.get(fingerprint) || { crls: [], serialNumbers: {} };
|
|
2181
2225
|
data.crls.push({ crlInfo, filename });
|
|
2182
2226
|
for (const revokedCertificate of crlInfo.tbsCertList.revokedCertificates) {
|
|
2183
2227
|
const serialNumber = revokedCertificate.userCertificate;
|
|
@@ -2185,875 +2229,208 @@ var CertificateManager = class {
|
|
|
2185
2229
|
data.serialNumbers[serialNumber] = revokedCertificate.revocationDate;
|
|
2186
2230
|
}
|
|
2187
2231
|
}
|
|
2188
|
-
debugLog(chalk6.cyan("CRL"),
|
|
2232
|
+
debugLog(chalk6.cyan("CRL"), fingerprint, "serial numbers = ", Object.keys(data.serialNumbers));
|
|
2189
2233
|
} catch (err) {
|
|
2190
2234
|
debugLog("CRL filename error =");
|
|
2191
2235
|
debugLog(err);
|
|
2192
2236
|
}
|
|
2193
|
-
this
|
|
2194
|
-
if (this
|
|
2195
|
-
if (this
|
|
2196
|
-
this
|
|
2197
|
-
this
|
|
2237
|
+
this.#pendingCrlToProcess -= 1;
|
|
2238
|
+
if (this.#pendingCrlToProcess === 0) {
|
|
2239
|
+
if (this.#onCrlProcess) {
|
|
2240
|
+
this.#onCrlProcess();
|
|
2241
|
+
this.#onCrlProcess = void 0;
|
|
2198
2242
|
}
|
|
2199
2243
|
} else {
|
|
2200
|
-
this
|
|
2244
|
+
this.#processNextCrl();
|
|
2201
2245
|
}
|
|
2202
2246
|
}
|
|
2203
|
-
async
|
|
2204
|
-
if (this
|
|
2247
|
+
async #readCertificates() {
|
|
2248
|
+
if (this.#readCertificatesCalled) {
|
|
2205
2249
|
return;
|
|
2206
2250
|
}
|
|
2207
|
-
this
|
|
2251
|
+
this.#readCertificatesCalled = true;
|
|
2208
2252
|
const usePolling = process.env.OPCUA_PKI_USE_POLLING === "true";
|
|
2209
|
-
const
|
|
2253
|
+
const chokidarOptions = {
|
|
2210
2254
|
usePolling,
|
|
2211
2255
|
...usePolling ? { interval: Math.min(10 * 60 * 1e3, Math.max(100, this.folderPoolingInterval)) } : {},
|
|
2212
2256
|
persistent: false
|
|
2213
2257
|
};
|
|
2214
|
-
|
|
2215
|
-
|
|
2216
|
-
|
|
2217
|
-
|
|
2218
|
-
|
|
2219
|
-
|
|
2220
|
-
|
|
2221
|
-
index.delete(key);
|
|
2222
|
-
}
|
|
2223
|
-
}
|
|
2224
|
-
});
|
|
2225
|
-
w.on("add", (filename) => {
|
|
2226
|
-
this._on_crl_file_added(index, filename);
|
|
2227
|
-
});
|
|
2228
|
-
w.on("change", (changedPath) => {
|
|
2229
|
-
debugLog("change in folder ", folder, changedPath);
|
|
2230
|
-
});
|
|
2231
|
-
this._watchers.push(w);
|
|
2232
|
-
w.on("ready", () => {
|
|
2233
|
-
resolve();
|
|
2234
|
-
});
|
|
2258
|
+
const createUnreffedWatcher = (folder) => {
|
|
2259
|
+
const capturedHandles = [];
|
|
2260
|
+
const origWatch = fs9.watch;
|
|
2261
|
+
fs9.watch = ((...args) => {
|
|
2262
|
+
const handle = origWatch.apply(fs9, args);
|
|
2263
|
+
capturedHandles.push(handle);
|
|
2264
|
+
return handle;
|
|
2235
2265
|
});
|
|
2236
|
-
|
|
2237
|
-
|
|
2238
|
-
|
|
2266
|
+
const w = chokidar.watch(folder, chokidarOptions);
|
|
2267
|
+
const unreffAll = () => {
|
|
2268
|
+
fs9.watch = origWatch;
|
|
2269
|
+
for (const h of capturedHandles) {
|
|
2270
|
+
h.unref();
|
|
2271
|
+
}
|
|
2272
|
+
};
|
|
2273
|
+
return { w, capturedHandles, unreffAll };
|
|
2274
|
+
};
|
|
2275
|
+
const promises = [
|
|
2276
|
+
this.#walkAllFiles(this.trustedFolder, this.#thumbs.trusted, createUnreffedWatcher),
|
|
2277
|
+
this.#walkAllFiles(this.issuersCertFolder, this.#thumbs.issuers.certs, createUnreffedWatcher),
|
|
2278
|
+
this.#walkAllFiles(this.rejectedFolder, this.#thumbs.rejected, createUnreffedWatcher),
|
|
2279
|
+
this.#walkCRLFiles(this.crlFolder, this.#thumbs.crl, createUnreffedWatcher),
|
|
2280
|
+
this.#walkCRLFiles(this.issuersCrlFolder, this.#thumbs.issuersCrl, createUnreffedWatcher)
|
|
2281
|
+
];
|
|
2282
|
+
await Promise.all(promises);
|
|
2283
|
+
await this.#waitAndCheckCRLProcessingStatus();
|
|
2284
|
+
}
|
|
2285
|
+
async #walkCRLFiles(folder, index, createUnreffedWatcher) {
|
|
2286
|
+
await new Promise((resolve, _reject) => {
|
|
2287
|
+
const { w, unreffAll } = createUnreffedWatcher(folder);
|
|
2239
2288
|
w.on("unlink", (filename) => {
|
|
2240
|
-
|
|
2241
|
-
|
|
2242
|
-
|
|
2243
|
-
|
|
2289
|
+
for (const [key, data] of index.entries()) {
|
|
2290
|
+
data.crls = data.crls.filter((c) => c.filename !== filename);
|
|
2291
|
+
if (data.crls.length === 0) {
|
|
2292
|
+
index.delete(key);
|
|
2293
|
+
}
|
|
2244
2294
|
}
|
|
2245
2295
|
});
|
|
2246
2296
|
w.on("add", (filename) => {
|
|
2247
|
-
|
|
2248
|
-
try {
|
|
2249
|
-
const certificate = readCertificate(filename);
|
|
2250
|
-
const info = exploreCertificate(certificate);
|
|
2251
|
-
const fingerprint2 = makeFingerprint(certificate);
|
|
2252
|
-
index.set(fingerprint2, { certificate, filename, info });
|
|
2253
|
-
this._filenameToHash.set(filename, fingerprint2);
|
|
2254
|
-
debugLog(
|
|
2255
|
-
chalk6.magenta("CERT"),
|
|
2256
|
-
info.tbsCertificate.subjectFingerPrint,
|
|
2257
|
-
info.tbsCertificate.serialNumber,
|
|
2258
|
-
info.tbsCertificate.extensions?.authorityKeyIdentifier?.authorityCertIssuerFingerPrint
|
|
2259
|
-
);
|
|
2260
|
-
} catch (err) {
|
|
2261
|
-
debugLog(`Walk files in folder ${folder} with file ${filename}`);
|
|
2262
|
-
debugLog(err);
|
|
2263
|
-
}
|
|
2297
|
+
this.#onCrlFileAdded(index, filename);
|
|
2264
2298
|
});
|
|
2265
2299
|
w.on("change", (changedPath) => {
|
|
2266
|
-
debugLog(
|
|
2267
|
-
try {
|
|
2268
|
-
const certificate = readCertificate(changedPath);
|
|
2269
|
-
const newFingerprint = makeFingerprint(certificate);
|
|
2270
|
-
const oldHash = this._filenameToHash.get(changedPath);
|
|
2271
|
-
if (oldHash && oldHash !== newFingerprint) {
|
|
2272
|
-
index.delete(oldHash);
|
|
2273
|
-
}
|
|
2274
|
-
index.set(newFingerprint, { certificate, filename: changedPath, info: exploreCertificate(certificate) });
|
|
2275
|
-
this._filenameToHash.set(changedPath, newFingerprint);
|
|
2276
|
-
} catch (err) {
|
|
2277
|
-
debugLog(`change event: failed to re-read ${changedPath}`, err);
|
|
2278
|
-
}
|
|
2300
|
+
debugLog("change in folder ", folder, changedPath);
|
|
2279
2301
|
});
|
|
2280
|
-
this.
|
|
2281
|
-
|
|
2282
|
-
|
|
2283
|
-
|
|
2284
|
-
debugLog([...index.keys()].map((k) => k.substring(0, 10)));
|
|
2285
|
-
resolve();
|
|
2286
|
-
});
|
|
2302
|
+
this.#watchers.push(w);
|
|
2303
|
+
w.on("ready", () => {
|
|
2304
|
+
unreffAll();
|
|
2305
|
+
resolve();
|
|
2287
2306
|
});
|
|
2288
|
-
}
|
|
2289
|
-
const promises = [
|
|
2290
|
-
_walkAllFiles.bind(this, this.trustedFolder, this._thumbs.trusted)(),
|
|
2291
|
-
_walkAllFiles.bind(this, this.issuersCertFolder, this._thumbs.issuers.certs)(),
|
|
2292
|
-
_walkAllFiles.bind(this, this.rejectedFolder, this._thumbs.rejected)(),
|
|
2293
|
-
_walkCRLFiles.bind(this, this.crlFolder, this._thumbs.crl)(),
|
|
2294
|
-
_walkCRLFiles.bind(this, this.issuersCrlFolder, this._thumbs.issuersCrl)()
|
|
2295
|
-
];
|
|
2296
|
-
await Promise.all(promises);
|
|
2297
|
-
await this.waitAndCheckCRLProcessingStatus();
|
|
2298
|
-
}
|
|
2299
|
-
// make sure that all crls have been processed.
|
|
2300
|
-
async waitAndCheckCRLProcessingStatus() {
|
|
2301
|
-
return new Promise((resolve, reject) => {
|
|
2302
|
-
if (this._pending_crl_to_process === 0) {
|
|
2303
|
-
setImmediate(resolve);
|
|
2304
|
-
return;
|
|
2305
|
-
}
|
|
2306
|
-
if (this._on_crl_process) {
|
|
2307
|
-
return reject(new Error("Internal Error"));
|
|
2308
|
-
}
|
|
2309
|
-
this._on_crl_process = resolve;
|
|
2310
2307
|
});
|
|
2311
2308
|
}
|
|
2312
|
-
|
|
2313
|
-
|
|
2314
|
-
|
|
2315
|
-
|
|
2316
|
-
|
|
2317
|
-
|
|
2318
|
-
|
|
2319
|
-
const d = new Date(date.getTime());
|
|
2320
|
-
d.setDate(d.getDate() + nbDays);
|
|
2321
|
-
return d;
|
|
2322
|
-
}
|
|
2323
|
-
var today = /* @__PURE__ */ new Date();
|
|
2324
|
-
var yesterday = get_offset_date(today, -1);
|
|
2325
|
-
var two_years_ago = get_offset_date(today, -2 * 365);
|
|
2326
|
-
var next_year = get_offset_date(today, 365);
|
|
2327
|
-
var gLocalConfig = {};
|
|
2328
|
-
var g_certificateAuthority;
|
|
2329
|
-
async function construct_CertificateAuthority2(subject) {
|
|
2330
|
-
assert10(typeof gLocalConfig.CAFolder === "string", "expecting a CAFolder in config");
|
|
2331
|
-
assert10(typeof gLocalConfig.keySize === "number", "expecting a keySize in config");
|
|
2332
|
-
if (!g_certificateAuthority) {
|
|
2333
|
-
g_certificateAuthority = new CertificateAuthority({
|
|
2334
|
-
keySize: gLocalConfig.keySize,
|
|
2335
|
-
location: gLocalConfig.CAFolder,
|
|
2336
|
-
subject
|
|
2337
|
-
});
|
|
2338
|
-
await g_certificateAuthority.initialize();
|
|
2339
|
-
}
|
|
2340
|
-
}
|
|
2341
|
-
var certificateManager;
|
|
2342
|
-
async function construct_CertificateManager() {
|
|
2343
|
-
assert10(typeof gLocalConfig.PKIFolder === "string", "expecting a PKIFolder in config");
|
|
2344
|
-
if (!certificateManager) {
|
|
2345
|
-
certificateManager = new CertificateManager({
|
|
2346
|
-
keySize: gLocalConfig.keySize,
|
|
2347
|
-
location: gLocalConfig.PKIFolder
|
|
2348
|
-
});
|
|
2349
|
-
await certificateManager.initialize();
|
|
2350
|
-
}
|
|
2351
|
-
}
|
|
2352
|
-
function default_template_content() {
|
|
2353
|
-
if (process.pkg?.entrypoint) {
|
|
2354
|
-
const a = fs10.readFileSync(path8.join(__dirname, "../../bin/pki_config.example.js"), "utf8");
|
|
2355
|
-
return a;
|
|
2356
|
-
}
|
|
2357
|
-
function find_default_config_template() {
|
|
2358
|
-
const rootFolder = find_module_root_folder();
|
|
2359
|
-
const configName = "pki_config.example.js";
|
|
2360
|
-
let default_config_template2 = path8.join(rootFolder, "bin", configName);
|
|
2361
|
-
if (!fs10.existsSync(default_config_template2)) {
|
|
2362
|
-
default_config_template2 = path8.join(__dirname, "..", configName);
|
|
2363
|
-
if (!fs10.existsSync(default_config_template2)) {
|
|
2364
|
-
default_config_template2 = path8.join(__dirname, `../bin/${configName}`);
|
|
2309
|
+
async #walkAllFiles(folder, index, createUnreffedWatcher) {
|
|
2310
|
+
const { w, unreffAll } = createUnreffedWatcher(folder);
|
|
2311
|
+
w.on("unlink", (filename) => {
|
|
2312
|
+
debugLog(chalk6.cyan(`unlink in folder ${folder}`), filename);
|
|
2313
|
+
const h = this.#filenameToHash.get(filename);
|
|
2314
|
+
if (h && index.has(h)) {
|
|
2315
|
+
index.delete(h);
|
|
2365
2316
|
}
|
|
2366
|
-
}
|
|
2367
|
-
return default_config_template2;
|
|
2368
|
-
}
|
|
2369
|
-
const default_config_template = find_default_config_template();
|
|
2370
|
-
assert10(fs10.existsSync(default_config_template));
|
|
2371
|
-
const default_config_template_content = fs10.readFileSync(default_config_template, "utf8");
|
|
2372
|
-
return default_config_template_content;
|
|
2373
|
-
}
|
|
2374
|
-
function find_module_root_folder() {
|
|
2375
|
-
let rootFolder = path8.join(__dirname);
|
|
2376
|
-
for (let i = 0; i < 4; i++) {
|
|
2377
|
-
if (fs10.existsSync(path8.join(rootFolder, "package.json"))) {
|
|
2378
|
-
return rootFolder;
|
|
2379
|
-
}
|
|
2380
|
-
rootFolder = path8.join(rootFolder, "..");
|
|
2381
|
-
}
|
|
2382
|
-
assert10(fs10.existsSync(path8.join(rootFolder, "package.json")), "root folder must have a package.json file");
|
|
2383
|
-
return rootFolder;
|
|
2384
|
-
}
|
|
2385
|
-
async function readConfiguration(argv) {
|
|
2386
|
-
if (argv.silent) {
|
|
2387
|
-
g_config.silent = true;
|
|
2388
|
-
} else {
|
|
2389
|
-
g_config.silent = false;
|
|
2390
|
-
}
|
|
2391
|
-
const fqdn2 = await extractFullyQualifiedDomainName();
|
|
2392
|
-
const hostname = os4.hostname();
|
|
2393
|
-
let certificateDir;
|
|
2394
|
-
function performSubstitution(str) {
|
|
2395
|
-
str = str.replace("{CWD}", process.cwd());
|
|
2396
|
-
if (certificateDir) {
|
|
2397
|
-
str = str.replace("{root}", certificateDir);
|
|
2398
|
-
}
|
|
2399
|
-
if (gLocalConfig?.PKIFolder) {
|
|
2400
|
-
str = str.replace("{PKIFolder}", gLocalConfig.PKIFolder);
|
|
2401
|
-
}
|
|
2402
|
-
str = str.replace("{hostname}", hostname);
|
|
2403
|
-
str = str.replace("%FQDN%", fqdn2);
|
|
2404
|
-
return str;
|
|
2405
|
-
}
|
|
2406
|
-
function prepare(file) {
|
|
2407
|
-
const tmp = path8.resolve(performSubstitution(file));
|
|
2408
|
-
return makePath(tmp);
|
|
2409
|
-
}
|
|
2410
|
-
certificateDir = argv.root;
|
|
2411
|
-
assert10(typeof certificateDir === "string");
|
|
2412
|
-
certificateDir = prepare(certificateDir);
|
|
2413
|
-
mkdirRecursiveSync(certificateDir);
|
|
2414
|
-
assert10(fs10.existsSync(certificateDir));
|
|
2415
|
-
const default_config = path8.join(certificateDir, "config.js");
|
|
2416
|
-
if (!fs10.existsSync(default_config)) {
|
|
2417
|
-
debugLog(chalk7.yellow(" Creating default g_config file "), chalk7.cyan(default_config));
|
|
2418
|
-
const default_config_template_content = default_template_content();
|
|
2419
|
-
fs10.writeFileSync(default_config, default_config_template_content);
|
|
2420
|
-
} else {
|
|
2421
|
-
debugLog(chalk7.yellow(" using g_config file "), chalk7.cyan(default_config));
|
|
2422
|
-
}
|
|
2423
|
-
if (!fs10.existsSync(default_config)) {
|
|
2424
|
-
debugLog(chalk7.redBright(" cannot find config file ", default_config));
|
|
2425
|
-
}
|
|
2426
|
-
const defaultRandomFile = path8.join(path8.dirname(default_config), "random.rnd");
|
|
2427
|
-
setEnv("RANDFILE", defaultRandomFile);
|
|
2428
|
-
const _require = createRequire(__filename);
|
|
2429
|
-
gLocalConfig = _require(default_config);
|
|
2430
|
-
gLocalConfig.subject = new Subject5(gLocalConfig.subject || "");
|
|
2431
|
-
if (argv.subject) {
|
|
2432
|
-
gLocalConfig.subject = new Subject5(argv.subject);
|
|
2433
|
-
}
|
|
2434
|
-
if (!gLocalConfig.subject.commonName) {
|
|
2435
|
-
throw new Error("subject must have a Common Name");
|
|
2436
|
-
}
|
|
2437
|
-
gLocalConfig.certificateDir = certificateDir;
|
|
2438
|
-
let CAFolder = argv.CAFolder || path8.join(certificateDir, "CA");
|
|
2439
|
-
CAFolder = prepare(CAFolder);
|
|
2440
|
-
gLocalConfig.CAFolder = CAFolder;
|
|
2441
|
-
gLocalConfig.PKIFolder = path8.join(gLocalConfig.certificateDir, "PKI");
|
|
2442
|
-
if (argv.PKIFolder) {
|
|
2443
|
-
gLocalConfig.PKIFolder = prepare(argv.PKIFolder);
|
|
2444
|
-
}
|
|
2445
|
-
gLocalConfig.PKIFolder = prepare(gLocalConfig.PKIFolder);
|
|
2446
|
-
if (argv.privateKey) {
|
|
2447
|
-
gLocalConfig.privateKey = prepare(argv.privateKey);
|
|
2448
|
-
}
|
|
2449
|
-
if (argv.applicationUri) {
|
|
2450
|
-
gLocalConfig.applicationUri = performSubstitution(argv.applicationUri);
|
|
2451
|
-
}
|
|
2452
|
-
if (argv.output) {
|
|
2453
|
-
gLocalConfig.outputFile = argv.output;
|
|
2454
|
-
}
|
|
2455
|
-
gLocalConfig.altNames = [];
|
|
2456
|
-
if (argv.altNames) {
|
|
2457
|
-
gLocalConfig.altNames = argv.altNames.split(";");
|
|
2458
|
-
}
|
|
2459
|
-
gLocalConfig.dns = [getFullyQualifiedDomainName()];
|
|
2460
|
-
if (argv.dns) {
|
|
2461
|
-
gLocalConfig.dns = argv.dns.split(",").map(performSubstitution);
|
|
2462
|
-
}
|
|
2463
|
-
gLocalConfig.ip = [];
|
|
2464
|
-
if (argv.ip) {
|
|
2465
|
-
gLocalConfig.ip = argv.ip.split(",");
|
|
2466
|
-
}
|
|
2467
|
-
if (argv.keySize) {
|
|
2468
|
-
const v = argv.keySize;
|
|
2469
|
-
if (v !== 1024 && v !== 2048 && v !== 3072 && v !== 4096) {
|
|
2470
|
-
throw new Error(`invalid keysize specified ${v} should be 1024,2048,3072 or 4096`);
|
|
2471
|
-
}
|
|
2472
|
-
gLocalConfig.keySize = argv.keySize;
|
|
2473
|
-
}
|
|
2474
|
-
if (argv.validity) {
|
|
2475
|
-
gLocalConfig.validity = argv.validity;
|
|
2476
|
-
}
|
|
2477
|
-
}
|
|
2478
|
-
async function createDefaultCertificate(base_name, prefix, key_length, applicationUri, dev) {
|
|
2479
|
-
assert10(key_length === 1024 || key_length === 2048 || key_length === 3072 || key_length === 4096);
|
|
2480
|
-
const private_key_file = makePath(base_name, `${prefix}key_${key_length}.pem`);
|
|
2481
|
-
const public_key_file = makePath(base_name, `${prefix}public_key_${key_length}.pub`);
|
|
2482
|
-
const certificate_file = makePath(base_name, `${prefix}cert_${key_length}.pem`);
|
|
2483
|
-
const certificate_file_outofdate = makePath(base_name, `${prefix}cert_${key_length}_outofdate.pem`);
|
|
2484
|
-
const certificate_file_not_active_yet = makePath(base_name, `${prefix}cert_${key_length}_not_active_yet.pem`);
|
|
2485
|
-
const certificate_revoked = makePath(base_name, `${prefix}cert_${key_length}_revoked.pem`);
|
|
2486
|
-
const self_signed_certificate_file = makePath(base_name, `${prefix}selfsigned_cert_${key_length}.pem`);
|
|
2487
|
-
const fqdn2 = getFullyQualifiedDomainName();
|
|
2488
|
-
const hostname = os4.hostname();
|
|
2489
|
-
const dns2 = [
|
|
2490
|
-
// for conformance reason, localhost shall not be present in the DNS field of COP
|
|
2491
|
-
// ***FORBIDEN** "localhost",
|
|
2492
|
-
getFullyQualifiedDomainName()
|
|
2493
|
-
];
|
|
2494
|
-
if (hostname !== fqdn2) {
|
|
2495
|
-
dns2.push(hostname);
|
|
2496
|
-
}
|
|
2497
|
-
const ip = [];
|
|
2498
|
-
async function createCertificateIfNotExist(certificate, private_key, applicationUri2, startDate, validity) {
|
|
2499
|
-
if (fs10.existsSync(certificate)) {
|
|
2500
|
-
warningLog(chalk7.yellow(" certificate"), chalk7.cyan(certificate), chalk7.yellow(" already exists => skipping"));
|
|
2501
|
-
return "";
|
|
2502
|
-
} else {
|
|
2503
|
-
return await createCertificate(certificate, private_key, applicationUri2, startDate, validity);
|
|
2504
|
-
}
|
|
2505
|
-
}
|
|
2506
|
-
async function createCertificate(certificate, privateKey, applicationUri2, startDate, validity) {
|
|
2507
|
-
const certificateSigningRequestFile = `${certificate}.csr`;
|
|
2508
|
-
const configFile = makePath(base_name, "../certificates/PKI/own/openssl.cnf");
|
|
2509
|
-
const dns3 = [os4.hostname()];
|
|
2510
|
-
const ip2 = ["127.0.0.1"];
|
|
2511
|
-
const params = {
|
|
2512
|
-
applicationUri: applicationUri2,
|
|
2513
|
-
privateKey,
|
|
2514
|
-
rootDir: ".",
|
|
2515
|
-
configFile,
|
|
2516
|
-
dns: dns3,
|
|
2517
|
-
ip: ip2,
|
|
2518
|
-
purpose: CertificatePurpose2.ForApplication
|
|
2519
|
-
};
|
|
2520
|
-
await createCertificateSigningRequestWithOpenSSL(certificateSigningRequestFile, params);
|
|
2521
|
-
return await g_certificateAuthority.signCertificateRequest(certificate, certificateSigningRequestFile, {
|
|
2522
|
-
applicationUri: applicationUri2,
|
|
2523
|
-
dns: dns3,
|
|
2524
|
-
ip: ip2,
|
|
2525
|
-
startDate,
|
|
2526
|
-
validity
|
|
2527
2317
|
});
|
|
2528
|
-
|
|
2529
|
-
|
|
2530
|
-
|
|
2531
|
-
|
|
2532
|
-
|
|
2533
|
-
|
|
2534
|
-
|
|
2535
|
-
|
|
2318
|
+
w.on("add", (filename) => {
|
|
2319
|
+
debugLog(chalk6.cyan(`add in folder ${folder}`), filename);
|
|
2320
|
+
try {
|
|
2321
|
+
const certificate = readCertificate(filename);
|
|
2322
|
+
const info = exploreCertificate(certificate);
|
|
2323
|
+
const fingerprint = makeFingerprint(certificate);
|
|
2324
|
+
index.set(fingerprint, { certificate, filename, info });
|
|
2325
|
+
this.#filenameToHash.set(filename, fingerprint);
|
|
2326
|
+
debugLog(
|
|
2327
|
+
chalk6.magenta("CERT"),
|
|
2328
|
+
info.tbsCertificate.subjectFingerPrint,
|
|
2329
|
+
info.tbsCertificate.serialNumber,
|
|
2330
|
+
info.tbsCertificate.extensions?.authorityKeyIdentifier?.authorityCertIssuerFingerPrint
|
|
2331
|
+
);
|
|
2332
|
+
} catch (err) {
|
|
2333
|
+
debugLog(`Walk files in folder ${folder} with file ${filename}`);
|
|
2334
|
+
debugLog(err);
|
|
2335
|
+
}
|
|
2536
2336
|
});
|
|
2537
|
-
|
|
2538
|
-
|
|
2539
|
-
|
|
2540
|
-
|
|
2541
|
-
|
|
2542
|
-
|
|
2543
|
-
|
|
2544
|
-
|
|
2545
|
-
} else {
|
|
2546
|
-
await generatePrivateKeyFile3(privateKey, keyLength);
|
|
2547
|
-
}
|
|
2548
|
-
}
|
|
2549
|
-
displaySubtitle(` create private key :${private_key_file}`);
|
|
2550
|
-
await createPrivateKeyIfNotExist(private_key_file, key_length);
|
|
2551
|
-
displaySubtitle(` extract public key ${public_key_file} from private key `);
|
|
2552
|
-
await getPublicKeyFromPrivateKey(private_key_file, public_key_file);
|
|
2553
|
-
displaySubtitle(` create Certificate ${certificate_file}`);
|
|
2554
|
-
await createCertificateIfNotExist(certificate_file, private_key_file, applicationUri, yesterday, 365);
|
|
2555
|
-
displaySubtitle(` create self signed Certificate ${self_signed_certificate_file}`);
|
|
2556
|
-
if (fs10.existsSync(self_signed_certificate_file)) {
|
|
2557
|
-
return;
|
|
2558
|
-
}
|
|
2559
|
-
await createSelfSignedCertificate2(self_signed_certificate_file, private_key_file, applicationUri, yesterday, 365);
|
|
2560
|
-
if (dev) {
|
|
2561
|
-
await createCertificateIfNotExist(certificate_file_outofdate, private_key_file, applicationUri, two_years_ago, 365);
|
|
2562
|
-
await createCertificateIfNotExist(certificate_file_not_active_yet, private_key_file, applicationUri, next_year, 365);
|
|
2563
|
-
if (!fs10.existsSync(certificate_revoked)) {
|
|
2564
|
-
const certificate = await createCertificateIfNotExist(
|
|
2565
|
-
certificate_revoked,
|
|
2566
|
-
private_key_file,
|
|
2567
|
-
`${applicationUri}Revoked`,
|
|
2568
|
-
// make sure we used a uniq URI here
|
|
2569
|
-
yesterday,
|
|
2570
|
-
365
|
|
2571
|
-
);
|
|
2572
|
-
warningLog(" certificate to revoke => ", certificate);
|
|
2573
|
-
revoke_certificate(certificate_revoked);
|
|
2574
|
-
}
|
|
2575
|
-
}
|
|
2576
|
-
}
|
|
2577
|
-
async function wrap(func) {
|
|
2578
|
-
try {
|
|
2579
|
-
await func();
|
|
2580
|
-
} catch (err) {
|
|
2581
|
-
console.log(err.message);
|
|
2582
|
-
}
|
|
2583
|
-
}
|
|
2584
|
-
async function create_default_certificates(dev) {
|
|
2585
|
-
assert10(gLocalConfig);
|
|
2586
|
-
const base_name = gLocalConfig.certificateDir || "";
|
|
2587
|
-
assert10(fs10.existsSync(base_name));
|
|
2588
|
-
let clientURN;
|
|
2589
|
-
let serverURN;
|
|
2590
|
-
let discoveryServerURN;
|
|
2591
|
-
wrap(async () => {
|
|
2592
|
-
await extractFullyQualifiedDomainName();
|
|
2593
|
-
const hostname = os4.hostname();
|
|
2594
|
-
const fqdn2 = getFullyQualifiedDomainName();
|
|
2595
|
-
warningLog(chalk7.yellow(" hostname = "), chalk7.cyan(hostname));
|
|
2596
|
-
warningLog(chalk7.yellow(" fqdn = "), chalk7.cyan(fqdn2));
|
|
2597
|
-
clientURN = makeApplicationUrn(hostname, "NodeOPCUA-Client");
|
|
2598
|
-
serverURN = makeApplicationUrn(hostname, "NodeOPCUA-Server");
|
|
2599
|
-
discoveryServerURN = makeApplicationUrn(hostname, "NodeOPCUA-DiscoveryServer");
|
|
2600
|
-
displayTitle("Create Application Certificate for Server & its private key");
|
|
2601
|
-
await createDefaultCertificate(base_name, "client_", 1024, clientURN, dev);
|
|
2602
|
-
await createDefaultCertificate(base_name, "client_", 2048, clientURN, dev);
|
|
2603
|
-
await createDefaultCertificate(base_name, "client_", 3072, clientURN, dev);
|
|
2604
|
-
await createDefaultCertificate(base_name, "client_", 4096, clientURN, dev);
|
|
2605
|
-
displayTitle("Create Application Certificate for Client & its private key");
|
|
2606
|
-
await createDefaultCertificate(base_name, "server_", 1024, serverURN, dev);
|
|
2607
|
-
await createDefaultCertificate(base_name, "server_", 2048, serverURN, dev);
|
|
2608
|
-
await createDefaultCertificate(base_name, "server_", 3072, serverURN, dev);
|
|
2609
|
-
await createDefaultCertificate(base_name, "server_", 4096, serverURN, dev);
|
|
2610
|
-
displayTitle("Create Application Certificate for DiscoveryServer & its private key");
|
|
2611
|
-
await createDefaultCertificate(base_name, "discoveryServer_", 1024, discoveryServerURN, dev);
|
|
2612
|
-
await createDefaultCertificate(base_name, "discoveryServer_", 2048, discoveryServerURN, dev);
|
|
2613
|
-
await createDefaultCertificate(base_name, "discoveryServer_", 3072, discoveryServerURN, dev);
|
|
2614
|
-
await createDefaultCertificate(base_name, "discoveryServer_", 4096, discoveryServerURN, dev);
|
|
2615
|
-
});
|
|
2616
|
-
}
|
|
2617
|
-
async function createDefaultCertificates(dev) {
|
|
2618
|
-
await construct_CertificateAuthority2("");
|
|
2619
|
-
await construct_CertificateManager();
|
|
2620
|
-
await create_default_certificates(dev);
|
|
2621
|
-
}
|
|
2622
|
-
var commonOptions = [
|
|
2623
|
-
{
|
|
2624
|
-
name: "root",
|
|
2625
|
-
alias: "r",
|
|
2626
|
-
type: String,
|
|
2627
|
-
defaultValue: "{CWD}/certificates",
|
|
2628
|
-
description: "the location of the Certificate folder"
|
|
2629
|
-
},
|
|
2630
|
-
{
|
|
2631
|
-
name: "CAFolder",
|
|
2632
|
-
alias: "c",
|
|
2633
|
-
type: String,
|
|
2634
|
-
defaultValue: "{root}/CA",
|
|
2635
|
-
description: "the location of the Certificate Authority folder"
|
|
2636
|
-
},
|
|
2637
|
-
{ name: "PKIFolder", type: String, defaultValue: "{root}/PKI", description: "the location of the Public Key Infrastructure" },
|
|
2638
|
-
{ name: "silent", type: Boolean, defaultValue: false, description: "minimize output" },
|
|
2639
|
-
{
|
|
2640
|
-
name: "privateKey",
|
|
2641
|
-
alias: "p",
|
|
2642
|
-
type: String,
|
|
2643
|
-
defaultValue: "{PKIFolder}/own/private_key.pem",
|
|
2644
|
-
description: "the private key to use to generate certificate"
|
|
2645
|
-
},
|
|
2646
|
-
{
|
|
2647
|
-
name: "keySize",
|
|
2648
|
-
alias: "k",
|
|
2649
|
-
type: Number,
|
|
2650
|
-
defaultValue: 2048,
|
|
2651
|
-
description: "the private key size in bits (1024|2048|3072|4096)"
|
|
2652
|
-
},
|
|
2653
|
-
{ name: "help", alias: "h", type: Boolean, description: "display this help" }
|
|
2654
|
-
];
|
|
2655
|
-
function getOptions(names) {
|
|
2656
|
-
return commonOptions.filter((o) => names.includes(o.name) || o.name === "help" || o.name === "silent");
|
|
2657
|
-
}
|
|
2658
|
-
function showHelp(command, description, options, usage) {
|
|
2659
|
-
const sections = [
|
|
2660
|
-
{
|
|
2661
|
-
header: `Command: ${command}`,
|
|
2662
|
-
content: description
|
|
2663
|
-
},
|
|
2664
|
-
{
|
|
2665
|
-
header: "Usage",
|
|
2666
|
-
content: usage || `$0 ${command} [options]`
|
|
2667
|
-
},
|
|
2668
|
-
{
|
|
2669
|
-
header: "Options",
|
|
2670
|
-
optionList: options
|
|
2671
|
-
}
|
|
2672
|
-
];
|
|
2673
|
-
console.log(commandLineUsage(sections));
|
|
2674
|
-
}
|
|
2675
|
-
async function main(argumentsList) {
|
|
2676
|
-
const mainDefinitions = [{ name: "command", defaultOption: true }];
|
|
2677
|
-
let mainOptions;
|
|
2678
|
-
try {
|
|
2679
|
-
mainOptions = commandLineArgs(mainDefinitions, { argv: argumentsList, stopAtFirstUnknown: true });
|
|
2680
|
-
} catch (err) {
|
|
2681
|
-
console.log(err.message);
|
|
2682
|
-
return;
|
|
2683
|
-
}
|
|
2684
|
-
const argv = mainOptions._unknown || [];
|
|
2685
|
-
const command = mainOptions.command;
|
|
2686
|
-
if (!command || command === "help") {
|
|
2687
|
-
console.log(
|
|
2688
|
-
commandLineUsage([
|
|
2689
|
-
{
|
|
2690
|
-
header: "node-opcua-pki",
|
|
2691
|
-
content: `PKI management for node-opcua
|
|
2692
|
-
|
|
2693
|
-
${epilog}`
|
|
2694
|
-
},
|
|
2695
|
-
{
|
|
2696
|
-
header: "Commands",
|
|
2697
|
-
content: [
|
|
2698
|
-
{ name: "demo", summary: "create default certificate for node-opcua demos" },
|
|
2699
|
-
{ name: "createCA", summary: "create a Certificate Authority" },
|
|
2700
|
-
{ name: "createPKI", summary: "create a Public Key Infrastructure" },
|
|
2701
|
-
{ name: "certificate", summary: "create a new certificate" },
|
|
2702
|
-
{ name: "revoke <certificateFile>", summary: "revoke a existing certificate" },
|
|
2703
|
-
{ name: "csr", summary: "create a certificate signing request" },
|
|
2704
|
-
{ name: "sign", summary: "validate a certificate signing request and generate a certificate" },
|
|
2705
|
-
{ name: "dump <certificateFile>", summary: "display a certificate" },
|
|
2706
|
-
{ name: "toder <pemCertificate>", summary: "convert a certificate to a DER format with finger print" },
|
|
2707
|
-
{ name: "fingerprint <certificateFile>", summary: "print the certificate fingerprint" },
|
|
2708
|
-
{ name: "version", summary: "display the version number" }
|
|
2709
|
-
]
|
|
2710
|
-
}
|
|
2711
|
-
])
|
|
2712
|
-
);
|
|
2713
|
-
return;
|
|
2714
|
-
}
|
|
2715
|
-
if (command === "version") {
|
|
2716
|
-
const rootFolder = find_module_root_folder();
|
|
2717
|
-
const pkg = JSON.parse(fs10.readFileSync(path8.join(rootFolder, "package.json"), "utf-8"));
|
|
2718
|
-
console.log(pkg.version);
|
|
2719
|
-
return;
|
|
2720
|
-
}
|
|
2721
|
-
if (command === "demo") {
|
|
2722
|
-
const optionsDef = [
|
|
2723
|
-
...getOptions(["root", "silent"]),
|
|
2724
|
-
{ name: "dev", type: Boolean, description: "create all sort of fancy certificates for dev testing purposes" },
|
|
2725
|
-
{ name: "clean", type: Boolean, description: "Purge existing directory [use with care!]" }
|
|
2726
|
-
];
|
|
2727
|
-
const local_argv = commandLineArgs(optionsDef, { argv });
|
|
2728
|
-
if (local_argv.help)
|
|
2729
|
-
return showHelp(
|
|
2730
|
-
"demo",
|
|
2731
|
-
"create default certificate for node-opcua demos",
|
|
2732
|
-
optionsDef,
|
|
2733
|
-
"$0 demo [--dev] [--silent] [--clean]"
|
|
2734
|
-
);
|
|
2735
|
-
await wrap(async () => {
|
|
2736
|
-
await ensure_openssl_installed();
|
|
2737
|
-
displayChapter("Create Demo certificates");
|
|
2738
|
-
displayTitle("reading configuration");
|
|
2739
|
-
await readConfiguration(local_argv);
|
|
2740
|
-
if (local_argv.clean) {
|
|
2741
|
-
displayTitle("Cleaning old certificates");
|
|
2742
|
-
assert10(gLocalConfig);
|
|
2743
|
-
const certificateDir = gLocalConfig.certificateDir || "";
|
|
2744
|
-
const files = await fs10.promises.readdir(certificateDir);
|
|
2745
|
-
for (const file of files) {
|
|
2746
|
-
if (file.includes(".pem") || file.includes(".pub")) {
|
|
2747
|
-
await fs10.promises.unlink(path8.join(certificateDir, file));
|
|
2748
|
-
}
|
|
2337
|
+
w.on("change", (changedPath) => {
|
|
2338
|
+
debugLog(chalk6.cyan(`change in folder ${folder}`), changedPath);
|
|
2339
|
+
try {
|
|
2340
|
+
const certificate = readCertificate(changedPath);
|
|
2341
|
+
const newFingerprint = makeFingerprint(certificate);
|
|
2342
|
+
const oldHash = this.#filenameToHash.get(changedPath);
|
|
2343
|
+
if (oldHash && oldHash !== newFingerprint) {
|
|
2344
|
+
index.delete(oldHash);
|
|
2749
2345
|
}
|
|
2750
|
-
|
|
2346
|
+
index.set(newFingerprint, { certificate, filename: changedPath, info: exploreCertificate(certificate) });
|
|
2347
|
+
this.#filenameToHash.set(changedPath, newFingerprint);
|
|
2348
|
+
} catch (err) {
|
|
2349
|
+
debugLog(`change event: failed to re-read ${changedPath}`, err);
|
|
2751
2350
|
}
|
|
2752
|
-
displayTitle("create certificates");
|
|
2753
|
-
await createDefaultCertificates(local_argv.dev);
|
|
2754
|
-
displayChapter("Demo certificates CREATED");
|
|
2755
|
-
});
|
|
2756
|
-
return;
|
|
2757
|
-
}
|
|
2758
|
-
if (command === "createCA") {
|
|
2759
|
-
const optionsDef = [
|
|
2760
|
-
...getOptions(["root", "CAFolder", "keySize", "silent"]),
|
|
2761
|
-
{ name: "subject", type: String, defaultValue: defaultSubject, description: "the CA certificate subject" }
|
|
2762
|
-
];
|
|
2763
|
-
const local_argv = commandLineArgs(optionsDef, { argv });
|
|
2764
|
-
if (local_argv.help) return showHelp("createCA", "create a Certificate Authority", optionsDef);
|
|
2765
|
-
await wrap(async () => {
|
|
2766
|
-
await ensure_openssl_installed();
|
|
2767
|
-
await readConfiguration(local_argv);
|
|
2768
|
-
await construct_CertificateAuthority2(local_argv.subject);
|
|
2769
2351
|
});
|
|
2770
|
-
|
|
2771
|
-
|
|
2772
|
-
|
|
2773
|
-
|
|
2774
|
-
|
|
2775
|
-
|
|
2776
|
-
|
|
2777
|
-
|
|
2778
|
-
await construct_CertificateManager();
|
|
2779
|
-
});
|
|
2780
|
-
return;
|
|
2781
|
-
}
|
|
2782
|
-
if (command === "certificate") {
|
|
2783
|
-
const optionsDef = [
|
|
2784
|
-
...getOptions(["root", "CAFolder", "PKIFolder", "privateKey", "silent"]),
|
|
2785
|
-
{
|
|
2786
|
-
name: "applicationUri",
|
|
2787
|
-
alias: "a",
|
|
2788
|
-
type: String,
|
|
2789
|
-
defaultValue: "urn:{hostname}:Node-OPCUA-Server",
|
|
2790
|
-
description: "the application URI"
|
|
2791
|
-
},
|
|
2792
|
-
{
|
|
2793
|
-
name: "output",
|
|
2794
|
-
alias: "o",
|
|
2795
|
-
type: String,
|
|
2796
|
-
defaultValue: "my_certificate.pem",
|
|
2797
|
-
description: "the name of the generated certificate =>"
|
|
2798
|
-
},
|
|
2799
|
-
{
|
|
2800
|
-
name: "selfSigned",
|
|
2801
|
-
alias: "s",
|
|
2802
|
-
type: Boolean,
|
|
2803
|
-
defaultValue: false,
|
|
2804
|
-
description: "if true, certificate will be self-signed"
|
|
2805
|
-
},
|
|
2806
|
-
{ name: "validity", alias: "v", type: Number, description: "the certificate validity in days" },
|
|
2807
|
-
{
|
|
2808
|
-
name: "dns",
|
|
2809
|
-
type: String,
|
|
2810
|
-
defaultValue: "{hostname}",
|
|
2811
|
-
description: "the list of valid domain name (comma separated)"
|
|
2812
|
-
},
|
|
2813
|
-
{ name: "ip", type: String, defaultValue: "", description: "the list of valid IPs (comma separated)" },
|
|
2814
|
-
{
|
|
2815
|
-
name: "subject",
|
|
2816
|
-
type: String,
|
|
2817
|
-
defaultValue: "",
|
|
2818
|
-
description: "the certificate subject ( for instance C=FR/ST=Centre/L=Orleans/O=SomeOrganization/CN=Hello )"
|
|
2819
|
-
}
|
|
2820
|
-
];
|
|
2821
|
-
const local_argv = commandLineArgs(optionsDef, { argv });
|
|
2822
|
-
if (local_argv.help || !local_argv.applicationUri || !local_argv.output)
|
|
2823
|
-
return showHelp("certificate", "create a new certificate", optionsDef);
|
|
2824
|
-
async function command_certificate(local_argv2) {
|
|
2825
|
-
const selfSigned = !!local_argv2.selfSigned;
|
|
2826
|
-
if (!selfSigned) {
|
|
2827
|
-
await command_full_certificate(local_argv2);
|
|
2828
|
-
} else {
|
|
2829
|
-
await command_selfsigned_certificate(local_argv2);
|
|
2830
|
-
}
|
|
2831
|
-
}
|
|
2832
|
-
async function command_selfsigned_certificate(local_argv2) {
|
|
2833
|
-
const _fqdn = await extractFullyQualifiedDomainName();
|
|
2834
|
-
await readConfiguration(local_argv2);
|
|
2835
|
-
await construct_CertificateManager();
|
|
2836
|
-
displaySubtitle(` create self signed Certificate ${gLocalConfig.outputFile}`);
|
|
2837
|
-
let subject = local_argv2.subject && local_argv2.subject.length > 1 ? new Subject5(local_argv2.subject) : gLocalConfig.subject || "";
|
|
2838
|
-
subject = JSON.parse(JSON.stringify(subject));
|
|
2839
|
-
const params = {
|
|
2840
|
-
applicationUri: gLocalConfig.applicationUri || "",
|
|
2841
|
-
dns: gLocalConfig.dns || [],
|
|
2842
|
-
ip: gLocalConfig.ip || [],
|
|
2843
|
-
outputFile: gLocalConfig.outputFile || "self_signed_certificate.pem",
|
|
2844
|
-
startDate: gLocalConfig.startDate || /* @__PURE__ */ new Date(),
|
|
2845
|
-
subject,
|
|
2846
|
-
validity: gLocalConfig.validity || 365
|
|
2847
|
-
};
|
|
2848
|
-
await certificateManager.createSelfSignedCertificate(params);
|
|
2849
|
-
}
|
|
2850
|
-
async function command_full_certificate(local_argv2) {
|
|
2851
|
-
await readConfiguration(local_argv2);
|
|
2852
|
-
await construct_CertificateManager();
|
|
2853
|
-
await construct_CertificateAuthority2("");
|
|
2854
|
-
assert10(fs10.existsSync(gLocalConfig.CAFolder || ""), " CA folder must exist");
|
|
2855
|
-
gLocalConfig.privateKey = void 0;
|
|
2856
|
-
gLocalConfig.subject = local_argv2.subject && local_argv2.subject.length > 1 ? local_argv2.subject : gLocalConfig.subject;
|
|
2857
|
-
const csr_file = await certificateManager.createCertificateRequest(
|
|
2858
|
-
gLocalConfig
|
|
2859
|
-
);
|
|
2860
|
-
if (!csr_file) {
|
|
2861
|
-
return;
|
|
2862
|
-
}
|
|
2863
|
-
warningLog(" csr_file = ", csr_file);
|
|
2864
|
-
const certificate = csr_file.replace(".csr", ".pem");
|
|
2865
|
-
if (fs10.existsSync(certificate)) {
|
|
2866
|
-
throw new Error(` File ${certificate} already exist`);
|
|
2867
|
-
}
|
|
2868
|
-
await g_certificateAuthority.signCertificateRequest(
|
|
2869
|
-
certificate,
|
|
2870
|
-
csr_file,
|
|
2871
|
-
gLocalConfig
|
|
2872
|
-
);
|
|
2873
|
-
assert10(typeof gLocalConfig.outputFile === "string");
|
|
2874
|
-
fs10.writeFileSync(gLocalConfig.outputFile || "", fs10.readFileSync(certificate, "ascii"));
|
|
2875
|
-
}
|
|
2876
|
-
await wrap(async () => await command_certificate(local_argv));
|
|
2877
|
-
return;
|
|
2878
|
-
}
|
|
2879
|
-
if (command === "revoke") {
|
|
2880
|
-
const optionsDef = [{ name: "certificateFile", type: String, defaultOption: true }, ...getOptions(["root", "CAFolder"])];
|
|
2881
|
-
const local_argv = commandLineArgs(optionsDef, { argv });
|
|
2882
|
-
if (local_argv.help || !local_argv.certificateFile)
|
|
2883
|
-
return showHelp(
|
|
2884
|
-
"revoke <certificateFile>",
|
|
2885
|
-
"revoke a existing certificate",
|
|
2886
|
-
optionsDef,
|
|
2887
|
-
"$0 revoke my_certificate.pem"
|
|
2888
|
-
);
|
|
2889
|
-
async function revoke_certificate(certificate) {
|
|
2890
|
-
await g_certificateAuthority.revokeCertificate(certificate, {});
|
|
2891
|
-
}
|
|
2892
|
-
await wrap(async () => {
|
|
2893
|
-
const certificate = path8.resolve(local_argv.certificateFile);
|
|
2894
|
-
warningLog(chalk7.yellow(" Certificate to revoke : "), chalk7.cyan(certificate));
|
|
2895
|
-
if (!fs10.existsSync(certificate)) {
|
|
2896
|
-
throw new Error(`cannot find certificate to revoke ${certificate}`);
|
|
2897
|
-
}
|
|
2898
|
-
await readConfiguration(local_argv);
|
|
2899
|
-
await construct_CertificateAuthority2("");
|
|
2900
|
-
await revoke_certificate(certificate);
|
|
2901
|
-
warningLog("done ... ");
|
|
2902
|
-
warningLog(" crl = ", g_certificateAuthority.revocationList);
|
|
2903
|
-
warningLog("\nyou should now publish the new Certificate Revocation List");
|
|
2352
|
+
this.#watchers.push(w);
|
|
2353
|
+
await new Promise((resolve, _reject) => {
|
|
2354
|
+
w.on("ready", () => {
|
|
2355
|
+
unreffAll();
|
|
2356
|
+
debugLog("ready");
|
|
2357
|
+
debugLog([...index.keys()].map((k) => k.substring(0, 10)));
|
|
2358
|
+
resolve();
|
|
2359
|
+
});
|
|
2904
2360
|
});
|
|
2905
|
-
return;
|
|
2906
2361
|
}
|
|
2907
|
-
|
|
2908
|
-
|
|
2909
|
-
|
|
2910
|
-
{
|
|
2911
|
-
|
|
2912
|
-
alias: "a",
|
|
2913
|
-
type: String,
|
|
2914
|
-
defaultValue: "urn:{hostname}:Node-OPCUA-Server",
|
|
2915
|
-
description: "the application URI"
|
|
2916
|
-
},
|
|
2917
|
-
{
|
|
2918
|
-
name: "output",
|
|
2919
|
-
alias: "o",
|
|
2920
|
-
type: String,
|
|
2921
|
-
defaultValue: "my_certificate_signing_request.csr",
|
|
2922
|
-
description: "the name of the generated signing_request"
|
|
2923
|
-
},
|
|
2924
|
-
{
|
|
2925
|
-
name: "dns",
|
|
2926
|
-
type: String,
|
|
2927
|
-
defaultValue: "{hostname}",
|
|
2928
|
-
description: "the list of valid domain name (comma separated)"
|
|
2929
|
-
},
|
|
2930
|
-
{ name: "ip", type: String, defaultValue: "", description: "the list of valid IPs (comma separated)" },
|
|
2931
|
-
{
|
|
2932
|
-
name: "subject",
|
|
2933
|
-
type: String,
|
|
2934
|
-
defaultValue: "/CN=Certificate",
|
|
2935
|
-
description: "the certificate subject ( for instance /C=FR/ST=Centre/L=Orleans/O=SomeOrganization/CN=Hello )"
|
|
2936
|
-
}
|
|
2937
|
-
];
|
|
2938
|
-
const local_argv = commandLineArgs(optionsDef, { argv });
|
|
2939
|
-
if (local_argv.help) return showHelp("csr", "create a certificate signing request", optionsDef);
|
|
2940
|
-
await wrap(async () => {
|
|
2941
|
-
await readConfiguration(local_argv);
|
|
2942
|
-
if (!fs10.existsSync(gLocalConfig.PKIFolder || "")) {
|
|
2943
|
-
warningLog("PKI folder must exist");
|
|
2944
|
-
}
|
|
2945
|
-
await construct_CertificateManager();
|
|
2946
|
-
if (!gLocalConfig.outputFile || fs10.existsSync(gLocalConfig.outputFile)) {
|
|
2947
|
-
throw new Error(` File ${gLocalConfig.outputFile} already exist`);
|
|
2948
|
-
}
|
|
2949
|
-
gLocalConfig.privateKey = void 0;
|
|
2950
|
-
gLocalConfig.subject = local_argv.subject && local_argv.subject.length > 1 ? local_argv.subject : gLocalConfig.subject;
|
|
2951
|
-
const internal_csr_file = await certificateManager.createCertificateRequest(
|
|
2952
|
-
gLocalConfig
|
|
2953
|
-
);
|
|
2954
|
-
if (!internal_csr_file) {
|
|
2955
|
-
return;
|
|
2956
|
-
}
|
|
2957
|
-
if (!gLocalConfig.outputFile) {
|
|
2958
|
-
warningLog("please specify a output file");
|
|
2362
|
+
// make sure that all crls have been processed.
|
|
2363
|
+
async #waitAndCheckCRLProcessingStatus() {
|
|
2364
|
+
return new Promise((resolve, reject) => {
|
|
2365
|
+
if (this.#pendingCrlToProcess === 0) {
|
|
2366
|
+
setImmediate(resolve);
|
|
2959
2367
|
return;
|
|
2960
2368
|
}
|
|
2961
|
-
|
|
2962
|
-
|
|
2963
|
-
warningLog("Subject = ", gLocalConfig.subject);
|
|
2964
|
-
warningLog("applicationUri = ", gLocalConfig.applicationUri);
|
|
2965
|
-
warningLog("altNames = ", gLocalConfig.altNames);
|
|
2966
|
-
warningLog("dns = ", gLocalConfig.dns);
|
|
2967
|
-
warningLog("ip = ", gLocalConfig.ip);
|
|
2968
|
-
warningLog("CSR file = ", gLocalConfig.outputFile);
|
|
2969
|
-
});
|
|
2970
|
-
return;
|
|
2971
|
-
}
|
|
2972
|
-
if (command === "sign") {
|
|
2973
|
-
const optionsDef = [
|
|
2974
|
-
...getOptions(["root", "CAFolder", "silent"]),
|
|
2975
|
-
{ name: "csr", alias: "i", type: String, defaultValue: "my_certificate_signing_request.csr", description: "the csr" },
|
|
2976
|
-
{
|
|
2977
|
-
name: "output",
|
|
2978
|
-
alias: "o",
|
|
2979
|
-
type: String,
|
|
2980
|
-
defaultValue: "my_certificate.pem",
|
|
2981
|
-
description: "the name of the generated certificate"
|
|
2982
|
-
},
|
|
2983
|
-
{ name: "validity", alias: "v", type: Number, defaultValue: 365, description: "the certificate validity in days" }
|
|
2984
|
-
];
|
|
2985
|
-
const local_argv = commandLineArgs(optionsDef, { argv });
|
|
2986
|
-
if (local_argv.help || !local_argv.csr || !local_argv.output)
|
|
2987
|
-
return showHelp("sign", "validate a certificate signing request and generate a certificate", optionsDef);
|
|
2988
|
-
await wrap(async () => {
|
|
2989
|
-
await readConfiguration(local_argv);
|
|
2990
|
-
if (!fs10.existsSync(gLocalConfig.CAFolder || "")) {
|
|
2991
|
-
throw new Error(`CA folder must exist:${gLocalConfig.CAFolder}`);
|
|
2992
|
-
}
|
|
2993
|
-
await construct_CertificateAuthority2("");
|
|
2994
|
-
const csr_file = path8.resolve(local_argv.csr || "");
|
|
2995
|
-
if (!fs10.existsSync(csr_file)) {
|
|
2996
|
-
throw new Error(`Certificate signing request doesn't exist: ${csr_file}`);
|
|
2997
|
-
}
|
|
2998
|
-
const certificate = path8.resolve(local_argv.output || csr_file.replace(".csr", ".pem"));
|
|
2999
|
-
if (fs10.existsSync(certificate)) {
|
|
3000
|
-
throw new Error(` File ${certificate} already exist`);
|
|
2369
|
+
if (this.#onCrlProcess) {
|
|
2370
|
+
return reject(new Error("Internal Error"));
|
|
3001
2371
|
}
|
|
3002
|
-
|
|
3003
|
-
certificate,
|
|
3004
|
-
csr_file,
|
|
3005
|
-
gLocalConfig
|
|
3006
|
-
);
|
|
3007
|
-
assert10(typeof gLocalConfig.outputFile === "string");
|
|
3008
|
-
fs10.writeFileSync(gLocalConfig.outputFile || "", fs10.readFileSync(certificate, "ascii"));
|
|
3009
|
-
});
|
|
3010
|
-
return;
|
|
3011
|
-
}
|
|
3012
|
-
if (command === "dump") {
|
|
3013
|
-
const optionsDef = [
|
|
3014
|
-
{ name: "certificateFile", type: String, defaultOption: true },
|
|
3015
|
-
{ name: "help", alias: "h", type: Boolean }
|
|
3016
|
-
];
|
|
3017
|
-
const local_argv = commandLineArgs(optionsDef, { argv });
|
|
3018
|
-
if (local_argv.help || !local_argv.certificateFile)
|
|
3019
|
-
return showHelp("dump <certificateFile>", "display a certificate", optionsDef);
|
|
3020
|
-
await wrap(async () => {
|
|
3021
|
-
const data = await dumpCertificate(local_argv.certificateFile);
|
|
3022
|
-
warningLog(data);
|
|
2372
|
+
this.#onCrlProcess = resolve;
|
|
3023
2373
|
});
|
|
3024
|
-
return;
|
|
3025
|
-
}
|
|
3026
|
-
if (command === "toder") {
|
|
3027
|
-
const optionsDef = [
|
|
3028
|
-
{ name: "pemCertificate", type: String, defaultOption: true },
|
|
3029
|
-
{ name: "help", alias: "h", type: Boolean }
|
|
3030
|
-
];
|
|
3031
|
-
const local_argv = commandLineArgs(optionsDef, { argv });
|
|
3032
|
-
if (local_argv.help || !local_argv.pemCertificate)
|
|
3033
|
-
return showHelp("toder <pemCertificate>", "convert a certificate to a DER format with finger print", optionsDef);
|
|
3034
|
-
await wrap(async () => {
|
|
3035
|
-
await toDer(local_argv.pemCertificate);
|
|
3036
|
-
});
|
|
3037
|
-
return;
|
|
3038
2374
|
}
|
|
3039
|
-
|
|
3040
|
-
|
|
3041
|
-
|
|
3042
|
-
|
|
3043
|
-
|
|
3044
|
-
|
|
3045
|
-
|
|
3046
|
-
|
|
3047
|
-
|
|
3048
|
-
|
|
3049
|
-
|
|
3050
|
-
|
|
3051
|
-
|
|
3052
|
-
|
|
3053
|
-
|
|
3054
|
-
|
|
3055
|
-
|
|
3056
|
-
|
|
2375
|
+
};
|
|
2376
|
+
|
|
2377
|
+
// packages/node-opcua-pki/lib/pki/toolbox_pfx.ts
|
|
2378
|
+
import assert9 from "assert";
|
|
2379
|
+
import fs10 from "fs";
|
|
2380
|
+
var q2 = quote;
|
|
2381
|
+
var n3 = makePath;
|
|
2382
|
+
async function createPFX(options) {
|
|
2383
|
+
const { certificateFile, privateKeyFile, outputFile, passphrase = "", caCertificateFiles } = options;
|
|
2384
|
+
assert9(fs10.existsSync(certificateFile), `Certificate file does not exist: ${certificateFile}`);
|
|
2385
|
+
assert9(fs10.existsSync(privateKeyFile), `Private key file does not exist: ${privateKeyFile}`);
|
|
2386
|
+
let cmd = `pkcs12 -export`;
|
|
2387
|
+
cmd += ` -in ${q2(n3(certificateFile))}`;
|
|
2388
|
+
cmd += ` -inkey ${q2(n3(privateKeyFile))}`;
|
|
2389
|
+
if (caCertificateFiles) {
|
|
2390
|
+
for (const caFile of caCertificateFiles) {
|
|
2391
|
+
assert9(fs10.existsSync(caFile), `CA certificate file does not exist: ${caFile}`);
|
|
2392
|
+
cmd += ` -certfile ${q2(n3(caFile))}`;
|
|
2393
|
+
}
|
|
2394
|
+
}
|
|
2395
|
+
cmd += ` -out ${q2(n3(outputFile))}`;
|
|
2396
|
+
cmd += ` -passout pass:${passphrase}`;
|
|
2397
|
+
await execute_openssl(cmd, {});
|
|
2398
|
+
}
|
|
2399
|
+
async function extractCertificateFromPFX(options) {
|
|
2400
|
+
const { pfxFile, passphrase = "" } = options;
|
|
2401
|
+
assert9(fs10.existsSync(pfxFile), `PFX file does not exist: ${pfxFile}`);
|
|
2402
|
+
const cmd = `pkcs12 -in ${q2(n3(pfxFile))} -clcerts -nokeys -nodes -passin pass:${passphrase}`;
|
|
2403
|
+
return await execute_openssl(cmd, {});
|
|
2404
|
+
}
|
|
2405
|
+
async function extractPrivateKeyFromPFX(options) {
|
|
2406
|
+
const { pfxFile, passphrase = "" } = options;
|
|
2407
|
+
assert9(fs10.existsSync(pfxFile), `PFX file does not exist: ${pfxFile}`);
|
|
2408
|
+
const cmd = `pkcs12 -in ${q2(n3(pfxFile))} -nocerts -nodes -passin pass:${passphrase}`;
|
|
2409
|
+
return await execute_openssl(cmd, {});
|
|
2410
|
+
}
|
|
2411
|
+
async function extractCACertificatesFromPFX(options) {
|
|
2412
|
+
const { pfxFile, passphrase = "" } = options;
|
|
2413
|
+
assert9(fs10.existsSync(pfxFile), `PFX file does not exist: ${pfxFile}`);
|
|
2414
|
+
const cmd = `pkcs12 -in ${q2(n3(pfxFile))} -cacerts -nokeys -nodes -passin pass:${passphrase}`;
|
|
2415
|
+
return await execute_openssl(cmd, {});
|
|
2416
|
+
}
|
|
2417
|
+
async function extractAllFromPFX(options) {
|
|
2418
|
+
const [certificate, privateKey, caCertificates] = await Promise.all([
|
|
2419
|
+
extractCertificateFromPFX(options),
|
|
2420
|
+
extractPrivateKeyFromPFX(options),
|
|
2421
|
+
extractCACertificatesFromPFX(options)
|
|
2422
|
+
]);
|
|
2423
|
+
return { certificate, privateKey, caCertificates };
|
|
2424
|
+
}
|
|
2425
|
+
async function convertPFXtoPEM(pfxFile, pemFile, passphrase = "") {
|
|
2426
|
+
assert9(fs10.existsSync(pfxFile), `PFX file does not exist: ${pfxFile}`);
|
|
2427
|
+
const cmd = `pkcs12 -in ${q2(n3(pfxFile))} -out ${q2(n3(pemFile))} -nodes -passin pass:${passphrase}`;
|
|
2428
|
+
await execute_openssl(cmd, {});
|
|
2429
|
+
}
|
|
2430
|
+
async function dumpPFX(pfxFile, passphrase = "") {
|
|
2431
|
+
assert9(fs10.existsSync(pfxFile), `PFX file does not exist: ${pfxFile}`);
|
|
2432
|
+
const cmd = `pkcs12 -in ${q2(n3(pfxFile))} -info -nodes -passin pass:${passphrase}`;
|
|
2433
|
+
return await execute_openssl(cmd, {});
|
|
3057
2434
|
}
|
|
3058
2435
|
export {
|
|
3059
2436
|
CertificateAuthority,
|
|
@@ -3063,22 +2440,15 @@ export {
|
|
|
3063
2440
|
VerificationStatus,
|
|
3064
2441
|
adjustApplicationUri,
|
|
3065
2442
|
adjustDate,
|
|
3066
|
-
|
|
3067
|
-
|
|
3068
|
-
|
|
3069
|
-
|
|
3070
|
-
|
|
3071
|
-
|
|
3072
|
-
|
|
3073
|
-
displayTitle,
|
|
3074
|
-
doDebug,
|
|
2443
|
+
convertPFXtoPEM,
|
|
2444
|
+
createPFX,
|
|
2445
|
+
dumpPFX,
|
|
2446
|
+
extractAllFromPFX,
|
|
2447
|
+
extractCACertificatesFromPFX,
|
|
2448
|
+
extractCertificateFromPFX,
|
|
2449
|
+
extractPrivateKeyFromPFX,
|
|
3075
2450
|
findIssuerCertificateInChain,
|
|
3076
|
-
g_config,
|
|
3077
2451
|
install_prerequisite,
|
|
3078
|
-
|
|
3079
|
-
mkdirRecursiveSync,
|
|
3080
|
-
main as pki_main,
|
|
3081
|
-
quote,
|
|
3082
|
-
warningLog
|
|
2452
|
+
quote
|
|
3083
2453
|
};
|
|
3084
2454
|
//# sourceMappingURL=index.mjs.map
|