node-opcua-pki 6.3.0 → 6.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/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 +507 -68
- package/dist/index.d.ts +507 -68
- package/dist/index.js +662 -1244
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +713 -1296
- package/dist/index.mjs.map +1 -1
- package/package.json +3 -3
- package/readme.md +23 -7
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,98 +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 assert11 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
|
-
import assert10 from "assert";
|
|
1171
1073
|
import fs9 from "fs";
|
|
1172
|
-
import
|
|
1074
|
+
import path6 from "path";
|
|
1173
1075
|
import { withLock } from "@ster5/global-mutex";
|
|
1174
1076
|
import chalk6 from "chalk";
|
|
1175
1077
|
import chokidar from "chokidar";
|
|
@@ -1188,18 +1090,18 @@ import {
|
|
|
1188
1090
|
} from "node-opcua-crypto";
|
|
1189
1091
|
|
|
1190
1092
|
// packages/node-opcua-pki/lib/toolbox/without_openssl/create_certificate_signing_request.ts
|
|
1191
|
-
import
|
|
1093
|
+
import assert7 from "assert";
|
|
1192
1094
|
import fs7 from "fs";
|
|
1193
1095
|
import { createCertificateSigningRequest, pemToPrivateKey, Subject as Subject3 } from "node-opcua-crypto";
|
|
1194
1096
|
async function createCertificateSigningRequestAsync(certificateSigningRequestFilename, params) {
|
|
1195
|
-
|
|
1196
|
-
|
|
1197
|
-
|
|
1198
|
-
|
|
1199
|
-
|
|
1200
|
-
|
|
1201
|
-
|
|
1202
|
-
|
|
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");
|
|
1203
1105
|
const subject = params.subject ? new Subject3(params.subject).toString() : void 0;
|
|
1204
1106
|
displaySubtitle("- Creating a Certificate Signing Request with subtile");
|
|
1205
1107
|
const privateKeyPem = await fs7.promises.readFile(params.privateKey, "utf-8");
|
|
@@ -1218,7 +1120,7 @@ async function createCertificateSigningRequestAsync(certificateSigningRequestFil
|
|
|
1218
1120
|
}
|
|
1219
1121
|
|
|
1220
1122
|
// packages/node-opcua-pki/lib/toolbox/without_openssl/create_self_signed_certificate.ts
|
|
1221
|
-
import
|
|
1123
|
+
import assert8 from "assert";
|
|
1222
1124
|
import fs8 from "fs";
|
|
1223
1125
|
import {
|
|
1224
1126
|
CertificatePurpose,
|
|
@@ -1228,17 +1130,17 @@ import {
|
|
|
1228
1130
|
} from "node-opcua-crypto";
|
|
1229
1131
|
async function createSelfSignedCertificateAsync(certificate, params) {
|
|
1230
1132
|
params.purpose = params.purpose || CertificatePurpose.ForApplication;
|
|
1231
|
-
|
|
1232
|
-
|
|
1233
|
-
|
|
1234
|
-
|
|
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));
|
|
1235
1137
|
if (!params.subject) {
|
|
1236
1138
|
throw Error("Missing subject");
|
|
1237
1139
|
}
|
|
1238
|
-
|
|
1239
|
-
|
|
1140
|
+
assert8(typeof params.applicationUri === "string");
|
|
1141
|
+
assert8(Array.isArray(params.dns));
|
|
1240
1142
|
adjustDate(params);
|
|
1241
|
-
|
|
1143
|
+
assert8(Object.prototype.hasOwnProperty.call(params, "validity"));
|
|
1242
1144
|
let subject = new Subject4(params.subject);
|
|
1243
1145
|
subject = subject.toString();
|
|
1244
1146
|
const purpose = params.purpose;
|
|
@@ -1269,6 +1171,12 @@ var simple_config_template_cnf_default = config3;
|
|
|
1269
1171
|
// packages/node-opcua-pki/lib/pki/certificate_manager.ts
|
|
1270
1172
|
var configurationFileSimpleTemplate = simple_config_template_cnf_default;
|
|
1271
1173
|
var fsWriteFile = fs9.promises.writeFile;
|
|
1174
|
+
function getOrComputeInfo(entry) {
|
|
1175
|
+
if (!entry.info) {
|
|
1176
|
+
entry.info = exploreCertificate(entry.certificate);
|
|
1177
|
+
}
|
|
1178
|
+
return entry.info;
|
|
1179
|
+
}
|
|
1272
1180
|
var VerificationStatus = /* @__PURE__ */ ((VerificationStatus2) => {
|
|
1273
1181
|
VerificationStatus2["BadCertificateInvalid"] = "BadCertificateInvalid";
|
|
1274
1182
|
VerificationStatus2["BadSecurityChecksFailed"] = "BadSecurityChecksFailed";
|
|
@@ -1289,28 +1197,28 @@ var VerificationStatus = /* @__PURE__ */ ((VerificationStatus2) => {
|
|
|
1289
1197
|
return VerificationStatus2;
|
|
1290
1198
|
})(VerificationStatus || {});
|
|
1291
1199
|
function makeFingerprint(certificate) {
|
|
1292
|
-
|
|
1200
|
+
const chain = split_der(certificate);
|
|
1201
|
+
return makeSHA1Thumbprint(chain[0]).toString("hex");
|
|
1293
1202
|
}
|
|
1294
1203
|
function short(stringToShorten) {
|
|
1295
1204
|
return stringToShorten.substring(0, 10);
|
|
1296
1205
|
}
|
|
1297
1206
|
var forbiddenChars = /[\x00-\x1F<>:"/\\|?*]/g;
|
|
1298
1207
|
function buildIdealCertificateName(certificate) {
|
|
1299
|
-
const
|
|
1208
|
+
const fingerprint = makeFingerprint(certificate);
|
|
1300
1209
|
try {
|
|
1301
1210
|
const commonName = exploreCertificate(certificate).tbsCertificate.subject.commonName || "";
|
|
1302
1211
|
const sanitizedCommonName = commonName.replace(forbiddenChars, "_");
|
|
1303
|
-
return `${sanitizedCommonName}[${
|
|
1212
|
+
return `${sanitizedCommonName}[${fingerprint}]`;
|
|
1304
1213
|
} catch (_err) {
|
|
1305
|
-
return `invalid_certificate_[${
|
|
1214
|
+
return `invalid_certificate_[${fingerprint}]`;
|
|
1306
1215
|
}
|
|
1307
1216
|
}
|
|
1308
1217
|
function findMatchingIssuerKey(entries, wantedIssuerKey) {
|
|
1309
|
-
|
|
1310
|
-
const info =
|
|
1218
|
+
return entries.filter((entry) => {
|
|
1219
|
+
const info = getOrComputeInfo(entry);
|
|
1311
1220
|
return info.tbsCertificate.extensions && info.tbsCertificate.extensions.subjectKeyIdentifier === wantedIssuerKey;
|
|
1312
1221
|
});
|
|
1313
|
-
return selected;
|
|
1314
1222
|
}
|
|
1315
1223
|
function isSelfSigned2(info) {
|
|
1316
1224
|
return info.tbsCertificate.extensions?.subjectKeyIdentifier === info.tbsCertificate.extensions?.authorityKeyIdentifier?.keyIdentifier;
|
|
@@ -1353,104 +1261,184 @@ var CertificateManagerState = /* @__PURE__ */ ((CertificateManagerState2) => {
|
|
|
1353
1261
|
CertificateManagerState2[CertificateManagerState2["Disposed"] = 4] = "Disposed";
|
|
1354
1262
|
return CertificateManagerState2;
|
|
1355
1263
|
})(CertificateManagerState || {});
|
|
1356
|
-
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
|
+
*/
|
|
1357
1335
|
untrustUnknownCertificate = true;
|
|
1336
|
+
/** Current lifecycle state of this instance. */
|
|
1358
1337
|
state = 0 /* Uninitialized */;
|
|
1338
|
+
/** @deprecated Use {@link folderPollingInterval} instead (typo fix). */
|
|
1359
1339
|
folderPoolingInterval = 5e3;
|
|
1340
|
+
/** Interval in milliseconds for file-system polling (when enabled). */
|
|
1341
|
+
get folderPollingInterval() {
|
|
1342
|
+
return this.folderPoolingInterval;
|
|
1343
|
+
}
|
|
1344
|
+
set folderPollingInterval(value) {
|
|
1345
|
+
this.folderPoolingInterval = value;
|
|
1346
|
+
}
|
|
1347
|
+
/** RSA key size used when generating the private key. */
|
|
1360
1348
|
keySize;
|
|
1361
|
-
location;
|
|
1362
|
-
|
|
1363
|
-
|
|
1364
|
-
|
|
1365
|
-
|
|
1366
|
-
|
|
1367
|
-
|
|
1349
|
+
#location;
|
|
1350
|
+
#watchers = [];
|
|
1351
|
+
#readCertificatesCalled = false;
|
|
1352
|
+
#filenameToHash = /* @__PURE__ */ new Map();
|
|
1353
|
+
#initializingPromise;
|
|
1354
|
+
#thumbs = {
|
|
1355
|
+
rejected: /* @__PURE__ */ new Map(),
|
|
1356
|
+
trusted: /* @__PURE__ */ new Map(),
|
|
1368
1357
|
issuers: {
|
|
1369
|
-
certs:
|
|
1358
|
+
certs: /* @__PURE__ */ new Map()
|
|
1370
1359
|
},
|
|
1371
|
-
crl:
|
|
1372
|
-
issuersCrl:
|
|
1360
|
+
crl: /* @__PURE__ */ new Map(),
|
|
1361
|
+
issuersCrl: /* @__PURE__ */ new Map()
|
|
1373
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
|
+
*/
|
|
1374
1372
|
constructor(options) {
|
|
1375
1373
|
options.keySize = options.keySize || 2048;
|
|
1376
|
-
|
|
1377
|
-
|
|
1378
|
-
|
|
1379
|
-
this
|
|
1374
|
+
if (!options.location) {
|
|
1375
|
+
throw new Error("CertificateManager: missing 'location' option");
|
|
1376
|
+
}
|
|
1377
|
+
this.#location = makePath(options.location, "");
|
|
1380
1378
|
this.keySize = options.keySize;
|
|
1381
1379
|
mkdirRecursiveSync(options.location);
|
|
1382
|
-
if (!fs9.existsSync(this
|
|
1383
|
-
throw new Error(`CertificateManager cannot access location ${this
|
|
1380
|
+
if (!fs9.existsSync(this.#location)) {
|
|
1381
|
+
throw new Error(`CertificateManager cannot access location ${this.#location}`);
|
|
1384
1382
|
}
|
|
1385
1383
|
}
|
|
1384
|
+
/** Path to the OpenSSL configuration file. */
|
|
1386
1385
|
get configFile() {
|
|
1387
|
-
return
|
|
1386
|
+
return path6.join(this.rootDir, "own/openssl.cnf");
|
|
1388
1387
|
}
|
|
1388
|
+
/** Root directory of the PKI store. */
|
|
1389
1389
|
get rootDir() {
|
|
1390
|
-
return this
|
|
1390
|
+
return this.#location;
|
|
1391
1391
|
}
|
|
1392
|
+
/** Path to the private key file (`own/private/private_key.pem`). */
|
|
1392
1393
|
get privateKey() {
|
|
1393
|
-
return
|
|
1394
|
+
return path6.join(this.rootDir, "own/private/private_key.pem");
|
|
1394
1395
|
}
|
|
1396
|
+
/** Path to the OpenSSL random seed file. */
|
|
1395
1397
|
get randomFile() {
|
|
1396
|
-
return
|
|
1397
|
-
}
|
|
1398
|
-
/**
|
|
1399
|
-
* returns the certificate status trusted/rejected
|
|
1400
|
-
* @param certificate
|
|
1401
|
-
*/
|
|
1402
|
-
async getCertificateStatus(certificate) {
|
|
1403
|
-
await this.initialize();
|
|
1404
|
-
let status = await this._checkRejectedOrTrusted(certificate);
|
|
1405
|
-
if (status === "unknown") {
|
|
1406
|
-
assert10(certificate instanceof Buffer);
|
|
1407
|
-
const pem = toPem(certificate, "CERTIFICATE");
|
|
1408
|
-
const fingerprint2 = makeFingerprint(certificate);
|
|
1409
|
-
const filename = path7.join(this.rejectedFolder, `${buildIdealCertificateName(certificate)}.pem`);
|
|
1410
|
-
await fs9.promises.writeFile(filename, pem);
|
|
1411
|
-
this._thumbs.rejected[fingerprint2] = {
|
|
1412
|
-
certificate,
|
|
1413
|
-
filename
|
|
1414
|
-
};
|
|
1415
|
-
status = "rejected";
|
|
1416
|
-
}
|
|
1417
|
-
return status;
|
|
1398
|
+
return path6.join(this.rootDir, "./random.rnd");
|
|
1418
1399
|
}
|
|
1419
1400
|
/**
|
|
1420
1401
|
* Move a certificate to the rejected store.
|
|
1421
|
-
* If the certificate was previously trusted, it will be
|
|
1402
|
+
* If the certificate was previously trusted, it will be removed from the trusted folder.
|
|
1422
1403
|
* @param certificate - the DER-encoded certificate
|
|
1423
1404
|
*/
|
|
1424
1405
|
async rejectCertificate(certificate) {
|
|
1425
|
-
await this
|
|
1406
|
+
await this.#moveCertificate(certificate, "rejected");
|
|
1426
1407
|
}
|
|
1427
1408
|
/**
|
|
1428
1409
|
* Move a certificate to the trusted store.
|
|
1429
|
-
* If the certificate was previously rejected, it will be
|
|
1410
|
+
* If the certificate was previously rejected, it will be removed from the rejected folder.
|
|
1430
1411
|
* @param certificate - the DER-encoded certificate
|
|
1431
1412
|
*/
|
|
1432
1413
|
async trustCertificate(certificate) {
|
|
1433
|
-
await this
|
|
1414
|
+
await this.#moveCertificate(certificate, "trusted");
|
|
1434
1415
|
}
|
|
1435
1416
|
/** Path to the rejected certificates folder. */
|
|
1436
1417
|
get rejectedFolder() {
|
|
1437
|
-
return
|
|
1418
|
+
return path6.join(this.rootDir, "rejected");
|
|
1438
1419
|
}
|
|
1439
1420
|
/** Path to the trusted certificates folder. */
|
|
1440
1421
|
get trustedFolder() {
|
|
1441
|
-
return
|
|
1422
|
+
return path6.join(this.rootDir, "trusted/certs");
|
|
1442
1423
|
}
|
|
1443
1424
|
/** Path to the trusted CRL folder. */
|
|
1444
1425
|
get crlFolder() {
|
|
1445
|
-
return
|
|
1426
|
+
return path6.join(this.rootDir, "trusted/crl");
|
|
1446
1427
|
}
|
|
1447
1428
|
/** Path to the issuer (CA) certificates folder. */
|
|
1448
1429
|
get issuersCertFolder() {
|
|
1449
|
-
return
|
|
1430
|
+
return path6.join(this.rootDir, "issuers/certs");
|
|
1450
1431
|
}
|
|
1451
1432
|
/** Path to the issuer CRL folder. */
|
|
1452
1433
|
get issuersCrlFolder() {
|
|
1453
|
-
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");
|
|
1454
1442
|
}
|
|
1455
1443
|
/**
|
|
1456
1444
|
* Check if a certificate is in the trusted store.
|
|
@@ -1461,33 +1449,27 @@ var CertificateManager = class {
|
|
|
1461
1449
|
* or `"BadCertificateInvalid"` if the certificate cannot be parsed.
|
|
1462
1450
|
*/
|
|
1463
1451
|
async isCertificateTrusted(certificate) {
|
|
1464
|
-
const
|
|
1465
|
-
|
|
1466
|
-
if (certificateInTrust) {
|
|
1452
|
+
const fingerprint = makeFingerprint(certificate);
|
|
1453
|
+
if (this.#thumbs.trusted.has(fingerprint)) {
|
|
1467
1454
|
return "Good";
|
|
1468
|
-
}
|
|
1469
|
-
|
|
1470
|
-
if (!
|
|
1471
|
-
|
|
1472
|
-
|
|
1473
|
-
|
|
1474
|
-
);
|
|
1475
|
-
|
|
1476
|
-
|
|
1477
|
-
}
|
|
1478
|
-
try {
|
|
1479
|
-
const _certificateInfo = exploreCertificateInfo(certificate);
|
|
1480
|
-
_certificateInfo;
|
|
1481
|
-
} catch (_err) {
|
|
1482
|
-
return "BadCertificateInvalid";
|
|
1483
|
-
}
|
|
1484
|
-
debugLog("certificate has never been seen before and is now rejected (untrusted) ", certificateFilenameInRejected);
|
|
1485
|
-
await fsWriteFile(certificateFilenameInRejected, toPem(certificate, "CERTIFICATE"));
|
|
1455
|
+
}
|
|
1456
|
+
if (!this.#thumbs.rejected.has(fingerprint)) {
|
|
1457
|
+
if (!this.untrustUnknownCertificate) {
|
|
1458
|
+
return "Good";
|
|
1459
|
+
}
|
|
1460
|
+
try {
|
|
1461
|
+
exploreCertificateInfo(certificate);
|
|
1462
|
+
} catch (_err) {
|
|
1463
|
+
return "BadCertificateInvalid";
|
|
1486
1464
|
}
|
|
1487
|
-
|
|
1465
|
+
const filename = path6.join(this.rejectedFolder, `${buildIdealCertificateName(certificate)}.pem`);
|
|
1466
|
+
debugLog("certificate has never been seen before and is now rejected (untrusted) ", filename);
|
|
1467
|
+
await fsWriteFile(filename, toPem(certificate, "CERTIFICATE"));
|
|
1468
|
+
this.#thumbs.rejected.set(fingerprint, { certificate, filename });
|
|
1488
1469
|
}
|
|
1470
|
+
return "BadCertificateUntrusted";
|
|
1489
1471
|
}
|
|
1490
|
-
async
|
|
1472
|
+
async #innerVerifyCertificateAsync(certificate, _isIssuer, level, options) {
|
|
1491
1473
|
if (level >= 5) {
|
|
1492
1474
|
return "BadSecurityChecksFailed" /* BadSecurityChecksFailed */;
|
|
1493
1475
|
}
|
|
@@ -1522,7 +1504,7 @@ var CertificateManager = class {
|
|
|
1522
1504
|
} else {
|
|
1523
1505
|
debugLog(" the issuer certificate has been found in the issuer.cert folder !");
|
|
1524
1506
|
}
|
|
1525
|
-
const issuerStatus = await this
|
|
1507
|
+
const issuerStatus = await this.#innerVerifyCertificateAsync(issuerCertificate, true, level + 1, options);
|
|
1526
1508
|
if (issuerStatus === "BadCertificateRevocationUnknown" /* BadCertificateRevocationUnknown */) {
|
|
1527
1509
|
return "BadCertificateIssuerRevocationUnknown" /* BadCertificateIssuerRevocationUnknown */;
|
|
1528
1510
|
}
|
|
@@ -1556,7 +1538,7 @@ var CertificateManager = class {
|
|
|
1556
1538
|
debugLog("revokedStatus", revokedStatus);
|
|
1557
1539
|
return revokedStatus;
|
|
1558
1540
|
}
|
|
1559
|
-
const issuerTrustedStatus = await this
|
|
1541
|
+
const issuerTrustedStatus = await this.#checkRejectedOrTrusted(issuerCertificate);
|
|
1560
1542
|
debugLog("issuerTrustedStatus", issuerTrustedStatus);
|
|
1561
1543
|
if (issuerTrustedStatus === "unknown") {
|
|
1562
1544
|
hasTrustedIssuer = false;
|
|
@@ -1575,12 +1557,14 @@ var CertificateManager = class {
|
|
|
1575
1557
|
debugLog("revokedStatus of self signed certificate:", revokedStatus);
|
|
1576
1558
|
}
|
|
1577
1559
|
}
|
|
1578
|
-
const status = await this
|
|
1560
|
+
const status = await this.#checkRejectedOrTrusted(certificate);
|
|
1579
1561
|
if (status === "rejected") {
|
|
1580
|
-
|
|
1562
|
+
if (!(options.acceptCertificateWithValidIssuerChain && hasValidIssuer && hasTrustedIssuer)) {
|
|
1563
|
+
return "BadCertificateUntrusted" /* BadCertificateUntrusted */;
|
|
1564
|
+
}
|
|
1581
1565
|
}
|
|
1582
1566
|
const _c2 = chain[1] ? exploreCertificateInfo(chain[1]) : "non";
|
|
1583
|
-
_c2;
|
|
1567
|
+
debugLog("chain[1] info=", _c2);
|
|
1584
1568
|
const certificateInfo = exploreCertificateInfo(certificate);
|
|
1585
1569
|
const now = /* @__PURE__ */ new Date();
|
|
1586
1570
|
let isTimeInvalid = false;
|
|
@@ -1603,7 +1587,6 @@ var CertificateManager = class {
|
|
|
1603
1587
|
if (status === "trusted") {
|
|
1604
1588
|
return isTimeInvalid ? "BadCertificateTimeInvalid" /* BadCertificateTimeInvalid */ : "Good" /* Good */;
|
|
1605
1589
|
}
|
|
1606
|
-
assert10(status === "unknown");
|
|
1607
1590
|
if (hasIssuerKey) {
|
|
1608
1591
|
if (!hasTrustedIssuer) {
|
|
1609
1592
|
return "BadCertificateUntrusted" /* BadCertificateUntrusted */;
|
|
@@ -1611,78 +1594,103 @@ var CertificateManager = class {
|
|
|
1611
1594
|
if (!hasValidIssuer) {
|
|
1612
1595
|
return "BadCertificateUntrusted" /* BadCertificateUntrusted */;
|
|
1613
1596
|
}
|
|
1597
|
+
if (!options.acceptCertificateWithValidIssuerChain) {
|
|
1598
|
+
return "BadCertificateUntrusted" /* BadCertificateUntrusted */;
|
|
1599
|
+
}
|
|
1614
1600
|
return isTimeInvalid ? "BadCertificateTimeInvalid" /* BadCertificateTimeInvalid */ : "Good" /* Good */;
|
|
1615
1601
|
} else {
|
|
1616
1602
|
return "BadCertificateUntrusted" /* BadCertificateUntrusted */;
|
|
1617
1603
|
}
|
|
1618
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
|
+
*/
|
|
1619
1617
|
async verifyCertificateAsync(certificate, options) {
|
|
1620
|
-
const status1 = await this
|
|
1618
|
+
const status1 = await this.#innerVerifyCertificateAsync(certificate, false, 0, options);
|
|
1621
1619
|
return status1;
|
|
1622
1620
|
}
|
|
1623
1621
|
/**
|
|
1624
|
-
* Verify certificate
|
|
1625
|
-
*
|
|
1626
|
-
*
|
|
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
|
|
1627
1630
|
*/
|
|
1628
1631
|
async verifyCertificate(certificate, options) {
|
|
1629
1632
|
if (!certificate) {
|
|
1630
1633
|
return "BadSecurityChecksFailed" /* BadSecurityChecksFailed */;
|
|
1631
1634
|
}
|
|
1632
|
-
|
|
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
|
+
}
|
|
1633
1642
|
}
|
|
1634
|
-
|
|
1635
|
-
*
|
|
1636
|
-
*
|
|
1637
|
-
* +---> trusted
|
|
1638
|
-
* +---> rejected
|
|
1639
|
-
* +---> own
|
|
1640
|
-
* +---> cert
|
|
1641
|
-
* +---> own
|
|
1643
|
+
/**
|
|
1644
|
+
* Initialize the PKI directory structure, generate the
|
|
1645
|
+
* private key (if missing), and start file-system watchers.
|
|
1642
1646
|
*
|
|
1647
|
+
* This method is idempotent — subsequent calls are no-ops.
|
|
1648
|
+
* It must be called before any certificate operations.
|
|
1643
1649
|
*/
|
|
1644
1650
|
async initialize() {
|
|
1645
1651
|
if (this.state !== 0 /* Uninitialized */) {
|
|
1646
1652
|
return;
|
|
1647
1653
|
}
|
|
1648
1654
|
this.state = 1 /* Initializing */;
|
|
1649
|
-
|
|
1655
|
+
this.#initializingPromise = this.#initialize();
|
|
1656
|
+
await this.#initializingPromise;
|
|
1657
|
+
this.#initializingPromise = void 0;
|
|
1650
1658
|
this.state = 2 /* Initialized */;
|
|
1659
|
+
_CertificateManager.#activeInstances.add(this);
|
|
1660
|
+
_CertificateManager.#installProcessCleanup();
|
|
1651
1661
|
}
|
|
1652
|
-
async
|
|
1662
|
+
async #initialize() {
|
|
1653
1663
|
this.state = 1 /* Initializing */;
|
|
1654
|
-
const pkiDir = this
|
|
1664
|
+
const pkiDir = this.#location;
|
|
1655
1665
|
mkdirRecursiveSync(pkiDir);
|
|
1656
|
-
mkdirRecursiveSync(
|
|
1657
|
-
mkdirRecursiveSync(
|
|
1658
|
-
mkdirRecursiveSync(
|
|
1659
|
-
mkdirRecursiveSync(
|
|
1660
|
-
mkdirRecursiveSync(
|
|
1661
|
-
mkdirRecursiveSync(
|
|
1662
|
-
mkdirRecursiveSync(
|
|
1663
|
-
mkdirRecursiveSync(
|
|
1664
|
-
mkdirRecursiveSync(
|
|
1665
|
-
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"));
|
|
1666
1676
|
if (!fs9.existsSync(this.configFile) || !fs9.existsSync(this.privateKey)) {
|
|
1667
1677
|
return await this.withLock2(async () => {
|
|
1668
|
-
|
|
1669
|
-
if (this.state === 4 /* Disposed */) {
|
|
1678
|
+
if (this.state === 3 /* Disposing */ || this.state === 4 /* Disposed */) {
|
|
1670
1679
|
return;
|
|
1671
1680
|
}
|
|
1672
|
-
assert10(this.state === 1 /* Initializing */);
|
|
1673
1681
|
if (!fs9.existsSync(this.configFile)) {
|
|
1674
1682
|
fs9.writeFileSync(this.configFile, configurationFileSimpleTemplate);
|
|
1675
1683
|
}
|
|
1676
1684
|
if (!fs9.existsSync(this.privateKey)) {
|
|
1677
1685
|
debugLog("generating private key ...");
|
|
1678
1686
|
await generatePrivateKeyFile2(this.privateKey, this.keySize);
|
|
1679
|
-
await this
|
|
1687
|
+
await this.#readCertificates();
|
|
1680
1688
|
} else {
|
|
1681
|
-
await this
|
|
1689
|
+
await this.#readCertificates();
|
|
1682
1690
|
}
|
|
1683
1691
|
});
|
|
1684
1692
|
} else {
|
|
1685
|
-
await this
|
|
1693
|
+
await this.#readCertificates();
|
|
1686
1694
|
}
|
|
1687
1695
|
}
|
|
1688
1696
|
/**
|
|
@@ -1699,37 +1707,69 @@ var CertificateManager = class {
|
|
|
1699
1707
|
return;
|
|
1700
1708
|
}
|
|
1701
1709
|
if (this.state === 1 /* Initializing */) {
|
|
1702
|
-
|
|
1703
|
-
|
|
1710
|
+
if (this.#initializingPromise) {
|
|
1711
|
+
await this.#initializingPromise;
|
|
1712
|
+
}
|
|
1704
1713
|
}
|
|
1705
1714
|
try {
|
|
1706
1715
|
this.state = 3 /* Disposing */;
|
|
1707
|
-
await Promise.all(this.
|
|
1708
|
-
this.
|
|
1716
|
+
await Promise.all(this.#watchers.map((w) => w.close()));
|
|
1717
|
+
this.#watchers.forEach((w) => {
|
|
1709
1718
|
w.removeAllListeners();
|
|
1710
1719
|
});
|
|
1711
|
-
this.
|
|
1720
|
+
this.#watchers.splice(0);
|
|
1712
1721
|
} finally {
|
|
1713
1722
|
this.state = 4 /* Disposed */;
|
|
1723
|
+
_CertificateManager.#activeInstances.delete(this);
|
|
1714
1724
|
}
|
|
1715
1725
|
}
|
|
1726
|
+
/**
|
|
1727
|
+
* Force a full re-scan of all PKI folders, rebuilding
|
|
1728
|
+
* the in-memory `_thumbs` index from scratch.
|
|
1729
|
+
*
|
|
1730
|
+
* Call this after external processes have modified the
|
|
1731
|
+
* PKI folders (e.g. via `writeTrustList` or CLI tools)
|
|
1732
|
+
* to ensure the CertificateManager sees the latest
|
|
1733
|
+
* state without waiting for file-system events.
|
|
1734
|
+
*/
|
|
1735
|
+
async reloadCertificates() {
|
|
1736
|
+
await Promise.all(this.#watchers.map((w) => w.close()));
|
|
1737
|
+
for (const w of this.#watchers) {
|
|
1738
|
+
w.removeAllListeners();
|
|
1739
|
+
}
|
|
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();
|
|
1749
|
+
}
|
|
1716
1750
|
async withLock2(action) {
|
|
1717
|
-
const lockFileName =
|
|
1751
|
+
const lockFileName = path6.join(this.rootDir, "mutex.lock");
|
|
1718
1752
|
return withLock({ fileToLock: lockFileName }, async () => {
|
|
1719
1753
|
return await action();
|
|
1720
1754
|
});
|
|
1721
1755
|
}
|
|
1722
1756
|
/**
|
|
1757
|
+
* Create a self-signed certificate for this PKI's private key.
|
|
1723
1758
|
*
|
|
1724
|
-
*
|
|
1759
|
+
* The certificate is written to `params.outputFile` or
|
|
1760
|
+
* `own/certs/self_signed_certificate.pem` by default.
|
|
1725
1761
|
*
|
|
1762
|
+
* @param params - certificate parameters (subject, SANs,
|
|
1763
|
+
* validity, etc.)
|
|
1726
1764
|
*/
|
|
1727
1765
|
async createSelfSignedCertificate(params) {
|
|
1728
|
-
|
|
1766
|
+
if (typeof params.applicationUri !== "string") {
|
|
1767
|
+
throw new Error("createSelfSignedCertificate: expecting applicationUri to be a string");
|
|
1768
|
+
}
|
|
1729
1769
|
if (!fs9.existsSync(this.privateKey)) {
|
|
1730
1770
|
throw new Error(`Cannot find private key ${this.privateKey}`);
|
|
1731
1771
|
}
|
|
1732
|
-
let certificateFilename =
|
|
1772
|
+
let certificateFilename = path6.join(this.rootDir, "own/certs/self_signed_certificate.pem");
|
|
1733
1773
|
certificateFilename = params.outputFile || certificateFilename;
|
|
1734
1774
|
const _params = params;
|
|
1735
1775
|
_params.rootDir = this.rootDir;
|
|
@@ -1740,22 +1780,31 @@ var CertificateManager = class {
|
|
|
1740
1780
|
await createSelfSignedCertificate(certificateFilename, _params);
|
|
1741
1781
|
});
|
|
1742
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
|
+
*/
|
|
1743
1793
|
async createCertificateRequest(params) {
|
|
1744
|
-
|
|
1794
|
+
if (!params) {
|
|
1795
|
+
throw new Error("params is required");
|
|
1796
|
+
}
|
|
1745
1797
|
const _params = params;
|
|
1746
1798
|
if (Object.prototype.hasOwnProperty.call(_params, "rootDir")) {
|
|
1747
1799
|
throw new Error("rootDir should not be specified ");
|
|
1748
1800
|
}
|
|
1749
|
-
|
|
1750
|
-
|
|
1751
|
-
|
|
1752
|
-
_params.rootDir = path7.resolve(this.rootDir);
|
|
1753
|
-
_params.configFile = path7.resolve(this.configFile);
|
|
1754
|
-
_params.privateKey = path7.resolve(this.privateKey);
|
|
1801
|
+
_params.rootDir = path6.resolve(this.rootDir);
|
|
1802
|
+
_params.configFile = path6.resolve(this.configFile);
|
|
1803
|
+
_params.privateKey = path6.resolve(this.privateKey);
|
|
1755
1804
|
return await this.withLock2(async () => {
|
|
1756
1805
|
const now = /* @__PURE__ */ new Date();
|
|
1757
|
-
const
|
|
1758
|
-
const certificateSigningRequestFilename =
|
|
1806
|
+
const today = `${now.toISOString().slice(0, 10)}_${now.getTime()}`;
|
|
1807
|
+
const certificateSigningRequestFilename = path6.join(this.rootDir, "own/certs", `certificate_${today}.csr`);
|
|
1759
1808
|
await createCertificateSigningRequestAsync(certificateSigningRequestFilename, _params);
|
|
1760
1809
|
return certificateSigningRequestFilename;
|
|
1761
1810
|
});
|
|
@@ -1776,13 +1825,13 @@ var CertificateManager = class {
|
|
|
1776
1825
|
}
|
|
1777
1826
|
}
|
|
1778
1827
|
const pemCertificate = toPem(certificate, "CERTIFICATE");
|
|
1779
|
-
const
|
|
1780
|
-
if (this.
|
|
1828
|
+
const fingerprint = makeFingerprint(certificate);
|
|
1829
|
+
if (this.#thumbs.issuers.certs.has(fingerprint)) {
|
|
1781
1830
|
return "Good" /* Good */;
|
|
1782
1831
|
}
|
|
1783
|
-
const filename =
|
|
1832
|
+
const filename = path6.join(this.issuersCertFolder, `issuer_${buildIdealCertificateName(certificate)}.pem`);
|
|
1784
1833
|
await fs9.promises.writeFile(filename, pemCertificate, "ascii");
|
|
1785
|
-
this.
|
|
1834
|
+
this.#thumbs.issuers.certs.set(fingerprint, { certificate, filename });
|
|
1786
1835
|
if (addInTrustList) {
|
|
1787
1836
|
await this.trustCertificate(certificate);
|
|
1788
1837
|
}
|
|
@@ -1796,18 +1845,18 @@ var CertificateManager = class {
|
|
|
1796
1845
|
async addRevocationList(crl, target = "issuers") {
|
|
1797
1846
|
return await this.withLock2(async () => {
|
|
1798
1847
|
try {
|
|
1799
|
-
const index = target === "trusted" ? this.
|
|
1848
|
+
const index = target === "trusted" ? this.#thumbs.crl : this.#thumbs.issuersCrl;
|
|
1800
1849
|
const folder = target === "trusted" ? this.crlFolder : this.issuersCrlFolder;
|
|
1801
1850
|
const crlInfo = exploreCertificateRevocationList(crl);
|
|
1802
1851
|
const key = crlInfo.tbsCertList.issuerFingerprint;
|
|
1803
|
-
if (!index
|
|
1804
|
-
index
|
|
1852
|
+
if (!index.has(key)) {
|
|
1853
|
+
index.set(key, { crls: [], serialNumbers: {} });
|
|
1805
1854
|
}
|
|
1806
1855
|
const pemCertificate = toPem(crl, "X509 CRL");
|
|
1807
|
-
const filename =
|
|
1856
|
+
const filename = path6.join(folder, `crl_${buildIdealCertificateName(crl)}.pem`);
|
|
1808
1857
|
await fs9.promises.writeFile(filename, pemCertificate, "ascii");
|
|
1809
|
-
await this
|
|
1810
|
-
await this
|
|
1858
|
+
await this.#onCrlFileAdded(index, filename);
|
|
1859
|
+
await this.#waitAndCheckCRLProcessingStatus();
|
|
1811
1860
|
return "Good" /* Good */;
|
|
1812
1861
|
} catch (err) {
|
|
1813
1862
|
debugLog(err);
|
|
@@ -1826,9 +1875,9 @@ var CertificateManager = class {
|
|
|
1826
1875
|
try {
|
|
1827
1876
|
const files = await fs9.promises.readdir(folder);
|
|
1828
1877
|
for (const file of files) {
|
|
1829
|
-
const ext =
|
|
1878
|
+
const ext = path6.extname(file).toLowerCase();
|
|
1830
1879
|
if (ext === ".crl" || ext === ".pem" || ext === ".der") {
|
|
1831
|
-
await fs9.promises.unlink(
|
|
1880
|
+
await fs9.promises.unlink(path6.join(folder, file));
|
|
1832
1881
|
}
|
|
1833
1882
|
}
|
|
1834
1883
|
} catch (err) {
|
|
@@ -1836,15 +1885,13 @@ var CertificateManager = class {
|
|
|
1836
1885
|
throw err;
|
|
1837
1886
|
}
|
|
1838
1887
|
}
|
|
1839
|
-
|
|
1840
|
-
delete index[key];
|
|
1841
|
-
}
|
|
1888
|
+
index.clear();
|
|
1842
1889
|
};
|
|
1843
1890
|
if (target === "issuers" || target === "all") {
|
|
1844
|
-
await clearFolder(this.issuersCrlFolder, this.
|
|
1891
|
+
await clearFolder(this.issuersCrlFolder, this.#thumbs.issuersCrl);
|
|
1845
1892
|
}
|
|
1846
1893
|
if (target === "trusted" || target === "all") {
|
|
1847
|
-
await clearFolder(this.crlFolder, this.
|
|
1894
|
+
await clearFolder(this.crlFolder, this.#thumbs.crl);
|
|
1848
1895
|
}
|
|
1849
1896
|
}
|
|
1850
1897
|
/**
|
|
@@ -1853,9 +1900,9 @@ var CertificateManager = class {
|
|
|
1853
1900
|
* @param thumbprint - hex-encoded SHA-1 thumbprint (lowercase)
|
|
1854
1901
|
*/
|
|
1855
1902
|
async hasIssuer(thumbprint) {
|
|
1856
|
-
await this
|
|
1903
|
+
await this.#readCertificates();
|
|
1857
1904
|
const normalized = thumbprint.toLowerCase();
|
|
1858
|
-
return
|
|
1905
|
+
return this.#thumbs.issuers.certs.has(normalized);
|
|
1859
1906
|
}
|
|
1860
1907
|
/**
|
|
1861
1908
|
* Remove a trusted certificate identified by its SHA-1 thumbprint.
|
|
@@ -1865,9 +1912,9 @@ var CertificateManager = class {
|
|
|
1865
1912
|
* @returns the removed certificate buffer, or `null` if not found
|
|
1866
1913
|
*/
|
|
1867
1914
|
async removeTrustedCertificate(thumbprint) {
|
|
1868
|
-
await this
|
|
1915
|
+
await this.#readCertificates();
|
|
1869
1916
|
const normalized = thumbprint.toLowerCase();
|
|
1870
|
-
const entry = this.
|
|
1917
|
+
const entry = this.#thumbs.trusted.get(normalized);
|
|
1871
1918
|
if (!entry) {
|
|
1872
1919
|
return null;
|
|
1873
1920
|
}
|
|
@@ -1878,7 +1925,7 @@ var CertificateManager = class {
|
|
|
1878
1925
|
throw err;
|
|
1879
1926
|
}
|
|
1880
1927
|
}
|
|
1881
|
-
|
|
1928
|
+
this.#thumbs.trusted.delete(normalized);
|
|
1882
1929
|
return entry.certificate;
|
|
1883
1930
|
}
|
|
1884
1931
|
/**
|
|
@@ -1889,9 +1936,9 @@ var CertificateManager = class {
|
|
|
1889
1936
|
* @returns the removed certificate buffer, or `null` if not found
|
|
1890
1937
|
*/
|
|
1891
1938
|
async removeIssuer(thumbprint) {
|
|
1892
|
-
await this
|
|
1939
|
+
await this.#readCertificates();
|
|
1893
1940
|
const normalized = thumbprint.toLowerCase();
|
|
1894
|
-
const entry = this.
|
|
1941
|
+
const entry = this.#thumbs.issuers.certs.get(normalized);
|
|
1895
1942
|
if (!entry) {
|
|
1896
1943
|
return null;
|
|
1897
1944
|
}
|
|
@@ -1902,7 +1949,7 @@ var CertificateManager = class {
|
|
|
1902
1949
|
throw err;
|
|
1903
1950
|
}
|
|
1904
1951
|
}
|
|
1905
|
-
|
|
1952
|
+
this.#thumbs.issuers.certs.delete(normalized);
|
|
1906
1953
|
return entry.certificate;
|
|
1907
1954
|
}
|
|
1908
1955
|
/**
|
|
@@ -1915,7 +1962,7 @@ var CertificateManager = class {
|
|
|
1915
1962
|
const issuerInfo = exploreCertificate(issuerCertificate);
|
|
1916
1963
|
const issuerFingerprint = issuerInfo.tbsCertificate.subjectFingerPrint;
|
|
1917
1964
|
const processIndex = async (index) => {
|
|
1918
|
-
const crlData = index
|
|
1965
|
+
const crlData = index.get(issuerFingerprint);
|
|
1919
1966
|
if (!crlData) return;
|
|
1920
1967
|
for (const crlEntry of crlData.crls) {
|
|
1921
1968
|
try {
|
|
@@ -1926,13 +1973,13 @@ var CertificateManager = class {
|
|
|
1926
1973
|
}
|
|
1927
1974
|
}
|
|
1928
1975
|
}
|
|
1929
|
-
delete
|
|
1976
|
+
index.delete(issuerFingerprint);
|
|
1930
1977
|
};
|
|
1931
1978
|
if (target === "issuers" || target === "all") {
|
|
1932
|
-
await processIndex(this.
|
|
1979
|
+
await processIndex(this.#thumbs.issuersCrl);
|
|
1933
1980
|
}
|
|
1934
1981
|
if (target === "trusted" || target === "all") {
|
|
1935
|
-
await processIndex(this.
|
|
1982
|
+
await processIndex(this.#thumbs.crl);
|
|
1936
1983
|
}
|
|
1937
1984
|
}
|
|
1938
1985
|
/**
|
|
@@ -1964,9 +2011,23 @@ var CertificateManager = class {
|
|
|
1964
2011
|
return "BadCertificateInvalid" /* BadCertificateInvalid */;
|
|
1965
2012
|
}
|
|
1966
2013
|
if (certificates.length > 1) {
|
|
2014
|
+
const issuerFolder = this.issuersCertFolder;
|
|
2015
|
+
const issuerThumbprints = /* @__PURE__ */ new Set();
|
|
2016
|
+
const files = await fs9.promises.readdir(issuerFolder);
|
|
2017
|
+
for (const file of files) {
|
|
2018
|
+
const ext = path6.extname(file).toLowerCase();
|
|
2019
|
+
if (ext === ".pem" || ext === ".der") {
|
|
2020
|
+
try {
|
|
2021
|
+
const issuerCert = readCertificate(path6.join(issuerFolder, file));
|
|
2022
|
+
const fp = makeFingerprint(issuerCert);
|
|
2023
|
+
issuerThumbprints.add(fp);
|
|
2024
|
+
} catch (_err) {
|
|
2025
|
+
}
|
|
2026
|
+
}
|
|
2027
|
+
}
|
|
1967
2028
|
for (const issuerCert of certificates.slice(1)) {
|
|
1968
2029
|
const thumbprint = makeFingerprint(issuerCert);
|
|
1969
|
-
if (!
|
|
2030
|
+
if (!issuerThumbprints.has(thumbprint)) {
|
|
1970
2031
|
return "BadCertificateChainIncomplete" /* BadCertificateChainIncomplete */;
|
|
1971
2032
|
}
|
|
1972
2033
|
}
|
|
@@ -1987,8 +2048,8 @@ var CertificateManager = class {
|
|
|
1987
2048
|
* signed by this issuer.
|
|
1988
2049
|
*/
|
|
1989
2050
|
async isIssuerInUseByTrustedCertificate(issuerCertificate) {
|
|
1990
|
-
await this
|
|
1991
|
-
for (const entry of
|
|
2051
|
+
await this.#readCertificates();
|
|
2052
|
+
for (const entry of this.#thumbs.trusted.values()) {
|
|
1992
2053
|
if (!entry.certificate) continue;
|
|
1993
2054
|
try {
|
|
1994
2055
|
if (verifyCertificateSignature(entry.certificate, issuerCertificate)) {
|
|
@@ -2023,7 +2084,7 @@ var CertificateManager = class {
|
|
|
2023
2084
|
debugLog("Certificate has no extension 3");
|
|
2024
2085
|
return null;
|
|
2025
2086
|
}
|
|
2026
|
-
const issuerCertificates =
|
|
2087
|
+
const issuerCertificates = [...this.#thumbs.issuers.certs.values()];
|
|
2027
2088
|
const selectedIssuerCertificates = findMatchingIssuerKey(issuerCertificates, wantedIssuerKey);
|
|
2028
2089
|
if (selectedIssuerCertificates.length > 0) {
|
|
2029
2090
|
if (selectedIssuerCertificates.length > 1) {
|
|
@@ -2031,7 +2092,7 @@ var CertificateManager = class {
|
|
|
2031
2092
|
}
|
|
2032
2093
|
return selectedIssuerCertificates[0].certificate || null;
|
|
2033
2094
|
}
|
|
2034
|
-
const trustedCertificates =
|
|
2095
|
+
const trustedCertificates = [...this.#thumbs.trusted.values()];
|
|
2035
2096
|
const selectedTrustedCertificates = findMatchingIssuerKey(trustedCertificates, wantedIssuerKey);
|
|
2036
2097
|
if (selectedTrustedCertificates.length > 1) {
|
|
2037
2098
|
warningLog(
|
|
@@ -2043,53 +2104,78 @@ var CertificateManager = class {
|
|
|
2043
2104
|
return selectedTrustedCertificates.length > 0 ? selectedTrustedCertificates[0].certificate : null;
|
|
2044
2105
|
}
|
|
2045
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.
|
|
2046
2110
|
* @internal
|
|
2047
2111
|
* @private
|
|
2048
2112
|
*/
|
|
2049
|
-
async
|
|
2050
|
-
|
|
2051
|
-
|
|
2052
|
-
|
|
2053
|
-
|
|
2054
|
-
if (Object.prototype.hasOwnProperty.call(this._thumbs.rejected, fingerprint2)) {
|
|
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)) {
|
|
2055
2118
|
return "rejected";
|
|
2056
2119
|
}
|
|
2057
|
-
if (
|
|
2120
|
+
if (this.#thumbs.trusted.has(fingerprint)) {
|
|
2058
2121
|
return "trusted";
|
|
2059
2122
|
}
|
|
2060
2123
|
return "unknown";
|
|
2061
2124
|
}
|
|
2062
|
-
async
|
|
2063
|
-
|
|
2064
|
-
|
|
2065
|
-
|
|
2066
|
-
|
|
2067
|
-
|
|
2068
|
-
|
|
2069
|
-
|
|
2070
|
-
|
|
2071
|
-
|
|
2072
|
-
debugLog(" cannot find certificate ", fingerprint2.substring(0, 10), " in", this._thumbs, [status]);
|
|
2073
|
-
throw new Error("internal");
|
|
2125
|
+
async #moveCertificate(certificate, newStatus) {
|
|
2126
|
+
await this.withLock2(async () => {
|
|
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";
|
|
2074
2135
|
}
|
|
2075
|
-
|
|
2076
|
-
|
|
2077
|
-
|
|
2078
|
-
|
|
2079
|
-
|
|
2080
|
-
|
|
2081
|
-
|
|
2082
|
-
|
|
2083
|
-
|
|
2084
|
-
|
|
2085
|
-
|
|
2086
|
-
|
|
2136
|
+
debugLog("#moveCertificate", fingerprint.substring(0, 10), "from", status, "to", newStatus);
|
|
2137
|
+
if (status !== "rejected" && status !== "trusted") {
|
|
2138
|
+
throw new Error(`#moveCertificate: unexpected status '${status}' for certificate ${fingerprint.substring(0, 10)}`);
|
|
2139
|
+
}
|
|
2140
|
+
if (status !== newStatus) {
|
|
2141
|
+
const indexSrc = status === "rejected" ? this.#thumbs.rejected : this.#thumbs.trusted;
|
|
2142
|
+
const srcEntry = indexSrc.get(fingerprint);
|
|
2143
|
+
if (!srcEntry) {
|
|
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`);
|
|
2146
|
+
}
|
|
2147
|
+
const destFolder = newStatus === "trusted" ? this.trustedFolder : this.rejectedFolder;
|
|
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);
|
|
2151
|
+
await fs9.promises.rename(srcEntry.filename, certificateDest);
|
|
2152
|
+
indexSrc.delete(fingerprint);
|
|
2153
|
+
const indexDest = newStatus === "trusted" ? this.#thumbs.trusted : this.#thumbs.rejected;
|
|
2154
|
+
indexDest.set(fingerprint, { certificate, filename: certificateDest });
|
|
2155
|
+
}
|
|
2156
|
+
});
|
|
2087
2157
|
}
|
|
2088
|
-
|
|
2158
|
+
#findAssociatedCRLs(issuerCertificate) {
|
|
2089
2159
|
const issuerCertificateInfo = exploreCertificate(issuerCertificate);
|
|
2090
2160
|
const key = issuerCertificateInfo.tbsCertificate.subjectFingerPrint;
|
|
2091
|
-
return this.
|
|
2161
|
+
return this.#thumbs.issuersCrl.get(key) ?? this.#thumbs.crl.get(key) ?? null;
|
|
2092
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
|
+
*/
|
|
2093
2179
|
async isCertificateRevoked(certificate, issuerCertificate) {
|
|
2094
2180
|
if (isSelfSigned3(certificate)) {
|
|
2095
2181
|
return "Good" /* Good */;
|
|
@@ -2100,913 +2186,251 @@ var CertificateManager = class {
|
|
|
2100
2186
|
if (!issuerCertificate) {
|
|
2101
2187
|
return "BadCertificateChainIncomplete" /* BadCertificateChainIncomplete */;
|
|
2102
2188
|
}
|
|
2103
|
-
const crls = this
|
|
2189
|
+
const crls = this.#findAssociatedCRLs(issuerCertificate);
|
|
2104
2190
|
if (!crls) {
|
|
2105
2191
|
return "BadCertificateRevocationUnknown" /* BadCertificateRevocationUnknown */;
|
|
2106
2192
|
}
|
|
2107
2193
|
const certInfo = exploreCertificate(certificate);
|
|
2108
2194
|
const serialNumber = certInfo.tbsCertificate.serialNumber || certInfo.tbsCertificate.extensions?.authorityKeyIdentifier?.serial || "";
|
|
2109
2195
|
const key = certInfo.tbsCertificate.extensions?.authorityKeyIdentifier?.authorityCertIssuerFingerPrint || "<unknown>";
|
|
2110
|
-
const crl2 = this.
|
|
2196
|
+
const crl2 = this.#thumbs.crl.get(key) ?? null;
|
|
2111
2197
|
if (crls.serialNumbers[serialNumber] || crl2?.serialNumbers[serialNumber]) {
|
|
2112
2198
|
return "BadCertificateRevoked" /* BadCertificateRevoked */;
|
|
2113
2199
|
}
|
|
2114
2200
|
return "Good" /* Good */;
|
|
2115
2201
|
}
|
|
2116
|
-
|
|
2117
|
-
|
|
2118
|
-
queue = [];
|
|
2119
|
-
|
|
2120
|
-
this
|
|
2121
|
-
this
|
|
2122
|
-
if (this
|
|
2123
|
-
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();
|
|
2124
2210
|
}
|
|
2125
2211
|
}
|
|
2126
|
-
async
|
|
2212
|
+
async #processNextCrl() {
|
|
2127
2213
|
try {
|
|
2128
|
-
const nextCRL = this
|
|
2214
|
+
const nextCRL = this.#queue.shift();
|
|
2129
2215
|
if (!nextCRL) return;
|
|
2130
2216
|
const { index, filename } = nextCRL;
|
|
2131
2217
|
const crl = await readCertificateRevocationList(filename);
|
|
2132
2218
|
const crlInfo = exploreCertificateRevocationList(crl);
|
|
2133
2219
|
debugLog(chalk6.cyan("add CRL in folder "), filename);
|
|
2134
|
-
const
|
|
2135
|
-
|
|
2136
|
-
crls: [],
|
|
2137
|
-
|
|
2138
|
-
};
|
|
2139
|
-
|
|
2140
|
-
const serialNumbers = index[fingerprint2].serialNumbers;
|
|
2220
|
+
const fingerprint = crlInfo.tbsCertList.issuerFingerprint;
|
|
2221
|
+
if (!index.has(fingerprint)) {
|
|
2222
|
+
index.set(fingerprint, { crls: [], serialNumbers: {} });
|
|
2223
|
+
}
|
|
2224
|
+
const data = index.get(fingerprint) || { crls: [], serialNumbers: {} };
|
|
2225
|
+
data.crls.push({ crlInfo, filename });
|
|
2141
2226
|
for (const revokedCertificate of crlInfo.tbsCertList.revokedCertificates) {
|
|
2142
2227
|
const serialNumber = revokedCertificate.userCertificate;
|
|
2143
|
-
if (!serialNumbers[serialNumber]) {
|
|
2144
|
-
serialNumbers[serialNumber] = revokedCertificate.revocationDate;
|
|
2228
|
+
if (!data.serialNumbers[serialNumber]) {
|
|
2229
|
+
data.serialNumbers[serialNumber] = revokedCertificate.revocationDate;
|
|
2145
2230
|
}
|
|
2146
2231
|
}
|
|
2147
|
-
debugLog(chalk6.cyan("CRL"),
|
|
2232
|
+
debugLog(chalk6.cyan("CRL"), fingerprint, "serial numbers = ", Object.keys(data.serialNumbers));
|
|
2148
2233
|
} catch (err) {
|
|
2149
2234
|
debugLog("CRL filename error =");
|
|
2150
2235
|
debugLog(err);
|
|
2151
2236
|
}
|
|
2152
|
-
this
|
|
2153
|
-
if (this
|
|
2154
|
-
if (this
|
|
2155
|
-
this
|
|
2156
|
-
this
|
|
2237
|
+
this.#pendingCrlToProcess -= 1;
|
|
2238
|
+
if (this.#pendingCrlToProcess === 0) {
|
|
2239
|
+
if (this.#onCrlProcess) {
|
|
2240
|
+
this.#onCrlProcess();
|
|
2241
|
+
this.#onCrlProcess = void 0;
|
|
2157
2242
|
}
|
|
2158
2243
|
} else {
|
|
2159
|
-
this
|
|
2244
|
+
this.#processNextCrl();
|
|
2160
2245
|
}
|
|
2161
2246
|
}
|
|
2162
|
-
async
|
|
2163
|
-
if (this
|
|
2247
|
+
async #readCertificates() {
|
|
2248
|
+
if (this.#readCertificatesCalled) {
|
|
2164
2249
|
return;
|
|
2165
2250
|
}
|
|
2166
|
-
this
|
|
2167
|
-
const
|
|
2168
|
-
|
|
2169
|
-
|
|
2170
|
-
|
|
2171
|
-
|
|
2172
|
-
stabilityThreshold: 2e3,
|
|
2173
|
-
pollInterval: 600
|
|
2174
|
-
}
|
|
2251
|
+
this.#readCertificatesCalled = true;
|
|
2252
|
+
const usePolling = process.env.OPCUA_PKI_USE_POLLING === "true";
|
|
2253
|
+
const chokidarOptions = {
|
|
2254
|
+
usePolling,
|
|
2255
|
+
...usePolling ? { interval: Math.min(10 * 60 * 1e3, Math.max(100, this.folderPoolingInterval)) } : {},
|
|
2256
|
+
persistent: false
|
|
2175
2257
|
};
|
|
2176
|
-
|
|
2177
|
-
|
|
2178
|
-
|
|
2179
|
-
|
|
2180
|
-
|
|
2181
|
-
|
|
2182
|
-
|
|
2183
|
-
w.on("add", (filename, stat) => {
|
|
2184
|
-
stat;
|
|
2185
|
-
this._on_crl_file_added(index, filename);
|
|
2186
|
-
});
|
|
2187
|
-
w.on("change", (path9, stat) => {
|
|
2188
|
-
debugLog("change in folder ", folder, path9, stat);
|
|
2189
|
-
});
|
|
2190
|
-
this._watchers.push(w);
|
|
2191
|
-
w.on("ready", () => {
|
|
2192
|
-
resolve();
|
|
2193
|
-
});
|
|
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;
|
|
2194
2265
|
});
|
|
2195
|
-
|
|
2196
|
-
|
|
2197
|
-
|
|
2198
|
-
|
|
2199
|
-
|
|
2200
|
-
debugLog(chalk6.cyan(`unlink in folder ${folder}`), filename);
|
|
2201
|
-
const h = this._filenameToHash[filename];
|
|
2202
|
-
if (h && index[h]) {
|
|
2203
|
-
delete index[h];
|
|
2266
|
+
const w = chokidar.watch(folder, chokidarOptions);
|
|
2267
|
+
const unreffAll = () => {
|
|
2268
|
+
fs9.watch = origWatch;
|
|
2269
|
+
for (const h of capturedHandles) {
|
|
2270
|
+
h.unref();
|
|
2204
2271
|
}
|
|
2205
|
-
}
|
|
2206
|
-
w
|
|
2207
|
-
|
|
2208
|
-
|
|
2209
|
-
|
|
2210
|
-
|
|
2211
|
-
|
|
2212
|
-
|
|
2213
|
-
|
|
2214
|
-
|
|
2215
|
-
|
|
2216
|
-
|
|
2217
|
-
|
|
2218
|
-
|
|
2219
|
-
|
|
2220
|
-
|
|
2221
|
-
|
|
2222
|
-
|
|
2223
|
-
);
|
|
2224
|
-
|
|
2225
|
-
|
|
2226
|
-
|
|
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);
|
|
2288
|
+
w.on("unlink", (filename) => {
|
|
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
|
+
}
|
|
2227
2294
|
}
|
|
2228
2295
|
});
|
|
2229
|
-
w.on("
|
|
2230
|
-
|
|
2231
|
-
debugLog("change in folder ", folder, path9);
|
|
2296
|
+
w.on("add", (filename) => {
|
|
2297
|
+
this.#onCrlFileAdded(index, filename);
|
|
2232
2298
|
});
|
|
2233
|
-
|
|
2234
|
-
|
|
2235
|
-
|
|
2236
|
-
|
|
2237
|
-
|
|
2238
|
-
|
|
2239
|
-
|
|
2299
|
+
w.on("change", (changedPath) => {
|
|
2300
|
+
debugLog("change in folder ", folder, changedPath);
|
|
2301
|
+
});
|
|
2302
|
+
this.#watchers.push(w);
|
|
2303
|
+
w.on("ready", () => {
|
|
2304
|
+
unreffAll();
|
|
2305
|
+
resolve();
|
|
2240
2306
|
});
|
|
2241
|
-
}
|
|
2242
|
-
const promises = [
|
|
2243
|
-
_walkAllFiles.bind(this, this.trustedFolder, this._thumbs.trusted)(),
|
|
2244
|
-
_walkAllFiles.bind(this, this.issuersCertFolder, this._thumbs.issuers.certs)(),
|
|
2245
|
-
_walkAllFiles.bind(this, this.rejectedFolder, this._thumbs.rejected)(),
|
|
2246
|
-
_walkCRLFiles.bind(this, this.crlFolder, this._thumbs.crl)(),
|
|
2247
|
-
_walkCRLFiles.bind(this, this.issuersCrlFolder, this._thumbs.issuersCrl)()
|
|
2248
|
-
];
|
|
2249
|
-
await Promise.all(promises);
|
|
2250
|
-
await this.waitAndCheckCRLProcessingStatus();
|
|
2251
|
-
}
|
|
2252
|
-
// make sure that all crls have been processed.
|
|
2253
|
-
async waitAndCheckCRLProcessingStatus() {
|
|
2254
|
-
return new Promise((resolve, reject) => {
|
|
2255
|
-
if (this._pending_crl_to_process === 0) {
|
|
2256
|
-
setImmediate(resolve);
|
|
2257
|
-
return;
|
|
2258
|
-
}
|
|
2259
|
-
if (this._on_crl_process) {
|
|
2260
|
-
return reject(new Error("Internal Error"));
|
|
2261
|
-
}
|
|
2262
|
-
this._on_crl_process = resolve;
|
|
2263
|
-
});
|
|
2264
|
-
}
|
|
2265
|
-
};
|
|
2266
|
-
|
|
2267
|
-
// packages/node-opcua-pki/lib/ca/crypto_create_CA.ts
|
|
2268
|
-
import commandLineArgs from "command-line-args";
|
|
2269
|
-
import commandLineUsage from "command-line-usage";
|
|
2270
|
-
var epilog = "Copyright (c) sterfive - node-opcua - 2017-2026";
|
|
2271
|
-
function get_offset_date(date, nbDays) {
|
|
2272
|
-
const d = new Date(date.getTime());
|
|
2273
|
-
d.setDate(d.getDate() + nbDays);
|
|
2274
|
-
return d;
|
|
2275
|
-
}
|
|
2276
|
-
var today = /* @__PURE__ */ new Date();
|
|
2277
|
-
var yesterday = get_offset_date(today, -1);
|
|
2278
|
-
var two_years_ago = get_offset_date(today, -2 * 365);
|
|
2279
|
-
var next_year = get_offset_date(today, 365);
|
|
2280
|
-
var gLocalConfig = {};
|
|
2281
|
-
var g_certificateAuthority;
|
|
2282
|
-
async function construct_CertificateAuthority2(subject) {
|
|
2283
|
-
assert11(typeof gLocalConfig.CAFolder === "string", "expecting a CAFolder in config");
|
|
2284
|
-
assert11(typeof gLocalConfig.keySize === "number", "expecting a keySize in config");
|
|
2285
|
-
if (!g_certificateAuthority) {
|
|
2286
|
-
g_certificateAuthority = new CertificateAuthority({
|
|
2287
|
-
keySize: gLocalConfig.keySize,
|
|
2288
|
-
location: gLocalConfig.CAFolder,
|
|
2289
|
-
subject
|
|
2290
|
-
});
|
|
2291
|
-
await g_certificateAuthority.initialize();
|
|
2292
|
-
}
|
|
2293
|
-
}
|
|
2294
|
-
var certificateManager;
|
|
2295
|
-
async function construct_CertificateManager() {
|
|
2296
|
-
assert11(typeof gLocalConfig.PKIFolder === "string", "expecting a PKIFolder in config");
|
|
2297
|
-
if (!certificateManager) {
|
|
2298
|
-
certificateManager = new CertificateManager({
|
|
2299
|
-
keySize: gLocalConfig.keySize,
|
|
2300
|
-
location: gLocalConfig.PKIFolder
|
|
2301
2307
|
});
|
|
2302
|
-
await certificateManager.initialize();
|
|
2303
2308
|
}
|
|
2304
|
-
|
|
2305
|
-
|
|
2306
|
-
|
|
2307
|
-
|
|
2308
|
-
|
|
2309
|
-
|
|
2310
|
-
|
|
2311
|
-
const rootFolder = find_module_root_folder();
|
|
2312
|
-
const configName = "pki_config.example.js";
|
|
2313
|
-
let default_config_template2 = path8.join(rootFolder, "bin", configName);
|
|
2314
|
-
if (!fs10.existsSync(default_config_template2)) {
|
|
2315
|
-
default_config_template2 = path8.join(__dirname, "..", configName);
|
|
2316
|
-
if (!fs10.existsSync(default_config_template2)) {
|
|
2317
|
-
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);
|
|
2318
2316
|
}
|
|
2319
|
-
}
|
|
2320
|
-
return default_config_template2;
|
|
2321
|
-
}
|
|
2322
|
-
const default_config_template = find_default_config_template();
|
|
2323
|
-
assert11(fs10.existsSync(default_config_template));
|
|
2324
|
-
const default_config_template_content = fs10.readFileSync(default_config_template, "utf8");
|
|
2325
|
-
return default_config_template_content;
|
|
2326
|
-
}
|
|
2327
|
-
function find_module_root_folder() {
|
|
2328
|
-
let rootFolder = path8.join(__dirname);
|
|
2329
|
-
for (let i = 0; i < 4; i++) {
|
|
2330
|
-
if (fs10.existsSync(path8.join(rootFolder, "package.json"))) {
|
|
2331
|
-
return rootFolder;
|
|
2332
|
-
}
|
|
2333
|
-
rootFolder = path8.join(rootFolder, "..");
|
|
2334
|
-
}
|
|
2335
|
-
assert11(fs10.existsSync(path8.join(rootFolder, "package.json")), "root folder must have a package.json file");
|
|
2336
|
-
return rootFolder;
|
|
2337
|
-
}
|
|
2338
|
-
async function readConfiguration(argv) {
|
|
2339
|
-
if (argv.silent) {
|
|
2340
|
-
g_config.silent = true;
|
|
2341
|
-
} else {
|
|
2342
|
-
g_config.silent = false;
|
|
2343
|
-
}
|
|
2344
|
-
const fqdn2 = await extractFullyQualifiedDomainName();
|
|
2345
|
-
const hostname = os4.hostname();
|
|
2346
|
-
let certificateDir;
|
|
2347
|
-
function performSubstitution(str) {
|
|
2348
|
-
str = str.replace("{CWD}", process.cwd());
|
|
2349
|
-
if (certificateDir) {
|
|
2350
|
-
str = str.replace("{root}", certificateDir);
|
|
2351
|
-
}
|
|
2352
|
-
if (gLocalConfig?.PKIFolder) {
|
|
2353
|
-
str = str.replace("{PKIFolder}", gLocalConfig.PKIFolder);
|
|
2354
|
-
}
|
|
2355
|
-
str = str.replace("{hostname}", hostname);
|
|
2356
|
-
str = str.replace("%FQDN%", fqdn2);
|
|
2357
|
-
return str;
|
|
2358
|
-
}
|
|
2359
|
-
function prepare(file) {
|
|
2360
|
-
const tmp = path8.resolve(performSubstitution(file));
|
|
2361
|
-
return makePath(tmp);
|
|
2362
|
-
}
|
|
2363
|
-
certificateDir = argv.root;
|
|
2364
|
-
assert11(typeof certificateDir === "string");
|
|
2365
|
-
certificateDir = prepare(certificateDir);
|
|
2366
|
-
mkdirRecursiveSync(certificateDir);
|
|
2367
|
-
assert11(fs10.existsSync(certificateDir));
|
|
2368
|
-
const default_config = path8.join(certificateDir, "config.js");
|
|
2369
|
-
if (!fs10.existsSync(default_config)) {
|
|
2370
|
-
debugLog(chalk7.yellow(" Creating default g_config file "), chalk7.cyan(default_config));
|
|
2371
|
-
const default_config_template_content = default_template_content();
|
|
2372
|
-
fs10.writeFileSync(default_config, default_config_template_content);
|
|
2373
|
-
} else {
|
|
2374
|
-
debugLog(chalk7.yellow(" using g_config file "), chalk7.cyan(default_config));
|
|
2375
|
-
}
|
|
2376
|
-
if (!fs10.existsSync(default_config)) {
|
|
2377
|
-
debugLog(chalk7.redBright(" cannot find config file ", default_config));
|
|
2378
|
-
}
|
|
2379
|
-
const defaultRandomFile = path8.join(path8.dirname(default_config), "random.rnd");
|
|
2380
|
-
setEnv("RANDFILE", defaultRandomFile);
|
|
2381
|
-
const _require = createRequire(__filename);
|
|
2382
|
-
gLocalConfig = _require(default_config);
|
|
2383
|
-
gLocalConfig.subject = new Subject5(gLocalConfig.subject || "");
|
|
2384
|
-
if (argv.subject) {
|
|
2385
|
-
gLocalConfig.subject = new Subject5(argv.subject);
|
|
2386
|
-
}
|
|
2387
|
-
if (!gLocalConfig.subject.commonName) {
|
|
2388
|
-
throw new Error("subject must have a Common Name");
|
|
2389
|
-
}
|
|
2390
|
-
gLocalConfig.certificateDir = certificateDir;
|
|
2391
|
-
let CAFolder = argv.CAFolder || path8.join(certificateDir, "CA");
|
|
2392
|
-
CAFolder = prepare(CAFolder);
|
|
2393
|
-
gLocalConfig.CAFolder = CAFolder;
|
|
2394
|
-
gLocalConfig.PKIFolder = path8.join(gLocalConfig.certificateDir, "PKI");
|
|
2395
|
-
if (argv.PKIFolder) {
|
|
2396
|
-
gLocalConfig.PKIFolder = prepare(argv.PKIFolder);
|
|
2397
|
-
}
|
|
2398
|
-
gLocalConfig.PKIFolder = prepare(gLocalConfig.PKIFolder);
|
|
2399
|
-
if (argv.privateKey) {
|
|
2400
|
-
gLocalConfig.privateKey = prepare(argv.privateKey);
|
|
2401
|
-
}
|
|
2402
|
-
if (argv.applicationUri) {
|
|
2403
|
-
gLocalConfig.applicationUri = performSubstitution(argv.applicationUri);
|
|
2404
|
-
}
|
|
2405
|
-
if (argv.output) {
|
|
2406
|
-
gLocalConfig.outputFile = argv.output;
|
|
2407
|
-
}
|
|
2408
|
-
gLocalConfig.altNames = [];
|
|
2409
|
-
if (argv.altNames) {
|
|
2410
|
-
gLocalConfig.altNames = argv.altNames.split(";");
|
|
2411
|
-
}
|
|
2412
|
-
gLocalConfig.dns = [getFullyQualifiedDomainName()];
|
|
2413
|
-
if (argv.dns) {
|
|
2414
|
-
gLocalConfig.dns = argv.dns.split(",").map(performSubstitution);
|
|
2415
|
-
}
|
|
2416
|
-
gLocalConfig.ip = [];
|
|
2417
|
-
if (argv.ip) {
|
|
2418
|
-
gLocalConfig.ip = argv.ip.split(",");
|
|
2419
|
-
}
|
|
2420
|
-
if (argv.keySize) {
|
|
2421
|
-
const v = argv.keySize;
|
|
2422
|
-
if (v !== 1024 && v !== 2048 && v !== 3072 && v !== 4096) {
|
|
2423
|
-
throw new Error(`invalid keysize specified ${v} should be 1024,2048,3072 or 4096`);
|
|
2424
|
-
}
|
|
2425
|
-
gLocalConfig.keySize = argv.keySize;
|
|
2426
|
-
}
|
|
2427
|
-
if (argv.validity) {
|
|
2428
|
-
gLocalConfig.validity = argv.validity;
|
|
2429
|
-
}
|
|
2430
|
-
}
|
|
2431
|
-
async function createDefaultCertificate(base_name, prefix, key_length, applicationUri, dev) {
|
|
2432
|
-
assert11(key_length === 1024 || key_length === 2048 || key_length === 3072 || key_length === 4096);
|
|
2433
|
-
const private_key_file = makePath(base_name, `${prefix}key_${key_length}.pem`);
|
|
2434
|
-
const public_key_file = makePath(base_name, `${prefix}public_key_${key_length}.pub`);
|
|
2435
|
-
const certificate_file = makePath(base_name, `${prefix}cert_${key_length}.pem`);
|
|
2436
|
-
const certificate_file_outofdate = makePath(base_name, `${prefix}cert_${key_length}_outofdate.pem`);
|
|
2437
|
-
const certificate_file_not_active_yet = makePath(base_name, `${prefix}cert_${key_length}_not_active_yet.pem`);
|
|
2438
|
-
const certificate_revoked = makePath(base_name, `${prefix}cert_${key_length}_revoked.pem`);
|
|
2439
|
-
const self_signed_certificate_file = makePath(base_name, `${prefix}selfsigned_cert_${key_length}.pem`);
|
|
2440
|
-
const fqdn2 = getFullyQualifiedDomainName();
|
|
2441
|
-
const hostname = os4.hostname();
|
|
2442
|
-
const dns2 = [
|
|
2443
|
-
// for conformance reason, localhost shall not be present in the DNS field of COP
|
|
2444
|
-
// ***FORBIDEN** "localhost",
|
|
2445
|
-
getFullyQualifiedDomainName()
|
|
2446
|
-
];
|
|
2447
|
-
if (hostname !== fqdn2) {
|
|
2448
|
-
dns2.push(hostname);
|
|
2449
|
-
}
|
|
2450
|
-
const ip = [];
|
|
2451
|
-
async function createCertificateIfNotExist(certificate, private_key, applicationUri2, startDate, validity) {
|
|
2452
|
-
if (fs10.existsSync(certificate)) {
|
|
2453
|
-
warningLog(chalk7.yellow(" certificate"), chalk7.cyan(certificate), chalk7.yellow(" already exists => skipping"));
|
|
2454
|
-
return "";
|
|
2455
|
-
} else {
|
|
2456
|
-
return await createCertificate(certificate, private_key, applicationUri2, startDate, validity);
|
|
2457
|
-
}
|
|
2458
|
-
}
|
|
2459
|
-
async function createCertificate(certificate, privateKey, applicationUri2, startDate, validity) {
|
|
2460
|
-
const certificateSigningRequestFile = `${certificate}.csr`;
|
|
2461
|
-
const configFile = makePath(base_name, "../certificates/PKI/own/openssl.cnf");
|
|
2462
|
-
const dns3 = [os4.hostname()];
|
|
2463
|
-
const ip2 = ["127.0.0.1"];
|
|
2464
|
-
const params = {
|
|
2465
|
-
applicationUri: applicationUri2,
|
|
2466
|
-
privateKey,
|
|
2467
|
-
rootDir: ".",
|
|
2468
|
-
configFile,
|
|
2469
|
-
dns: dns3,
|
|
2470
|
-
ip: ip2,
|
|
2471
|
-
purpose: CertificatePurpose2.ForApplication
|
|
2472
|
-
};
|
|
2473
|
-
await createCertificateSigningRequestWithOpenSSL(certificateSigningRequestFile, params);
|
|
2474
|
-
return await g_certificateAuthority.signCertificateRequest(certificate, certificateSigningRequestFile, {
|
|
2475
|
-
applicationUri: applicationUri2,
|
|
2476
|
-
dns: dns3,
|
|
2477
|
-
ip: ip2,
|
|
2478
|
-
startDate,
|
|
2479
|
-
validity
|
|
2480
2317
|
});
|
|
2481
|
-
|
|
2482
|
-
|
|
2483
|
-
|
|
2484
|
-
|
|
2485
|
-
|
|
2486
|
-
|
|
2487
|
-
|
|
2488
|
-
|
|
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
|
+
}
|
|
2489
2336
|
});
|
|
2490
|
-
|
|
2491
|
-
|
|
2492
|
-
|
|
2493
|
-
|
|
2494
|
-
|
|
2495
|
-
|
|
2496
|
-
|
|
2497
|
-
|
|
2498
|
-
} else {
|
|
2499
|
-
await generatePrivateKeyFile3(privateKey, keyLength);
|
|
2500
|
-
}
|
|
2501
|
-
}
|
|
2502
|
-
displaySubtitle(` create private key :${private_key_file}`);
|
|
2503
|
-
await createPrivateKeyIfNotExist(private_key_file, key_length);
|
|
2504
|
-
displaySubtitle(` extract public key ${public_key_file} from private key `);
|
|
2505
|
-
await getPublicKeyFromPrivateKey(private_key_file, public_key_file);
|
|
2506
|
-
displaySubtitle(` create Certificate ${certificate_file}`);
|
|
2507
|
-
await createCertificateIfNotExist(certificate_file, private_key_file, applicationUri, yesterday, 365);
|
|
2508
|
-
displaySubtitle(` create self signed Certificate ${self_signed_certificate_file}`);
|
|
2509
|
-
if (fs10.existsSync(self_signed_certificate_file)) {
|
|
2510
|
-
return;
|
|
2511
|
-
}
|
|
2512
|
-
await createSelfSignedCertificate2(self_signed_certificate_file, private_key_file, applicationUri, yesterday, 365);
|
|
2513
|
-
if (dev) {
|
|
2514
|
-
await createCertificateIfNotExist(certificate_file_outofdate, private_key_file, applicationUri, two_years_ago, 365);
|
|
2515
|
-
await createCertificateIfNotExist(certificate_file_not_active_yet, private_key_file, applicationUri, next_year, 365);
|
|
2516
|
-
if (!fs10.existsSync(certificate_revoked)) {
|
|
2517
|
-
const certificate = await createCertificateIfNotExist(
|
|
2518
|
-
certificate_revoked,
|
|
2519
|
-
private_key_file,
|
|
2520
|
-
`${applicationUri}Revoked`,
|
|
2521
|
-
// make sure we used a uniq URI here
|
|
2522
|
-
yesterday,
|
|
2523
|
-
365
|
|
2524
|
-
);
|
|
2525
|
-
warningLog(" certificate to revoke => ", certificate);
|
|
2526
|
-
revoke_certificate(certificate_revoked);
|
|
2527
|
-
}
|
|
2528
|
-
}
|
|
2529
|
-
}
|
|
2530
|
-
async function wrap(func) {
|
|
2531
|
-
try {
|
|
2532
|
-
await func();
|
|
2533
|
-
} catch (err) {
|
|
2534
|
-
console.log(err.message);
|
|
2535
|
-
}
|
|
2536
|
-
}
|
|
2537
|
-
async function create_default_certificates(dev) {
|
|
2538
|
-
assert11(gLocalConfig);
|
|
2539
|
-
const base_name = gLocalConfig.certificateDir || "";
|
|
2540
|
-
assert11(fs10.existsSync(base_name));
|
|
2541
|
-
let clientURN;
|
|
2542
|
-
let serverURN;
|
|
2543
|
-
let discoveryServerURN;
|
|
2544
|
-
wrap(async () => {
|
|
2545
|
-
await extractFullyQualifiedDomainName();
|
|
2546
|
-
const hostname = os4.hostname();
|
|
2547
|
-
const fqdn2 = getFullyQualifiedDomainName();
|
|
2548
|
-
warningLog(chalk7.yellow(" hostname = "), chalk7.cyan(hostname));
|
|
2549
|
-
warningLog(chalk7.yellow(" fqdn = "), chalk7.cyan(fqdn2));
|
|
2550
|
-
clientURN = makeApplicationUrn(hostname, "NodeOPCUA-Client");
|
|
2551
|
-
serverURN = makeApplicationUrn(hostname, "NodeOPCUA-Server");
|
|
2552
|
-
discoveryServerURN = makeApplicationUrn(hostname, "NodeOPCUA-DiscoveryServer");
|
|
2553
|
-
displayTitle("Create Application Certificate for Server & its private key");
|
|
2554
|
-
await createDefaultCertificate(base_name, "client_", 1024, clientURN, dev);
|
|
2555
|
-
await createDefaultCertificate(base_name, "client_", 2048, clientURN, dev);
|
|
2556
|
-
await createDefaultCertificate(base_name, "client_", 3072, clientURN, dev);
|
|
2557
|
-
await createDefaultCertificate(base_name, "client_", 4096, clientURN, dev);
|
|
2558
|
-
displayTitle("Create Application Certificate for Client & its private key");
|
|
2559
|
-
await createDefaultCertificate(base_name, "server_", 1024, serverURN, dev);
|
|
2560
|
-
await createDefaultCertificate(base_name, "server_", 2048, serverURN, dev);
|
|
2561
|
-
await createDefaultCertificate(base_name, "server_", 3072, serverURN, dev);
|
|
2562
|
-
await createDefaultCertificate(base_name, "server_", 4096, serverURN, dev);
|
|
2563
|
-
displayTitle("Create Application Certificate for DiscoveryServer & its private key");
|
|
2564
|
-
await createDefaultCertificate(base_name, "discoveryServer_", 1024, discoveryServerURN, dev);
|
|
2565
|
-
await createDefaultCertificate(base_name, "discoveryServer_", 2048, discoveryServerURN, dev);
|
|
2566
|
-
await createDefaultCertificate(base_name, "discoveryServer_", 3072, discoveryServerURN, dev);
|
|
2567
|
-
await createDefaultCertificate(base_name, "discoveryServer_", 4096, discoveryServerURN, dev);
|
|
2568
|
-
});
|
|
2569
|
-
}
|
|
2570
|
-
async function createDefaultCertificates(dev) {
|
|
2571
|
-
await construct_CertificateAuthority2("");
|
|
2572
|
-
await construct_CertificateManager();
|
|
2573
|
-
await create_default_certificates(dev);
|
|
2574
|
-
}
|
|
2575
|
-
var commonOptions = [
|
|
2576
|
-
{
|
|
2577
|
-
name: "root",
|
|
2578
|
-
alias: "r",
|
|
2579
|
-
type: String,
|
|
2580
|
-
defaultValue: "{CWD}/certificates",
|
|
2581
|
-
description: "the location of the Certificate folder"
|
|
2582
|
-
},
|
|
2583
|
-
{
|
|
2584
|
-
name: "CAFolder",
|
|
2585
|
-
alias: "c",
|
|
2586
|
-
type: String,
|
|
2587
|
-
defaultValue: "{root}/CA",
|
|
2588
|
-
description: "the location of the Certificate Authority folder"
|
|
2589
|
-
},
|
|
2590
|
-
{ name: "PKIFolder", type: String, defaultValue: "{root}/PKI", description: "the location of the Public Key Infrastructure" },
|
|
2591
|
-
{ name: "silent", type: Boolean, defaultValue: false, description: "minimize output" },
|
|
2592
|
-
{
|
|
2593
|
-
name: "privateKey",
|
|
2594
|
-
alias: "p",
|
|
2595
|
-
type: String,
|
|
2596
|
-
defaultValue: "{PKIFolder}/own/private_key.pem",
|
|
2597
|
-
description: "the private key to use to generate certificate"
|
|
2598
|
-
},
|
|
2599
|
-
{
|
|
2600
|
-
name: "keySize",
|
|
2601
|
-
alias: "k",
|
|
2602
|
-
type: Number,
|
|
2603
|
-
defaultValue: 2048,
|
|
2604
|
-
description: "the private key size in bits (1024|2048|3072|4096)"
|
|
2605
|
-
},
|
|
2606
|
-
{ name: "help", alias: "h", type: Boolean, description: "display this help" }
|
|
2607
|
-
];
|
|
2608
|
-
function getOptions(names) {
|
|
2609
|
-
return commonOptions.filter((o) => names.includes(o.name) || o.name === "help" || o.name === "silent");
|
|
2610
|
-
}
|
|
2611
|
-
function showHelp(command, description, options, usage) {
|
|
2612
|
-
const sections = [
|
|
2613
|
-
{
|
|
2614
|
-
header: `Command: ${command}`,
|
|
2615
|
-
content: description
|
|
2616
|
-
},
|
|
2617
|
-
{
|
|
2618
|
-
header: "Usage",
|
|
2619
|
-
content: usage || `$0 ${command} [options]`
|
|
2620
|
-
},
|
|
2621
|
-
{
|
|
2622
|
-
header: "Options",
|
|
2623
|
-
optionList: options
|
|
2624
|
-
}
|
|
2625
|
-
];
|
|
2626
|
-
console.log(commandLineUsage(sections));
|
|
2627
|
-
}
|
|
2628
|
-
async function main(argumentsList) {
|
|
2629
|
-
const mainDefinitions = [{ name: "command", defaultOption: true }];
|
|
2630
|
-
let mainOptions;
|
|
2631
|
-
try {
|
|
2632
|
-
mainOptions = commandLineArgs(mainDefinitions, { argv: argumentsList, stopAtFirstUnknown: true });
|
|
2633
|
-
} catch (err) {
|
|
2634
|
-
console.log(err.message);
|
|
2635
|
-
return;
|
|
2636
|
-
}
|
|
2637
|
-
const argv = mainOptions._unknown || [];
|
|
2638
|
-
const command = mainOptions.command;
|
|
2639
|
-
if (!command || command === "help") {
|
|
2640
|
-
console.log(
|
|
2641
|
-
commandLineUsage([
|
|
2642
|
-
{
|
|
2643
|
-
header: "node-opcua-pki",
|
|
2644
|
-
content: `PKI management for node-opcua
|
|
2645
|
-
|
|
2646
|
-
${epilog}`
|
|
2647
|
-
},
|
|
2648
|
-
{
|
|
2649
|
-
header: "Commands",
|
|
2650
|
-
content: [
|
|
2651
|
-
{ name: "demo", summary: "create default certificate for node-opcua demos" },
|
|
2652
|
-
{ name: "createCA", summary: "create a Certificate Authority" },
|
|
2653
|
-
{ name: "createPKI", summary: "create a Public Key Infrastructure" },
|
|
2654
|
-
{ name: "certificate", summary: "create a new certificate" },
|
|
2655
|
-
{ name: "revoke <certificateFile>", summary: "revoke a existing certificate" },
|
|
2656
|
-
{ name: "csr", summary: "create a certificate signing request" },
|
|
2657
|
-
{ name: "sign", summary: "validate a certificate signing request and generate a certificate" },
|
|
2658
|
-
{ name: "dump <certificateFile>", summary: "display a certificate" },
|
|
2659
|
-
{ name: "toder <pemCertificate>", summary: "convert a certificate to a DER format with finger print" },
|
|
2660
|
-
{ name: "fingerprint <certificateFile>", summary: "print the certificate fingerprint" },
|
|
2661
|
-
{ name: "version", summary: "display the version number" }
|
|
2662
|
-
]
|
|
2663
|
-
}
|
|
2664
|
-
])
|
|
2665
|
-
);
|
|
2666
|
-
return;
|
|
2667
|
-
}
|
|
2668
|
-
if (command === "version") {
|
|
2669
|
-
const rootFolder = find_module_root_folder();
|
|
2670
|
-
const pkg = JSON.parse(fs10.readFileSync(path8.join(rootFolder, "package.json"), "utf-8"));
|
|
2671
|
-
console.log(pkg.version);
|
|
2672
|
-
return;
|
|
2673
|
-
}
|
|
2674
|
-
if (command === "demo") {
|
|
2675
|
-
const optionsDef = [
|
|
2676
|
-
...getOptions(["root", "silent"]),
|
|
2677
|
-
{ name: "dev", type: Boolean, description: "create all sort of fancy certificates for dev testing purposes" },
|
|
2678
|
-
{ name: "clean", type: Boolean, description: "Purge existing directory [use with care!]" }
|
|
2679
|
-
];
|
|
2680
|
-
const local_argv = commandLineArgs(optionsDef, { argv });
|
|
2681
|
-
if (local_argv.help)
|
|
2682
|
-
return showHelp(
|
|
2683
|
-
"demo",
|
|
2684
|
-
"create default certificate for node-opcua demos",
|
|
2685
|
-
optionsDef,
|
|
2686
|
-
"$0 demo [--dev] [--silent] [--clean]"
|
|
2687
|
-
);
|
|
2688
|
-
await wrap(async () => {
|
|
2689
|
-
await ensure_openssl_installed();
|
|
2690
|
-
displayChapter("Create Demo certificates");
|
|
2691
|
-
displayTitle("reading configuration");
|
|
2692
|
-
await readConfiguration(local_argv);
|
|
2693
|
-
if (local_argv.clean) {
|
|
2694
|
-
displayTitle("Cleaning old certificates");
|
|
2695
|
-
assert11(gLocalConfig);
|
|
2696
|
-
const certificateDir = gLocalConfig.certificateDir || "";
|
|
2697
|
-
const files = await fs10.promises.readdir(certificateDir);
|
|
2698
|
-
for (const file of files) {
|
|
2699
|
-
if (file.includes(".pem") || file.includes(".pub")) {
|
|
2700
|
-
await fs10.promises.unlink(path8.join(certificateDir, file));
|
|
2701
|
-
}
|
|
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);
|
|
2702
2345
|
}
|
|
2703
|
-
|
|
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);
|
|
2704
2350
|
}
|
|
2705
|
-
displayTitle("create certificates");
|
|
2706
|
-
await createDefaultCertificates(local_argv.dev);
|
|
2707
|
-
displayChapter("Demo certificates CREATED");
|
|
2708
2351
|
});
|
|
2709
|
-
|
|
2710
|
-
|
|
2711
|
-
|
|
2712
|
-
|
|
2713
|
-
|
|
2714
|
-
|
|
2715
|
-
|
|
2716
|
-
|
|
2717
|
-
if (local_argv.help) return showHelp("createCA", "create a Certificate Authority", optionsDef);
|
|
2718
|
-
await wrap(async () => {
|
|
2719
|
-
await ensure_openssl_installed();
|
|
2720
|
-
await readConfiguration(local_argv);
|
|
2721
|
-
await construct_CertificateAuthority2(local_argv.subject);
|
|
2722
|
-
});
|
|
2723
|
-
return;
|
|
2724
|
-
}
|
|
2725
|
-
if (command === "createPKI") {
|
|
2726
|
-
const optionsDef = getOptions(["root", "PKIFolder", "keySize", "silent"]);
|
|
2727
|
-
const local_argv = commandLineArgs(optionsDef, { argv });
|
|
2728
|
-
if (local_argv.help) return showHelp("createPKI", "create a Public Key Infrastructure", optionsDef);
|
|
2729
|
-
await wrap(async () => {
|
|
2730
|
-
await readConfiguration(local_argv);
|
|
2731
|
-
await construct_CertificateManager();
|
|
2732
|
-
});
|
|
2733
|
-
return;
|
|
2734
|
-
}
|
|
2735
|
-
if (command === "certificate") {
|
|
2736
|
-
const optionsDef = [
|
|
2737
|
-
...getOptions(["root", "CAFolder", "PKIFolder", "privateKey", "silent"]),
|
|
2738
|
-
{
|
|
2739
|
-
name: "applicationUri",
|
|
2740
|
-
alias: "a",
|
|
2741
|
-
type: String,
|
|
2742
|
-
defaultValue: "urn:{hostname}:Node-OPCUA-Server",
|
|
2743
|
-
description: "the application URI"
|
|
2744
|
-
},
|
|
2745
|
-
{
|
|
2746
|
-
name: "output",
|
|
2747
|
-
alias: "o",
|
|
2748
|
-
type: String,
|
|
2749
|
-
defaultValue: "my_certificate.pem",
|
|
2750
|
-
description: "the name of the generated certificate =>"
|
|
2751
|
-
},
|
|
2752
|
-
{
|
|
2753
|
-
name: "selfSigned",
|
|
2754
|
-
alias: "s",
|
|
2755
|
-
type: Boolean,
|
|
2756
|
-
defaultValue: false,
|
|
2757
|
-
description: "if true, certificate will be self-signed"
|
|
2758
|
-
},
|
|
2759
|
-
{ name: "validity", alias: "v", type: Number, description: "the certificate validity in days" },
|
|
2760
|
-
{
|
|
2761
|
-
name: "dns",
|
|
2762
|
-
type: String,
|
|
2763
|
-
defaultValue: "{hostname}",
|
|
2764
|
-
description: "the list of valid domain name (comma separated)"
|
|
2765
|
-
},
|
|
2766
|
-
{ name: "ip", type: String, defaultValue: "", description: "the list of valid IPs (comma separated)" },
|
|
2767
|
-
{
|
|
2768
|
-
name: "subject",
|
|
2769
|
-
type: String,
|
|
2770
|
-
defaultValue: "",
|
|
2771
|
-
description: "the certificate subject ( for instance C=FR/ST=Centre/L=Orleans/O=SomeOrganization/CN=Hello )"
|
|
2772
|
-
}
|
|
2773
|
-
];
|
|
2774
|
-
const local_argv = commandLineArgs(optionsDef, { argv });
|
|
2775
|
-
if (local_argv.help || !local_argv.applicationUri || !local_argv.output)
|
|
2776
|
-
return showHelp("certificate", "create a new certificate", optionsDef);
|
|
2777
|
-
async function command_certificate(local_argv2) {
|
|
2778
|
-
const selfSigned = !!local_argv2.selfSigned;
|
|
2779
|
-
if (!selfSigned) {
|
|
2780
|
-
await command_full_certificate(local_argv2);
|
|
2781
|
-
} else {
|
|
2782
|
-
await command_selfsigned_certificate(local_argv2);
|
|
2783
|
-
}
|
|
2784
|
-
}
|
|
2785
|
-
async function command_selfsigned_certificate(local_argv2) {
|
|
2786
|
-
const _fqdn = await extractFullyQualifiedDomainName();
|
|
2787
|
-
await readConfiguration(local_argv2);
|
|
2788
|
-
await construct_CertificateManager();
|
|
2789
|
-
displaySubtitle(` create self signed Certificate ${gLocalConfig.outputFile}`);
|
|
2790
|
-
let subject = local_argv2.subject && local_argv2.subject.length > 1 ? new Subject5(local_argv2.subject) : gLocalConfig.subject || "";
|
|
2791
|
-
subject = JSON.parse(JSON.stringify(subject));
|
|
2792
|
-
const params = {
|
|
2793
|
-
applicationUri: gLocalConfig.applicationUri || "",
|
|
2794
|
-
dns: gLocalConfig.dns || [],
|
|
2795
|
-
ip: gLocalConfig.ip || [],
|
|
2796
|
-
outputFile: gLocalConfig.outputFile || "self_signed_certificate.pem",
|
|
2797
|
-
startDate: gLocalConfig.startDate || /* @__PURE__ */ new Date(),
|
|
2798
|
-
subject,
|
|
2799
|
-
validity: gLocalConfig.validity || 365
|
|
2800
|
-
};
|
|
2801
|
-
await certificateManager.createSelfSignedCertificate(params);
|
|
2802
|
-
}
|
|
2803
|
-
async function command_full_certificate(local_argv2) {
|
|
2804
|
-
await readConfiguration(local_argv2);
|
|
2805
|
-
await construct_CertificateManager();
|
|
2806
|
-
await construct_CertificateAuthority2("");
|
|
2807
|
-
assert11(fs10.existsSync(gLocalConfig.CAFolder || ""), " CA folder must exist");
|
|
2808
|
-
gLocalConfig.privateKey = void 0;
|
|
2809
|
-
gLocalConfig.subject = local_argv2.subject && local_argv2.subject.length > 1 ? local_argv2.subject : gLocalConfig.subject;
|
|
2810
|
-
const csr_file = await certificateManager.createCertificateRequest(
|
|
2811
|
-
gLocalConfig
|
|
2812
|
-
);
|
|
2813
|
-
if (!csr_file) {
|
|
2814
|
-
return;
|
|
2815
|
-
}
|
|
2816
|
-
warningLog(" csr_file = ", csr_file);
|
|
2817
|
-
const certificate = csr_file.replace(".csr", ".pem");
|
|
2818
|
-
if (fs10.existsSync(certificate)) {
|
|
2819
|
-
throw new Error(` File ${certificate} already exist`);
|
|
2820
|
-
}
|
|
2821
|
-
await g_certificateAuthority.signCertificateRequest(
|
|
2822
|
-
certificate,
|
|
2823
|
-
csr_file,
|
|
2824
|
-
gLocalConfig
|
|
2825
|
-
);
|
|
2826
|
-
assert11(typeof gLocalConfig.outputFile === "string");
|
|
2827
|
-
fs10.writeFileSync(gLocalConfig.outputFile || "", fs10.readFileSync(certificate, "ascii"));
|
|
2828
|
-
}
|
|
2829
|
-
await wrap(async () => await command_certificate(local_argv));
|
|
2830
|
-
return;
|
|
2831
|
-
}
|
|
2832
|
-
if (command === "revoke") {
|
|
2833
|
-
const optionsDef = [{ name: "certificateFile", type: String, defaultOption: true }, ...getOptions(["root", "CAFolder"])];
|
|
2834
|
-
const local_argv = commandLineArgs(optionsDef, { argv });
|
|
2835
|
-
if (local_argv.help || !local_argv.certificateFile)
|
|
2836
|
-
return showHelp(
|
|
2837
|
-
"revoke <certificateFile>",
|
|
2838
|
-
"revoke a existing certificate",
|
|
2839
|
-
optionsDef,
|
|
2840
|
-
"$0 revoke my_certificate.pem"
|
|
2841
|
-
);
|
|
2842
|
-
async function revoke_certificate(certificate) {
|
|
2843
|
-
await g_certificateAuthority.revokeCertificate(certificate, {});
|
|
2844
|
-
}
|
|
2845
|
-
await wrap(async () => {
|
|
2846
|
-
const certificate = path8.resolve(local_argv.certificateFile);
|
|
2847
|
-
warningLog(chalk7.yellow(" Certificate to revoke : "), chalk7.cyan(certificate));
|
|
2848
|
-
if (!fs10.existsSync(certificate)) {
|
|
2849
|
-
throw new Error(`cannot find certificate to revoke ${certificate}`);
|
|
2850
|
-
}
|
|
2851
|
-
await readConfiguration(local_argv);
|
|
2852
|
-
await construct_CertificateAuthority2("");
|
|
2853
|
-
await revoke_certificate(certificate);
|
|
2854
|
-
warningLog("done ... ");
|
|
2855
|
-
warningLog(" crl = ", g_certificateAuthority.revocationList);
|
|
2856
|
-
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
|
+
});
|
|
2857
2360
|
});
|
|
2858
|
-
return;
|
|
2859
2361
|
}
|
|
2860
|
-
|
|
2861
|
-
|
|
2862
|
-
|
|
2863
|
-
{
|
|
2864
|
-
|
|
2865
|
-
alias: "a",
|
|
2866
|
-
type: String,
|
|
2867
|
-
defaultValue: "urn:{hostname}:Node-OPCUA-Server",
|
|
2868
|
-
description: "the application URI"
|
|
2869
|
-
},
|
|
2870
|
-
{
|
|
2871
|
-
name: "output",
|
|
2872
|
-
alias: "o",
|
|
2873
|
-
type: String,
|
|
2874
|
-
defaultValue: "my_certificate_signing_request.csr",
|
|
2875
|
-
description: "the name of the generated signing_request"
|
|
2876
|
-
},
|
|
2877
|
-
{
|
|
2878
|
-
name: "dns",
|
|
2879
|
-
type: String,
|
|
2880
|
-
defaultValue: "{hostname}",
|
|
2881
|
-
description: "the list of valid domain name (comma separated)"
|
|
2882
|
-
},
|
|
2883
|
-
{ name: "ip", type: String, defaultValue: "", description: "the list of valid IPs (comma separated)" },
|
|
2884
|
-
{
|
|
2885
|
-
name: "subject",
|
|
2886
|
-
type: String,
|
|
2887
|
-
defaultValue: "/CN=Certificate",
|
|
2888
|
-
description: "the certificate subject ( for instance /C=FR/ST=Centre/L=Orleans/O=SomeOrganization/CN=Hello )"
|
|
2889
|
-
}
|
|
2890
|
-
];
|
|
2891
|
-
const local_argv = commandLineArgs(optionsDef, { argv });
|
|
2892
|
-
if (local_argv.help) return showHelp("csr", "create a certificate signing request", optionsDef);
|
|
2893
|
-
await wrap(async () => {
|
|
2894
|
-
await readConfiguration(local_argv);
|
|
2895
|
-
if (!fs10.existsSync(gLocalConfig.PKIFolder || "")) {
|
|
2896
|
-
warningLog("PKI folder must exist");
|
|
2897
|
-
}
|
|
2898
|
-
await construct_CertificateManager();
|
|
2899
|
-
if (!gLocalConfig.outputFile || fs10.existsSync(gLocalConfig.outputFile)) {
|
|
2900
|
-
throw new Error(` File ${gLocalConfig.outputFile} already exist`);
|
|
2901
|
-
}
|
|
2902
|
-
gLocalConfig.privateKey = void 0;
|
|
2903
|
-
gLocalConfig.subject = local_argv.subject && local_argv.subject.length > 1 ? local_argv.subject : gLocalConfig.subject;
|
|
2904
|
-
const internal_csr_file = await certificateManager.createCertificateRequest(
|
|
2905
|
-
gLocalConfig
|
|
2906
|
-
);
|
|
2907
|
-
if (!internal_csr_file) {
|
|
2908
|
-
return;
|
|
2909
|
-
}
|
|
2910
|
-
if (!gLocalConfig.outputFile) {
|
|
2911
|
-
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);
|
|
2912
2367
|
return;
|
|
2913
2368
|
}
|
|
2914
|
-
|
|
2915
|
-
|
|
2916
|
-
warningLog("Subject = ", gLocalConfig.subject);
|
|
2917
|
-
warningLog("applicationUri = ", gLocalConfig.applicationUri);
|
|
2918
|
-
warningLog("altNames = ", gLocalConfig.altNames);
|
|
2919
|
-
warningLog("dns = ", gLocalConfig.dns);
|
|
2920
|
-
warningLog("ip = ", gLocalConfig.ip);
|
|
2921
|
-
warningLog("CSR file = ", gLocalConfig.outputFile);
|
|
2922
|
-
});
|
|
2923
|
-
return;
|
|
2924
|
-
}
|
|
2925
|
-
if (command === "sign") {
|
|
2926
|
-
const optionsDef = [
|
|
2927
|
-
...getOptions(["root", "CAFolder", "silent"]),
|
|
2928
|
-
{ name: "csr", alias: "i", type: String, defaultValue: "my_certificate_signing_request.csr", description: "the csr" },
|
|
2929
|
-
{
|
|
2930
|
-
name: "output",
|
|
2931
|
-
alias: "o",
|
|
2932
|
-
type: String,
|
|
2933
|
-
defaultValue: "my_certificate.pem",
|
|
2934
|
-
description: "the name of the generated certificate"
|
|
2935
|
-
},
|
|
2936
|
-
{ name: "validity", alias: "v", type: Number, defaultValue: 365, description: "the certificate validity in days" }
|
|
2937
|
-
];
|
|
2938
|
-
const local_argv = commandLineArgs(optionsDef, { argv });
|
|
2939
|
-
if (local_argv.help || !local_argv.csr || !local_argv.output)
|
|
2940
|
-
return showHelp("sign", "validate a certificate signing request and generate a certificate", optionsDef);
|
|
2941
|
-
await wrap(async () => {
|
|
2942
|
-
await readConfiguration(local_argv);
|
|
2943
|
-
if (!fs10.existsSync(gLocalConfig.CAFolder || "")) {
|
|
2944
|
-
throw new Error(`CA folder must exist:${gLocalConfig.CAFolder}`);
|
|
2945
|
-
}
|
|
2946
|
-
await construct_CertificateAuthority2("");
|
|
2947
|
-
const csr_file = path8.resolve(local_argv.csr || "");
|
|
2948
|
-
if (!fs10.existsSync(csr_file)) {
|
|
2949
|
-
throw new Error(`Certificate signing request doesn't exist: ${csr_file}`);
|
|
2950
|
-
}
|
|
2951
|
-
const certificate = path8.resolve(local_argv.output || csr_file.replace(".csr", ".pem"));
|
|
2952
|
-
if (fs10.existsSync(certificate)) {
|
|
2953
|
-
throw new Error(` File ${certificate} already exist`);
|
|
2369
|
+
if (this.#onCrlProcess) {
|
|
2370
|
+
return reject(new Error("Internal Error"));
|
|
2954
2371
|
}
|
|
2955
|
-
|
|
2956
|
-
certificate,
|
|
2957
|
-
csr_file,
|
|
2958
|
-
gLocalConfig
|
|
2959
|
-
);
|
|
2960
|
-
assert11(typeof gLocalConfig.outputFile === "string");
|
|
2961
|
-
fs10.writeFileSync(gLocalConfig.outputFile || "", fs10.readFileSync(certificate, "ascii"));
|
|
2962
|
-
});
|
|
2963
|
-
return;
|
|
2964
|
-
}
|
|
2965
|
-
if (command === "dump") {
|
|
2966
|
-
const optionsDef = [
|
|
2967
|
-
{ name: "certificateFile", type: String, defaultOption: true },
|
|
2968
|
-
{ name: "help", alias: "h", type: Boolean }
|
|
2969
|
-
];
|
|
2970
|
-
const local_argv = commandLineArgs(optionsDef, { argv });
|
|
2971
|
-
if (local_argv.help || !local_argv.certificateFile)
|
|
2972
|
-
return showHelp("dump <certificateFile>", "display a certificate", optionsDef);
|
|
2973
|
-
await wrap(async () => {
|
|
2974
|
-
const data = await dumpCertificate(local_argv.certificateFile);
|
|
2975
|
-
warningLog(data);
|
|
2976
|
-
});
|
|
2977
|
-
return;
|
|
2978
|
-
}
|
|
2979
|
-
if (command === "toder") {
|
|
2980
|
-
const optionsDef = [
|
|
2981
|
-
{ name: "pemCertificate", type: String, defaultOption: true },
|
|
2982
|
-
{ name: "help", alias: "h", type: Boolean }
|
|
2983
|
-
];
|
|
2984
|
-
const local_argv = commandLineArgs(optionsDef, { argv });
|
|
2985
|
-
if (local_argv.help || !local_argv.pemCertificate)
|
|
2986
|
-
return showHelp("toder <pemCertificate>", "convert a certificate to a DER format with finger print", optionsDef);
|
|
2987
|
-
await wrap(async () => {
|
|
2988
|
-
await toDer(local_argv.pemCertificate);
|
|
2372
|
+
this.#onCrlProcess = resolve;
|
|
2989
2373
|
});
|
|
2990
|
-
return;
|
|
2991
|
-
}
|
|
2992
|
-
if (command === "fingerprint") {
|
|
2993
|
-
const optionsDef = [
|
|
2994
|
-
{ name: "certificateFile", type: String, defaultOption: true },
|
|
2995
|
-
{ name: "help", alias: "h", type: Boolean }
|
|
2996
|
-
];
|
|
2997
|
-
const local_argv = commandLineArgs(optionsDef, { argv });
|
|
2998
|
-
if (local_argv.help || !local_argv.certificateFile)
|
|
2999
|
-
return showHelp("fingerprint <certificateFile>", "print the certificate fingerprint", optionsDef);
|
|
3000
|
-
await wrap(async () => {
|
|
3001
|
-
const certificate = local_argv.certificateFile;
|
|
3002
|
-
const data = await fingerprint(certificate);
|
|
3003
|
-
if (!data) return;
|
|
3004
|
-
const s = data.split("=")[1].split(":").join("").trim();
|
|
3005
|
-
warningLog(s);
|
|
3006
|
-
});
|
|
3007
|
-
return;
|
|
3008
2374
|
}
|
|
3009
|
-
|
|
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, {});
|
|
3010
2434
|
}
|
|
3011
2435
|
export {
|
|
3012
2436
|
CertificateAuthority,
|
|
@@ -3016,22 +2440,15 @@ export {
|
|
|
3016
2440
|
VerificationStatus,
|
|
3017
2441
|
adjustApplicationUri,
|
|
3018
2442
|
adjustDate,
|
|
3019
|
-
|
|
3020
|
-
|
|
3021
|
-
|
|
3022
|
-
|
|
3023
|
-
|
|
3024
|
-
|
|
3025
|
-
|
|
3026
|
-
displayTitle,
|
|
3027
|
-
doDebug,
|
|
2443
|
+
convertPFXtoPEM,
|
|
2444
|
+
createPFX,
|
|
2445
|
+
dumpPFX,
|
|
2446
|
+
extractAllFromPFX,
|
|
2447
|
+
extractCACertificatesFromPFX,
|
|
2448
|
+
extractCertificateFromPFX,
|
|
2449
|
+
extractPrivateKeyFromPFX,
|
|
3028
2450
|
findIssuerCertificateInChain,
|
|
3029
|
-
g_config,
|
|
3030
2451
|
install_prerequisite,
|
|
3031
|
-
|
|
3032
|
-
mkdirRecursiveSync,
|
|
3033
|
-
main as pki_main,
|
|
3034
|
-
quote,
|
|
3035
|
-
warningLog
|
|
2452
|
+
quote
|
|
3036
2453
|
};
|
|
3037
2454
|
//# sourceMappingURL=index.mjs.map
|