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.js
CHANGED
|
@@ -37,23 +37,16 @@ __export(lib_exports, {
|
|
|
37
37
|
VerificationStatus: () => VerificationStatus,
|
|
38
38
|
adjustApplicationUri: () => adjustApplicationUri,
|
|
39
39
|
adjustDate: () => adjustDate,
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
displayTitle: () => displayTitle,
|
|
48
|
-
doDebug: () => doDebug,
|
|
40
|
+
convertPFXtoPEM: () => convertPFXtoPEM,
|
|
41
|
+
createPFX: () => createPFX,
|
|
42
|
+
dumpPFX: () => dumpPFX,
|
|
43
|
+
extractAllFromPFX: () => extractAllFromPFX,
|
|
44
|
+
extractCACertificatesFromPFX: () => extractCACertificatesFromPFX,
|
|
45
|
+
extractCertificateFromPFX: () => extractCertificateFromPFX,
|
|
46
|
+
extractPrivateKeyFromPFX: () => extractPrivateKeyFromPFX,
|
|
49
47
|
findIssuerCertificateInChain: () => findIssuerCertificateInChain,
|
|
50
|
-
g_config: () => g_config,
|
|
51
48
|
install_prerequisite: () => install_prerequisite,
|
|
52
|
-
|
|
53
|
-
mkdirRecursiveSync: () => mkdirRecursiveSync,
|
|
54
|
-
pki_main: () => main,
|
|
55
|
-
quote: () => quote,
|
|
56
|
-
warningLog: () => warningLog
|
|
49
|
+
quote: () => quote
|
|
57
50
|
});
|
|
58
51
|
module.exports = __toCommonJS(lib_exports);
|
|
59
52
|
|
|
@@ -142,13 +135,6 @@ function makePath(folderName, filename) {
|
|
|
142
135
|
|
|
143
136
|
// packages/node-opcua-pki/lib/toolbox/display.ts
|
|
144
137
|
var import_chalk2 = __toESM(require("chalk"));
|
|
145
|
-
function displayChapter(str) {
|
|
146
|
-
const l = " ";
|
|
147
|
-
warningLog(`${import_chalk2.default.bgWhite(l)} `);
|
|
148
|
-
str = ` ${str}${l}`.substring(0, l.length);
|
|
149
|
-
warningLog(import_chalk2.default.bgWhite.cyan(str));
|
|
150
|
-
warningLog(`${import_chalk2.default.bgWhite(l)} `);
|
|
151
|
-
}
|
|
152
138
|
function displayTitle(str) {
|
|
153
139
|
if (!g_config.silent) {
|
|
154
140
|
warningLog("");
|
|
@@ -647,12 +633,6 @@ function generateStaticConfig(configPath, options) {
|
|
|
647
633
|
return temporaryConfigPath;
|
|
648
634
|
}
|
|
649
635
|
}
|
|
650
|
-
var q = quote;
|
|
651
|
-
var n2 = makePath;
|
|
652
|
-
async function getPublicKeyFromPrivateKey(privateKeyFilename, publicKeyFilename) {
|
|
653
|
-
(0, import_node_assert4.default)(import_node_fs4.default.existsSync(privateKeyFilename));
|
|
654
|
-
await execute_openssl(`rsa -pubout -in ${q(n2(privateKeyFilename))} -out ${q(n2(publicKeyFilename))}`, {});
|
|
655
|
-
}
|
|
656
636
|
function x509Date(date) {
|
|
657
637
|
date = date || /* @__PURE__ */ new Date();
|
|
658
638
|
const Y = date.getUTCFullYear();
|
|
@@ -670,45 +650,6 @@ function x509Date(date) {
|
|
|
670
650
|
return `${w(Y, 4) + w(M, 2) + w(D, 2) + w(h, 2) + w(m, 2) + w(s, 2)}Z`;
|
|
671
651
|
}
|
|
672
652
|
}
|
|
673
|
-
async function dumpCertificate(certificate) {
|
|
674
|
-
(0, import_node_assert4.default)(import_node_fs4.default.existsSync(certificate));
|
|
675
|
-
return await execute_openssl(`x509 -in ${q(n2(certificate))} -text -noout`, {});
|
|
676
|
-
}
|
|
677
|
-
async function toDer(certificatePem) {
|
|
678
|
-
(0, import_node_assert4.default)(import_node_fs4.default.existsSync(certificatePem));
|
|
679
|
-
const certificateDer = certificatePem.replace(".pem", ".der");
|
|
680
|
-
return await execute_openssl(`x509 -outform der -in ${certificatePem} -out ${certificateDer}`, {});
|
|
681
|
-
}
|
|
682
|
-
async function fingerprint(certificatePem) {
|
|
683
|
-
(0, import_node_assert4.default)(import_node_fs4.default.existsSync(certificatePem));
|
|
684
|
-
return await execute_openssl(`x509 -fingerprint -noout -in ${certificatePem}`, {});
|
|
685
|
-
}
|
|
686
|
-
|
|
687
|
-
// packages/node-opcua-pki/lib/toolbox/with_openssl/create_certificate_signing_request.ts
|
|
688
|
-
var q2 = quote;
|
|
689
|
-
var n3 = makePath;
|
|
690
|
-
async function createCertificateSigningRequestWithOpenSSL(certificateSigningRequestFilename, params) {
|
|
691
|
-
(0, import_node_assert5.default)(params);
|
|
692
|
-
(0, import_node_assert5.default)(params.rootDir);
|
|
693
|
-
(0, import_node_assert5.default)(params.configFile);
|
|
694
|
-
(0, import_node_assert5.default)(params.privateKey);
|
|
695
|
-
(0, import_node_assert5.default)(typeof params.privateKey === "string");
|
|
696
|
-
(0, import_node_assert5.default)(import_node_fs5.default.existsSync(params.configFile), `config file must exist ${params.configFile}`);
|
|
697
|
-
(0, import_node_assert5.default)(import_node_fs5.default.existsSync(params.privateKey), `Private key must exist${params.privateKey}`);
|
|
698
|
-
(0, import_node_assert5.default)(import_node_fs5.default.existsSync(params.rootDir), "RootDir key must exist");
|
|
699
|
-
(0, import_node_assert5.default)(typeof certificateSigningRequestFilename === "string");
|
|
700
|
-
processAltNames(params);
|
|
701
|
-
const configFile = generateStaticConfig(params.configFile, { cwd: params.rootDir });
|
|
702
|
-
const options = { cwd: params.rootDir, openssl_conf: import_node_path4.default.relative(params.rootDir, configFile) };
|
|
703
|
-
const configOption = ` -config ${q2(n3(configFile))}`;
|
|
704
|
-
const subject = params.subject ? new import_node_opcua_crypto.Subject(params.subject).toString() : void 0;
|
|
705
|
-
const subjectOptions = subject ? ` -subj "${subject}"` : "";
|
|
706
|
-
displaySubtitle("- Creating a Certificate Signing Request with openssl");
|
|
707
|
-
await execute_openssl(
|
|
708
|
-
"req -new -sha256 -batch -text " + configOption + " -key " + q2(n3(params.privateKey)) + subjectOptions + " -out " + q2(n3(certificateSigningRequestFilename)),
|
|
709
|
-
options
|
|
710
|
-
);
|
|
711
|
-
}
|
|
712
653
|
|
|
713
654
|
// packages/node-opcua-pki/lib/ca/templates/ca_config_template.cnf.ts
|
|
714
655
|
var config = `#.........DO NOT MODIFY BY HAND .........................
|
|
@@ -846,8 +787,8 @@ var config2 = {
|
|
|
846
787
|
forceCA: false,
|
|
847
788
|
pkiDir: "INVALID"
|
|
848
789
|
};
|
|
849
|
-
var
|
|
850
|
-
var
|
|
790
|
+
var n2 = makePath;
|
|
791
|
+
var q = quote;
|
|
851
792
|
function octetStringToIpAddress(a) {
|
|
852
793
|
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();
|
|
853
794
|
}
|
|
@@ -898,7 +839,7 @@ async function construct_CertificateAuthority(certificateAuthority) {
|
|
|
898
839
|
processAltNames({});
|
|
899
840
|
const options = { cwd: caRootDir };
|
|
900
841
|
const configFile = generateStaticConfig("conf/caconfig.cnf", options);
|
|
901
|
-
const configOption = ` -config ${
|
|
842
|
+
const configOption = ` -config ${q(n2(configFile))}`;
|
|
902
843
|
const keySize = certificateAuthority.keySize;
|
|
903
844
|
const privateKeyFilename = import_node_path5.default.join(caRootDir, "private/cakey.pem");
|
|
904
845
|
const csrFilename = import_node_path5.default.join(caRootDir, "private/cakey.csr");
|
|
@@ -906,12 +847,12 @@ async function construct_CertificateAuthority(certificateAuthority) {
|
|
|
906
847
|
await (0, import_node_opcua_crypto2.generatePrivateKeyFile)(privateKeyFilename, keySize);
|
|
907
848
|
displayTitle("Generate a certificate request for the CA key");
|
|
908
849
|
await execute_openssl(
|
|
909
|
-
"req -new -sha256 -text -extensions v3_ca" + configOption + " -key " +
|
|
850
|
+
"req -new -sha256 -text -extensions v3_ca" + configOption + " -key " + q(n2(privateKeyFilename)) + " -out " + q(n2(csrFilename)) + " " + subjectOpt,
|
|
910
851
|
options
|
|
911
852
|
);
|
|
912
853
|
displayTitle("Generate CA Certificate (self-signed)");
|
|
913
854
|
await execute_openssl(
|
|
914
|
-
" x509 -sha256 -req -days 3650 -text -extensions v3_ca -extfile " +
|
|
855
|
+
" 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",
|
|
915
856
|
options
|
|
916
857
|
);
|
|
917
858
|
displaySubtitle("generate initial CRL (Certificate Revocation List)");
|
|
@@ -923,11 +864,14 @@ async function regenerateCrl(revocationList, configOption, options) {
|
|
|
923
864
|
await execute_openssl(`ca -gencrl ${configOption} -out crl/revocation_list.crl`, options);
|
|
924
865
|
await execute_openssl("crl -in crl/revocation_list.crl -out crl/revocation_list.der -outform der", options);
|
|
925
866
|
displaySubtitle("Display (Certificate Revocation List)");
|
|
926
|
-
await execute_openssl(`crl -in ${
|
|
867
|
+
await execute_openssl(`crl -in ${q(n2(revocationList))} -text -noout`, options);
|
|
927
868
|
}
|
|
928
869
|
var CertificateAuthority = class {
|
|
870
|
+
/** RSA key size used when generating the CA private key. */
|
|
929
871
|
keySize;
|
|
872
|
+
/** Root filesystem path of the CA directory structure. */
|
|
930
873
|
location;
|
|
874
|
+
/** X.500 subject of the CA certificate. */
|
|
931
875
|
subject;
|
|
932
876
|
constructor(options) {
|
|
933
877
|
(0, import_node_assert6.default)(Object.prototype.hasOwnProperty.call(options, "location"));
|
|
@@ -936,33 +880,54 @@ var CertificateAuthority = class {
|
|
|
936
880
|
this.keySize = options.keySize || 2048;
|
|
937
881
|
this.subject = new import_node_opcua_crypto2.Subject(options.subject || defaultSubject);
|
|
938
882
|
}
|
|
883
|
+
/** Absolute path to the CA root directory (alias for {@link location}). */
|
|
939
884
|
get rootDir() {
|
|
940
885
|
return this.location;
|
|
941
886
|
}
|
|
887
|
+
/** Path to the OpenSSL configuration file (`conf/caconfig.cnf`). */
|
|
942
888
|
get configFile() {
|
|
943
889
|
return import_node_path5.default.normalize(import_node_path5.default.join(this.rootDir, "./conf/caconfig.cnf"));
|
|
944
890
|
}
|
|
891
|
+
/** Path to the CA certificate in PEM format (`public/cacert.pem`). */
|
|
945
892
|
get caCertificate() {
|
|
946
893
|
return makePath(this.rootDir, "./public/cacert.pem");
|
|
947
894
|
}
|
|
948
895
|
/**
|
|
949
|
-
*
|
|
896
|
+
* Path to the current Certificate Revocation List in DER format.
|
|
897
|
+
* (`crl/revocation_list.der`)
|
|
950
898
|
*/
|
|
951
899
|
get revocationListDER() {
|
|
952
900
|
return makePath(this.rootDir, "./crl/revocation_list.der");
|
|
953
901
|
}
|
|
954
902
|
/**
|
|
955
|
-
*
|
|
903
|
+
* Path to the current Certificate Revocation List in PEM format.
|
|
904
|
+
* (`crl/revocation_list.crl`)
|
|
956
905
|
*/
|
|
957
906
|
get revocationList() {
|
|
958
907
|
return makePath(this.rootDir, "./crl/revocation_list.crl");
|
|
959
908
|
}
|
|
909
|
+
/**
|
|
910
|
+
* Path to the concatenated CA certificate + CRL file.
|
|
911
|
+
* Used by OpenSSL for CRL-based verification.
|
|
912
|
+
*/
|
|
960
913
|
get caCertificateWithCrl() {
|
|
961
914
|
return makePath(this.rootDir, "./public/cacertificate_with_crl.pem");
|
|
962
915
|
}
|
|
916
|
+
/**
|
|
917
|
+
* Initialize the CA directory structure, generate the CA
|
|
918
|
+
* private key and self-signed certificate if they do not
|
|
919
|
+
* already exist.
|
|
920
|
+
*/
|
|
963
921
|
async initialize() {
|
|
964
922
|
await construct_CertificateAuthority(this);
|
|
965
923
|
}
|
|
924
|
+
/**
|
|
925
|
+
* Rebuild the combined CA certificate + CRL file.
|
|
926
|
+
*
|
|
927
|
+
* This concatenates the CA certificate with the current
|
|
928
|
+
* revocation list so that OpenSSL can verify certificates
|
|
929
|
+
* with CRL checking enabled.
|
|
930
|
+
*/
|
|
966
931
|
async constructCACertificateWithCRL() {
|
|
967
932
|
const cacertWithCRL = this.caCertificateWithCrl;
|
|
968
933
|
if (import_node_fs6.default.existsSync(this.revocationList)) {
|
|
@@ -974,6 +939,12 @@ var CertificateAuthority = class {
|
|
|
974
939
|
await import_node_fs6.default.promises.writeFile(cacertWithCRL, import_node_fs6.default.readFileSync(this.caCertificate));
|
|
975
940
|
}
|
|
976
941
|
}
|
|
942
|
+
/**
|
|
943
|
+
* Append the CA certificate to a signed certificate file,
|
|
944
|
+
* creating a PEM certificate chain.
|
|
945
|
+
*
|
|
946
|
+
* @param certificate - path to the certificate file to extend
|
|
947
|
+
*/
|
|
977
948
|
async constructCertificateChain(certificate) {
|
|
978
949
|
(0, import_node_assert6.default)(import_node_fs6.default.existsSync(certificate));
|
|
979
950
|
(0, import_node_assert6.default)(import_node_fs6.default.existsSync(this.caCertificate));
|
|
@@ -984,6 +955,13 @@ var CertificateAuthority = class {
|
|
|
984
955
|
// + fs.readFileSync(this.revocationList)
|
|
985
956
|
);
|
|
986
957
|
}
|
|
958
|
+
/**
|
|
959
|
+
* Create a self-signed certificate using OpenSSL.
|
|
960
|
+
*
|
|
961
|
+
* @param certificateFile - output path for the signed certificate
|
|
962
|
+
* @param privateKey - path to the private key file
|
|
963
|
+
* @param params - certificate parameters (subject, validity, SANs)
|
|
964
|
+
*/
|
|
987
965
|
async createSelfSignedCertificate(certificateFile, privateKey, params) {
|
|
988
966
|
(0, import_node_assert6.default)(typeof privateKey === "string");
|
|
989
967
|
(0, import_node_assert6.default)(import_node_fs6.default.existsSync(privateKey));
|
|
@@ -1005,28 +983,27 @@ var CertificateAuthority = class {
|
|
|
1005
983
|
const subjectOptions = subject && subject.length > 1 ? ` -subj ${subject} ` : "";
|
|
1006
984
|
displaySubtitle("- the certificate signing request");
|
|
1007
985
|
await execute_openssl(
|
|
1008
|
-
"req -new -sha256 -text " + configOption + subjectOptions + " -batch -key " +
|
|
986
|
+
"req -new -sha256 -text " + configOption + subjectOptions + " -batch -key " + q(n2(privateKey)) + " -out " + q(n2(csrFile)),
|
|
1009
987
|
options
|
|
1010
988
|
);
|
|
1011
989
|
displaySubtitle("- creating the self-signed certificate");
|
|
1012
990
|
await execute_openssl(
|
|
1013
|
-
"ca -selfsign -keyfile " +
|
|
991
|
+
"ca -selfsign -keyfile " + q(n2(privateKey)) + " -startdate " + x509Date(params.startDate) + " -enddate " + x509Date(params.endDate) + " -batch -out " + q(n2(certificateFile)) + " -in " + q(n2(csrFile)),
|
|
1014
992
|
options
|
|
1015
993
|
);
|
|
1016
994
|
displaySubtitle("- dump the certificate for a check");
|
|
1017
|
-
await execute_openssl(`x509 -in ${
|
|
995
|
+
await execute_openssl(`x509 -in ${q(n2(certificateFile))} -dates -fingerprint -purpose -noout`, {});
|
|
1018
996
|
displaySubtitle("- verify self-signed certificate");
|
|
1019
|
-
await execute_openssl_no_failure(`verify -verbose -CAfile ${
|
|
997
|
+
await execute_openssl_no_failure(`verify -verbose -CAfile ${q(n2(certificateFile))} ${q(n2(certificateFile))}`, options);
|
|
1020
998
|
await import_node_fs6.default.promises.unlink(csrFile);
|
|
1021
999
|
}
|
|
1022
1000
|
/**
|
|
1023
|
-
*
|
|
1001
|
+
* Revoke a certificate and regenerate the CRL.
|
|
1024
1002
|
*
|
|
1025
|
-
* @
|
|
1026
|
-
* @param
|
|
1027
|
-
* @param params
|
|
1028
|
-
*
|
|
1029
|
-
* @async
|
|
1003
|
+
* @param certificate - path to the certificate file to revoke
|
|
1004
|
+
* @param params - revocation parameters
|
|
1005
|
+
* @param params.reason - CRL reason code
|
|
1006
|
+
* (default `"keyCompromise"`)
|
|
1030
1007
|
*/
|
|
1031
1008
|
async revokeCertificate(certificate, params) {
|
|
1032
1009
|
const crlReasons = [
|
|
@@ -1047,31 +1024,34 @@ var CertificateAuthority = class {
|
|
|
1047
1024
|
setEnv("ALTNAME", "");
|
|
1048
1025
|
const randomFile = import_node_path5.default.join(this.rootDir, "random.rnd");
|
|
1049
1026
|
setEnv("RANDFILE", randomFile);
|
|
1050
|
-
const configOption = ` -config ${
|
|
1027
|
+
const configOption = ` -config ${q(n2(configFile))}`;
|
|
1051
1028
|
const reason = params.reason || "keyCompromise";
|
|
1052
1029
|
(0, import_node_assert6.default)(crlReasons.indexOf(reason) >= 0);
|
|
1053
1030
|
displayTitle(`Revoking certificate ${certificate}`);
|
|
1054
1031
|
displaySubtitle("Revoke certificate");
|
|
1055
|
-
await execute_openssl_no_failure(`ca -verbose ${configOption} -revoke ${
|
|
1032
|
+
await execute_openssl_no_failure(`ca -verbose ${configOption} -revoke ${q(certificate)} -crl_reason ${reason}`, options);
|
|
1056
1033
|
await regenerateCrl(this.revocationList, configOption, options);
|
|
1057
1034
|
displaySubtitle("Verify that certificate is revoked");
|
|
1058
1035
|
await execute_openssl_no_failure(
|
|
1059
|
-
"verify -verbose -CRLfile " +
|
|
1036
|
+
"verify -verbose -CRLfile " + q(n2(this.revocationList)) + " -CAfile " + q(n2(this.caCertificate)) + " -crl_check " + q(n2(certificate)),
|
|
1060
1037
|
options
|
|
1061
1038
|
);
|
|
1062
1039
|
displaySubtitle("Produce CRL in DER form ");
|
|
1063
|
-
await execute_openssl(`crl -in ${
|
|
1040
|
+
await execute_openssl(`crl -in ${q(n2(this.revocationList))} -out crl/revocation_list.der -outform der`, options);
|
|
1064
1041
|
displaySubtitle("Produce CRL in PEM form ");
|
|
1065
|
-
await execute_openssl(`crl -in ${
|
|
1042
|
+
await execute_openssl(`crl -in ${q(n2(this.revocationList))} -out crl/revocation_list.pem -outform pem -text `, options);
|
|
1066
1043
|
}
|
|
1067
1044
|
/**
|
|
1045
|
+
* Sign a Certificate Signing Request (CSR) with this CA.
|
|
1046
|
+
*
|
|
1047
|
+
* The signed certificate is written to `certificate`, and the
|
|
1048
|
+
* CA certificate chain plus CRL are appended to form a
|
|
1049
|
+
* complete certificate chain.
|
|
1068
1050
|
*
|
|
1069
|
-
* @param certificate
|
|
1070
|
-
* @param certificateSigningRequestFilename
|
|
1071
|
-
* @param
|
|
1072
|
-
* @
|
|
1073
|
-
* @param params.startDate - startDate of the certificate
|
|
1074
|
-
* @param params.validity - number of day of validity of the certificate
|
|
1051
|
+
* @param certificate - output path for the signed certificate
|
|
1052
|
+
* @param certificateSigningRequestFilename - path to the CSR
|
|
1053
|
+
* @param params1 - signing parameters (validity, dates, SANs)
|
|
1054
|
+
* @returns the path to the signed certificate
|
|
1075
1055
|
*/
|
|
1076
1056
|
async signCertificateRequest(certificate, certificateSigningRequestFilename, params1) {
|
|
1077
1057
|
await ensure_openssl_installed();
|
|
@@ -1089,12 +1069,12 @@ var CertificateAuthority = class {
|
|
|
1089
1069
|
if (typeof applicationUri !== "string") {
|
|
1090
1070
|
throw new Error("Cannot find applicationUri in CSR");
|
|
1091
1071
|
}
|
|
1092
|
-
const
|
|
1072
|
+
const dns = csrInfo.extensionRequest.subjectAltName.dNSName || [];
|
|
1093
1073
|
let ip = csrInfo.extensionRequest.subjectAltName.iPAddress || [];
|
|
1094
1074
|
ip = ip.map(octetStringToIpAddress);
|
|
1095
1075
|
const params = {
|
|
1096
1076
|
applicationUri,
|
|
1097
|
-
dns
|
|
1077
|
+
dns,
|
|
1098
1078
|
ip
|
|
1099
1079
|
};
|
|
1100
1080
|
processAltNames(params);
|
|
@@ -1102,11 +1082,11 @@ var CertificateAuthority = class {
|
|
|
1102
1082
|
displaySubtitle("- then we ask the authority to sign the certificate signing request");
|
|
1103
1083
|
const configOption = ` -config ${configFile}`;
|
|
1104
1084
|
await execute_openssl(
|
|
1105
|
-
"ca " + configOption + " -startdate " + x509Date(params1.startDate) + " -enddate " + x509Date(params1.endDate) + " -batch -out " +
|
|
1085
|
+
"ca " + configOption + " -startdate " + x509Date(params1.startDate) + " -enddate " + x509Date(params1.endDate) + " -batch -out " + q(n2(certificate)) + " -in " + q(n2(certificateSigningRequestFilename)),
|
|
1106
1086
|
options
|
|
1107
1087
|
);
|
|
1108
1088
|
displaySubtitle("- dump the certificate for a check");
|
|
1109
|
-
await execute_openssl(`x509 -in ${
|
|
1089
|
+
await execute_openssl(`x509 -in ${q(n2(certificate))} -dates -fingerprint -purpose -noout`, options);
|
|
1110
1090
|
displaySubtitle("- construct CA certificate with CRL");
|
|
1111
1091
|
await this.constructCACertificateWithCRL();
|
|
1112
1092
|
displaySubtitle("- construct certificate chain");
|
|
@@ -1115,6 +1095,11 @@ var CertificateAuthority = class {
|
|
|
1115
1095
|
await this.verifyCertificate(certificate);
|
|
1116
1096
|
return certificate;
|
|
1117
1097
|
}
|
|
1098
|
+
/**
|
|
1099
|
+
* Verify a certificate against this CA.
|
|
1100
|
+
*
|
|
1101
|
+
* @param certificate - path to the certificate file to verify
|
|
1102
|
+
*/
|
|
1118
1103
|
async verifyCertificate(certificate) {
|
|
1119
1104
|
const isImplemented = false;
|
|
1120
1105
|
if (isImplemented) {
|
|
@@ -1124,96 +1109,14 @@ var CertificateAuthority = class {
|
|
|
1124
1109
|
const _configOption = ` -config ${configFile}`;
|
|
1125
1110
|
_configOption;
|
|
1126
1111
|
await execute_openssl_no_failure(
|
|
1127
|
-
`verify -verbose -CAfile ${
|
|
1112
|
+
`verify -verbose -CAfile ${q(n2(this.caCertificateWithCrl))} ${q(n2(certificate))}`,
|
|
1128
1113
|
options
|
|
1129
1114
|
);
|
|
1130
1115
|
}
|
|
1131
1116
|
}
|
|
1132
1117
|
};
|
|
1133
1118
|
|
|
1134
|
-
// packages/node-opcua-pki/lib/ca/crypto_create_CA.ts
|
|
1135
|
-
var import_node_assert11 = __toESM(require("assert"));
|
|
1136
|
-
var import_node_fs10 = __toESM(require("fs"));
|
|
1137
|
-
var import_node_module = require("module");
|
|
1138
|
-
var import_node_os4 = __toESM(require("os"));
|
|
1139
|
-
var import_node_path7 = __toESM(require("path"));
|
|
1140
|
-
var import_chalk7 = __toESM(require("chalk"));
|
|
1141
|
-
var import_node_opcua_crypto6 = require("node-opcua-crypto");
|
|
1142
|
-
|
|
1143
|
-
// packages/node-opcua-pki/lib/misc/applicationurn.ts
|
|
1144
|
-
var import_node_assert7 = __toESM(require("assert"));
|
|
1145
|
-
var import_node_crypto = require("crypto");
|
|
1146
|
-
function makeApplicationUrn(hostname, suffix) {
|
|
1147
|
-
let hostnameHash = hostname;
|
|
1148
|
-
if (hostnameHash.length + 7 + suffix.length >= 64) {
|
|
1149
|
-
hostnameHash = (0, import_node_crypto.createHash)("md5").update(hostname).digest("hex").substring(0, 16);
|
|
1150
|
-
}
|
|
1151
|
-
const applicationUrn = `urn:${hostnameHash}:${suffix}`;
|
|
1152
|
-
(0, import_node_assert7.default)(applicationUrn.length <= 64);
|
|
1153
|
-
return applicationUrn;
|
|
1154
|
-
}
|
|
1155
|
-
|
|
1156
|
-
// packages/node-opcua-pki/lib/misc/hostname.ts
|
|
1157
|
-
var import_node_dns = __toESM(require("dns"));
|
|
1158
|
-
var import_node_os3 = __toESM(require("os"));
|
|
1159
|
-
var import_node_util = require("util");
|
|
1160
|
-
function trim(str, length) {
|
|
1161
|
-
if (!length) {
|
|
1162
|
-
return str;
|
|
1163
|
-
}
|
|
1164
|
-
return str.substring(0, Math.min(str.length, length));
|
|
1165
|
-
}
|
|
1166
|
-
function fqdn(callback) {
|
|
1167
|
-
const uqdn = import_node_os3.default.hostname();
|
|
1168
|
-
import_node_dns.default.lookup(uqdn, { hints: import_node_dns.default.ADDRCONFIG }, (err1, ip) => {
|
|
1169
|
-
if (err1) {
|
|
1170
|
-
return callback(err1);
|
|
1171
|
-
}
|
|
1172
|
-
import_node_dns.default.lookupService(ip, 0, (err2, _fqdn) => {
|
|
1173
|
-
if (err2) {
|
|
1174
|
-
return callback(err2);
|
|
1175
|
-
}
|
|
1176
|
-
_fqdn = _fqdn.replace(".localdomain", "");
|
|
1177
|
-
callback(null, _fqdn);
|
|
1178
|
-
});
|
|
1179
|
-
});
|
|
1180
|
-
}
|
|
1181
|
-
var _fullyQualifiedDomainNameInCache;
|
|
1182
|
-
async function extractFullyQualifiedDomainName() {
|
|
1183
|
-
if (_fullyQualifiedDomainNameInCache) {
|
|
1184
|
-
return _fullyQualifiedDomainNameInCache;
|
|
1185
|
-
}
|
|
1186
|
-
if (process.platform === "win32") {
|
|
1187
|
-
const env = process.env;
|
|
1188
|
-
_fullyQualifiedDomainNameInCache = env.COMPUTERNAME + (env.USERDNSDOMAIN && env.USERDNSDOMAIN?.length > 0 ? `.${env.USERDNSDOMAIN}` : "");
|
|
1189
|
-
} else {
|
|
1190
|
-
try {
|
|
1191
|
-
_fullyQualifiedDomainNameInCache = await (0, import_node_util.promisify)(fqdn)();
|
|
1192
|
-
if (_fullyQualifiedDomainNameInCache === "localhost") {
|
|
1193
|
-
throw new Error("localhost not expected");
|
|
1194
|
-
}
|
|
1195
|
-
if (/sethostname/.test(_fullyQualifiedDomainNameInCache)) {
|
|
1196
|
-
throw new Error("Detecting fqdn on windows !!!");
|
|
1197
|
-
}
|
|
1198
|
-
} catch (_err) {
|
|
1199
|
-
_fullyQualifiedDomainNameInCache = import_node_os3.default.hostname();
|
|
1200
|
-
}
|
|
1201
|
-
}
|
|
1202
|
-
return _fullyQualifiedDomainNameInCache;
|
|
1203
|
-
}
|
|
1204
|
-
async function prepareFQDN() {
|
|
1205
|
-
_fullyQualifiedDomainNameInCache = await extractFullyQualifiedDomainName();
|
|
1206
|
-
}
|
|
1207
|
-
function getFullyQualifiedDomainName(optional_max_length) {
|
|
1208
|
-
if (!_fullyQualifiedDomainNameInCache) {
|
|
1209
|
-
throw new Error("FullyQualifiedDomainName computation is not completed yet");
|
|
1210
|
-
}
|
|
1211
|
-
return _fullyQualifiedDomainNameInCache ? trim(_fullyQualifiedDomainNameInCache, optional_max_length) : "%FQDN%";
|
|
1212
|
-
}
|
|
1213
|
-
prepareFQDN();
|
|
1214
|
-
|
|
1215
1119
|
// packages/node-opcua-pki/lib/pki/certificate_manager.ts
|
|
1216
|
-
var import_node_assert10 = __toESM(require("assert"));
|
|
1217
1120
|
var import_node_fs9 = __toESM(require("fs"));
|
|
1218
1121
|
var import_node_path6 = __toESM(require("path"));
|
|
1219
1122
|
var import_global_mutex = require("@ster5/global-mutex");
|
|
@@ -1222,18 +1125,18 @@ var import_chokidar = __toESM(require("chokidar"));
|
|
|
1222
1125
|
var import_node_opcua_crypto5 = require("node-opcua-crypto");
|
|
1223
1126
|
|
|
1224
1127
|
// packages/node-opcua-pki/lib/toolbox/without_openssl/create_certificate_signing_request.ts
|
|
1225
|
-
var
|
|
1128
|
+
var import_node_assert7 = __toESM(require("assert"));
|
|
1226
1129
|
var import_node_fs7 = __toESM(require("fs"));
|
|
1227
1130
|
var import_node_opcua_crypto3 = require("node-opcua-crypto");
|
|
1228
1131
|
async function createCertificateSigningRequestAsync(certificateSigningRequestFilename, params) {
|
|
1229
|
-
(0,
|
|
1230
|
-
(0,
|
|
1231
|
-
(0,
|
|
1232
|
-
(0,
|
|
1233
|
-
(0,
|
|
1234
|
-
(0,
|
|
1235
|
-
(0,
|
|
1236
|
-
(0,
|
|
1132
|
+
(0, import_node_assert7.default)(params);
|
|
1133
|
+
(0, import_node_assert7.default)(params.rootDir);
|
|
1134
|
+
(0, import_node_assert7.default)(params.configFile);
|
|
1135
|
+
(0, import_node_assert7.default)(params.privateKey);
|
|
1136
|
+
(0, import_node_assert7.default)(typeof params.privateKey === "string");
|
|
1137
|
+
(0, import_node_assert7.default)(import_node_fs7.default.existsSync(params.privateKey), `Private key must exist${params.privateKey}`);
|
|
1138
|
+
(0, import_node_assert7.default)(import_node_fs7.default.existsSync(params.rootDir), "RootDir key must exist");
|
|
1139
|
+
(0, import_node_assert7.default)(typeof certificateSigningRequestFilename === "string");
|
|
1237
1140
|
const subject = params.subject ? new import_node_opcua_crypto3.Subject(params.subject).toString() : void 0;
|
|
1238
1141
|
displaySubtitle("- Creating a Certificate Signing Request with subtile");
|
|
1239
1142
|
const privateKeyPem = await import_node_fs7.default.promises.readFile(params.privateKey, "utf-8");
|
|
@@ -1252,22 +1155,22 @@ async function createCertificateSigningRequestAsync(certificateSigningRequestFil
|
|
|
1252
1155
|
}
|
|
1253
1156
|
|
|
1254
1157
|
// packages/node-opcua-pki/lib/toolbox/without_openssl/create_self_signed_certificate.ts
|
|
1255
|
-
var
|
|
1158
|
+
var import_node_assert8 = __toESM(require("assert"));
|
|
1256
1159
|
var import_node_fs8 = __toESM(require("fs"));
|
|
1257
1160
|
var import_node_opcua_crypto4 = require("node-opcua-crypto");
|
|
1258
1161
|
async function createSelfSignedCertificateAsync(certificate, params) {
|
|
1259
1162
|
params.purpose = params.purpose || import_node_opcua_crypto4.CertificatePurpose.ForApplication;
|
|
1260
|
-
(0,
|
|
1261
|
-
(0,
|
|
1262
|
-
(0,
|
|
1263
|
-
(0,
|
|
1163
|
+
(0, import_node_assert8.default)(params.purpose, "Please provide a Certificate Purpose");
|
|
1164
|
+
(0, import_node_assert8.default)(import_node_fs8.default.existsSync(params.configFile));
|
|
1165
|
+
(0, import_node_assert8.default)(import_node_fs8.default.existsSync(params.rootDir));
|
|
1166
|
+
(0, import_node_assert8.default)(import_node_fs8.default.existsSync(params.privateKey));
|
|
1264
1167
|
if (!params.subject) {
|
|
1265
1168
|
throw Error("Missing subject");
|
|
1266
1169
|
}
|
|
1267
|
-
(0,
|
|
1268
|
-
(0,
|
|
1170
|
+
(0, import_node_assert8.default)(typeof params.applicationUri === "string");
|
|
1171
|
+
(0, import_node_assert8.default)(Array.isArray(params.dns));
|
|
1269
1172
|
adjustDate(params);
|
|
1270
|
-
(0,
|
|
1173
|
+
(0, import_node_assert8.default)(Object.prototype.hasOwnProperty.call(params, "validity"));
|
|
1271
1174
|
let subject = new import_node_opcua_crypto4.Subject(params.subject);
|
|
1272
1175
|
subject = subject.toString();
|
|
1273
1176
|
const purpose = params.purpose;
|
|
@@ -1298,6 +1201,12 @@ var simple_config_template_cnf_default = config3;
|
|
|
1298
1201
|
// packages/node-opcua-pki/lib/pki/certificate_manager.ts
|
|
1299
1202
|
var configurationFileSimpleTemplate = simple_config_template_cnf_default;
|
|
1300
1203
|
var fsWriteFile = import_node_fs9.default.promises.writeFile;
|
|
1204
|
+
function getOrComputeInfo(entry) {
|
|
1205
|
+
if (!entry.info) {
|
|
1206
|
+
entry.info = (0, import_node_opcua_crypto5.exploreCertificate)(entry.certificate);
|
|
1207
|
+
}
|
|
1208
|
+
return entry.info;
|
|
1209
|
+
}
|
|
1301
1210
|
var VerificationStatus = /* @__PURE__ */ ((VerificationStatus2) => {
|
|
1302
1211
|
VerificationStatus2["BadCertificateInvalid"] = "BadCertificateInvalid";
|
|
1303
1212
|
VerificationStatus2["BadSecurityChecksFailed"] = "BadSecurityChecksFailed";
|
|
@@ -1318,28 +1227,28 @@ var VerificationStatus = /* @__PURE__ */ ((VerificationStatus2) => {
|
|
|
1318
1227
|
return VerificationStatus2;
|
|
1319
1228
|
})(VerificationStatus || {});
|
|
1320
1229
|
function makeFingerprint(certificate) {
|
|
1321
|
-
|
|
1230
|
+
const chain = (0, import_node_opcua_crypto5.split_der)(certificate);
|
|
1231
|
+
return (0, import_node_opcua_crypto5.makeSHA1Thumbprint)(chain[0]).toString("hex");
|
|
1322
1232
|
}
|
|
1323
1233
|
function short(stringToShorten) {
|
|
1324
1234
|
return stringToShorten.substring(0, 10);
|
|
1325
1235
|
}
|
|
1326
1236
|
var forbiddenChars = /[\x00-\x1F<>:"/\\|?*]/g;
|
|
1327
1237
|
function buildIdealCertificateName(certificate) {
|
|
1328
|
-
const
|
|
1238
|
+
const fingerprint = makeFingerprint(certificate);
|
|
1329
1239
|
try {
|
|
1330
1240
|
const commonName = (0, import_node_opcua_crypto5.exploreCertificate)(certificate).tbsCertificate.subject.commonName || "";
|
|
1331
1241
|
const sanitizedCommonName = commonName.replace(forbiddenChars, "_");
|
|
1332
|
-
return `${sanitizedCommonName}[${
|
|
1242
|
+
return `${sanitizedCommonName}[${fingerprint}]`;
|
|
1333
1243
|
} catch (_err) {
|
|
1334
|
-
return `invalid_certificate_[${
|
|
1244
|
+
return `invalid_certificate_[${fingerprint}]`;
|
|
1335
1245
|
}
|
|
1336
1246
|
}
|
|
1337
1247
|
function findMatchingIssuerKey(entries, wantedIssuerKey) {
|
|
1338
|
-
|
|
1339
|
-
const info = (
|
|
1248
|
+
return entries.filter((entry) => {
|
|
1249
|
+
const info = getOrComputeInfo(entry);
|
|
1340
1250
|
return info.tbsCertificate.extensions && info.tbsCertificate.extensions.subjectKeyIdentifier === wantedIssuerKey;
|
|
1341
1251
|
});
|
|
1342
|
-
return selected;
|
|
1343
1252
|
}
|
|
1344
1253
|
function isSelfSigned2(info) {
|
|
1345
1254
|
return info.tbsCertificate.extensions?.subjectKeyIdentifier === info.tbsCertificate.extensions?.authorityKeyIdentifier?.keyIdentifier;
|
|
@@ -1382,84 +1291,157 @@ var CertificateManagerState = /* @__PURE__ */ ((CertificateManagerState2) => {
|
|
|
1382
1291
|
CertificateManagerState2[CertificateManagerState2["Disposed"] = 4] = "Disposed";
|
|
1383
1292
|
return CertificateManagerState2;
|
|
1384
1293
|
})(CertificateManagerState || {});
|
|
1385
|
-
var CertificateManager = class {
|
|
1294
|
+
var CertificateManager = class _CertificateManager {
|
|
1295
|
+
// ── Global instance registry ─────────────────────────────────
|
|
1296
|
+
// Tracks all initialized CertificateManager instances so their
|
|
1297
|
+
// file watchers can be closed automatically on process exit,
|
|
1298
|
+
// even if the consumer forgets to call dispose().
|
|
1299
|
+
static #activeInstances = /* @__PURE__ */ new Set();
|
|
1300
|
+
static #cleanupInstalled = false;
|
|
1301
|
+
static #installProcessCleanup() {
|
|
1302
|
+
if (_CertificateManager.#cleanupInstalled) return;
|
|
1303
|
+
_CertificateManager.#cleanupInstalled = true;
|
|
1304
|
+
const closeDanglingWatchers = () => {
|
|
1305
|
+
for (const cm of _CertificateManager.#activeInstances) {
|
|
1306
|
+
for (const w of cm.#watchers) {
|
|
1307
|
+
try {
|
|
1308
|
+
w.close();
|
|
1309
|
+
} catch {
|
|
1310
|
+
}
|
|
1311
|
+
}
|
|
1312
|
+
cm.#watchers.splice(0);
|
|
1313
|
+
cm.state = 4 /* Disposed */;
|
|
1314
|
+
}
|
|
1315
|
+
_CertificateManager.#activeInstances.clear();
|
|
1316
|
+
};
|
|
1317
|
+
process.on("beforeExit", closeDanglingWatchers);
|
|
1318
|
+
for (const signal of ["SIGINT", "SIGTERM"]) {
|
|
1319
|
+
process.once(signal, () => {
|
|
1320
|
+
closeDanglingWatchers();
|
|
1321
|
+
process.exit();
|
|
1322
|
+
});
|
|
1323
|
+
}
|
|
1324
|
+
}
|
|
1325
|
+
/**
|
|
1326
|
+
* Dispose **all** active CertificateManager instances,
|
|
1327
|
+
* closing their file watchers and freeing resources.
|
|
1328
|
+
*
|
|
1329
|
+
* This is mainly useful in test tear-down to ensure the
|
|
1330
|
+
* Node.js process can exit cleanly.
|
|
1331
|
+
*/
|
|
1332
|
+
static async disposeAll() {
|
|
1333
|
+
const instances = [..._CertificateManager.#activeInstances];
|
|
1334
|
+
await Promise.all(instances.map((cm) => cm.dispose()));
|
|
1335
|
+
}
|
|
1336
|
+
/**
|
|
1337
|
+
* Assert that all CertificateManager instances have been
|
|
1338
|
+
* properly disposed. Throws an Error listing the locations
|
|
1339
|
+
* of any leaked instances.
|
|
1340
|
+
*
|
|
1341
|
+
* Intended for use in test `afterAll()` / `afterEach()`
|
|
1342
|
+
* hooks to catch missing `dispose()` calls early.
|
|
1343
|
+
*
|
|
1344
|
+
* @example
|
|
1345
|
+
* ```ts
|
|
1346
|
+
* after(() => {
|
|
1347
|
+
* CertificateManager.checkAllDisposed();
|
|
1348
|
+
* });
|
|
1349
|
+
* ```
|
|
1350
|
+
*/
|
|
1351
|
+
static checkAllDisposed() {
|
|
1352
|
+
if (_CertificateManager.#activeInstances.size === 0) return;
|
|
1353
|
+
const locations = [..._CertificateManager.#activeInstances].map((cm) => cm.rootDir);
|
|
1354
|
+
throw new Error(
|
|
1355
|
+
`${_CertificateManager.#activeInstances.size} CertificateManager instance(s) not disposed:
|
|
1356
|
+
- ${locations.join("\n - ")}`
|
|
1357
|
+
);
|
|
1358
|
+
}
|
|
1359
|
+
// ─────────────────────────────────────────────────────────────
|
|
1360
|
+
/**
|
|
1361
|
+
* When `true` (the default), any certificate that is not
|
|
1362
|
+
* already in the trusted or rejected store is automatically
|
|
1363
|
+
* written to the rejected folder the first time it is seen.
|
|
1364
|
+
*/
|
|
1386
1365
|
untrustUnknownCertificate = true;
|
|
1366
|
+
/** Current lifecycle state of this instance. */
|
|
1387
1367
|
state = 0 /* Uninitialized */;
|
|
1368
|
+
/** @deprecated Use {@link folderPollingInterval} instead (typo fix). */
|
|
1388
1369
|
folderPoolingInterval = 5e3;
|
|
1370
|
+
/** Interval in milliseconds for file-system polling (when enabled). */
|
|
1371
|
+
get folderPollingInterval() {
|
|
1372
|
+
return this.folderPoolingInterval;
|
|
1373
|
+
}
|
|
1374
|
+
set folderPollingInterval(value) {
|
|
1375
|
+
this.folderPoolingInterval = value;
|
|
1376
|
+
}
|
|
1377
|
+
/** RSA key size used when generating the private key. */
|
|
1389
1378
|
keySize;
|
|
1390
|
-
location;
|
|
1391
|
-
|
|
1392
|
-
|
|
1393
|
-
|
|
1394
|
-
|
|
1395
|
-
|
|
1396
|
-
|
|
1379
|
+
#location;
|
|
1380
|
+
#watchers = [];
|
|
1381
|
+
#readCertificatesCalled = false;
|
|
1382
|
+
#filenameToHash = /* @__PURE__ */ new Map();
|
|
1383
|
+
#initializingPromise;
|
|
1384
|
+
#thumbs = {
|
|
1385
|
+
rejected: /* @__PURE__ */ new Map(),
|
|
1386
|
+
trusted: /* @__PURE__ */ new Map(),
|
|
1397
1387
|
issuers: {
|
|
1398
|
-
certs:
|
|
1388
|
+
certs: /* @__PURE__ */ new Map()
|
|
1399
1389
|
},
|
|
1400
|
-
crl:
|
|
1401
|
-
issuersCrl:
|
|
1390
|
+
crl: /* @__PURE__ */ new Map(),
|
|
1391
|
+
issuersCrl: /* @__PURE__ */ new Map()
|
|
1402
1392
|
};
|
|
1393
|
+
/**
|
|
1394
|
+
* Create a new CertificateManager.
|
|
1395
|
+
*
|
|
1396
|
+
* The constructor creates the root directory if it does not
|
|
1397
|
+
* exist but does **not** initialise the PKI store — call
|
|
1398
|
+
* {@link initialize} before using any other method.
|
|
1399
|
+
*
|
|
1400
|
+
* @param options - configuration options
|
|
1401
|
+
*/
|
|
1403
1402
|
constructor(options) {
|
|
1404
1403
|
options.keySize = options.keySize || 2048;
|
|
1405
|
-
|
|
1406
|
-
|
|
1407
|
-
|
|
1408
|
-
this
|
|
1404
|
+
if (!options.location) {
|
|
1405
|
+
throw new Error("CertificateManager: missing 'location' option");
|
|
1406
|
+
}
|
|
1407
|
+
this.#location = makePath(options.location, "");
|
|
1409
1408
|
this.keySize = options.keySize;
|
|
1410
1409
|
mkdirRecursiveSync(options.location);
|
|
1411
|
-
if (!import_node_fs9.default.existsSync(this
|
|
1412
|
-
throw new Error(`CertificateManager cannot access location ${this
|
|
1410
|
+
if (!import_node_fs9.default.existsSync(this.#location)) {
|
|
1411
|
+
throw new Error(`CertificateManager cannot access location ${this.#location}`);
|
|
1413
1412
|
}
|
|
1414
1413
|
}
|
|
1414
|
+
/** Path to the OpenSSL configuration file. */
|
|
1415
1415
|
get configFile() {
|
|
1416
1416
|
return import_node_path6.default.join(this.rootDir, "own/openssl.cnf");
|
|
1417
1417
|
}
|
|
1418
|
+
/** Root directory of the PKI store. */
|
|
1418
1419
|
get rootDir() {
|
|
1419
|
-
return this
|
|
1420
|
+
return this.#location;
|
|
1420
1421
|
}
|
|
1422
|
+
/** Path to the private key file (`own/private/private_key.pem`). */
|
|
1421
1423
|
get privateKey() {
|
|
1422
1424
|
return import_node_path6.default.join(this.rootDir, "own/private/private_key.pem");
|
|
1423
1425
|
}
|
|
1426
|
+
/** Path to the OpenSSL random seed file. */
|
|
1424
1427
|
get randomFile() {
|
|
1425
1428
|
return import_node_path6.default.join(this.rootDir, "./random.rnd");
|
|
1426
1429
|
}
|
|
1427
|
-
/**
|
|
1428
|
-
* returns the certificate status trusted/rejected
|
|
1429
|
-
* @param certificate
|
|
1430
|
-
*/
|
|
1431
|
-
async getCertificateStatus(certificate) {
|
|
1432
|
-
await this.initialize();
|
|
1433
|
-
let status = await this._checkRejectedOrTrusted(certificate);
|
|
1434
|
-
if (status === "unknown") {
|
|
1435
|
-
(0, import_node_assert10.default)(certificate instanceof Buffer);
|
|
1436
|
-
const pem = (0, import_node_opcua_crypto5.toPem)(certificate, "CERTIFICATE");
|
|
1437
|
-
const fingerprint2 = makeFingerprint(certificate);
|
|
1438
|
-
const filename = import_node_path6.default.join(this.rejectedFolder, `${buildIdealCertificateName(certificate)}.pem`);
|
|
1439
|
-
await import_node_fs9.default.promises.writeFile(filename, pem);
|
|
1440
|
-
this._thumbs.rejected[fingerprint2] = {
|
|
1441
|
-
certificate,
|
|
1442
|
-
filename
|
|
1443
|
-
};
|
|
1444
|
-
status = "rejected";
|
|
1445
|
-
}
|
|
1446
|
-
return status;
|
|
1447
|
-
}
|
|
1448
1430
|
/**
|
|
1449
1431
|
* Move a certificate to the rejected store.
|
|
1450
|
-
* If the certificate was previously trusted, it will be
|
|
1432
|
+
* If the certificate was previously trusted, it will be removed from the trusted folder.
|
|
1451
1433
|
* @param certificate - the DER-encoded certificate
|
|
1452
1434
|
*/
|
|
1453
1435
|
async rejectCertificate(certificate) {
|
|
1454
|
-
await this
|
|
1436
|
+
await this.#moveCertificate(certificate, "rejected");
|
|
1455
1437
|
}
|
|
1456
1438
|
/**
|
|
1457
1439
|
* Move a certificate to the trusted store.
|
|
1458
|
-
* If the certificate was previously rejected, it will be
|
|
1440
|
+
* If the certificate was previously rejected, it will be removed from the rejected folder.
|
|
1459
1441
|
* @param certificate - the DER-encoded certificate
|
|
1460
1442
|
*/
|
|
1461
1443
|
async trustCertificate(certificate) {
|
|
1462
|
-
await this
|
|
1444
|
+
await this.#moveCertificate(certificate, "trusted");
|
|
1463
1445
|
}
|
|
1464
1446
|
/** Path to the rejected certificates folder. */
|
|
1465
1447
|
get rejectedFolder() {
|
|
@@ -1481,6 +1463,13 @@ var CertificateManager = class {
|
|
|
1481
1463
|
get issuersCrlFolder() {
|
|
1482
1464
|
return import_node_path6.default.join(this.rootDir, "issuers/crl");
|
|
1483
1465
|
}
|
|
1466
|
+
/** Path to the own certificate folder. */
|
|
1467
|
+
get ownCertFolder() {
|
|
1468
|
+
return import_node_path6.default.join(this.rootDir, "own/certs");
|
|
1469
|
+
}
|
|
1470
|
+
get ownPrivateFolder() {
|
|
1471
|
+
return import_node_path6.default.join(this.rootDir, "own/private");
|
|
1472
|
+
}
|
|
1484
1473
|
/**
|
|
1485
1474
|
* Check if a certificate is in the trusted store.
|
|
1486
1475
|
* If the certificate is unknown and `untrustUnknownCertificate` is set,
|
|
@@ -1490,33 +1479,27 @@ var CertificateManager = class {
|
|
|
1490
1479
|
* or `"BadCertificateInvalid"` if the certificate cannot be parsed.
|
|
1491
1480
|
*/
|
|
1492
1481
|
async isCertificateTrusted(certificate) {
|
|
1493
|
-
const
|
|
1494
|
-
|
|
1495
|
-
if (certificateInTrust) {
|
|
1482
|
+
const fingerprint = makeFingerprint(certificate);
|
|
1483
|
+
if (this.#thumbs.trusted.has(fingerprint)) {
|
|
1496
1484
|
return "Good";
|
|
1497
|
-
}
|
|
1498
|
-
|
|
1499
|
-
if (!
|
|
1500
|
-
|
|
1501
|
-
this.rejectedFolder,
|
|
1502
|
-
`${buildIdealCertificateName(certificate)}.pem`
|
|
1503
|
-
);
|
|
1504
|
-
if (!this.untrustUnknownCertificate) {
|
|
1505
|
-
return "Good";
|
|
1506
|
-
}
|
|
1507
|
-
try {
|
|
1508
|
-
const _certificateInfo = (0, import_node_opcua_crypto5.exploreCertificateInfo)(certificate);
|
|
1509
|
-
_certificateInfo;
|
|
1510
|
-
} catch (_err) {
|
|
1511
|
-
return "BadCertificateInvalid";
|
|
1512
|
-
}
|
|
1513
|
-
debugLog("certificate has never been seen before and is now rejected (untrusted) ", certificateFilenameInRejected);
|
|
1514
|
-
await fsWriteFile(certificateFilenameInRejected, (0, import_node_opcua_crypto5.toPem)(certificate, "CERTIFICATE"));
|
|
1485
|
+
}
|
|
1486
|
+
if (!this.#thumbs.rejected.has(fingerprint)) {
|
|
1487
|
+
if (!this.untrustUnknownCertificate) {
|
|
1488
|
+
return "Good";
|
|
1515
1489
|
}
|
|
1516
|
-
|
|
1490
|
+
try {
|
|
1491
|
+
(0, import_node_opcua_crypto5.exploreCertificateInfo)(certificate);
|
|
1492
|
+
} catch (_err) {
|
|
1493
|
+
return "BadCertificateInvalid";
|
|
1494
|
+
}
|
|
1495
|
+
const filename = import_node_path6.default.join(this.rejectedFolder, `${buildIdealCertificateName(certificate)}.pem`);
|
|
1496
|
+
debugLog("certificate has never been seen before and is now rejected (untrusted) ", filename);
|
|
1497
|
+
await fsWriteFile(filename, (0, import_node_opcua_crypto5.toPem)(certificate, "CERTIFICATE"));
|
|
1498
|
+
this.#thumbs.rejected.set(fingerprint, { certificate, filename });
|
|
1517
1499
|
}
|
|
1500
|
+
return "BadCertificateUntrusted";
|
|
1518
1501
|
}
|
|
1519
|
-
async
|
|
1502
|
+
async #innerVerifyCertificateAsync(certificate, _isIssuer, level, options) {
|
|
1520
1503
|
if (level >= 5) {
|
|
1521
1504
|
return "BadSecurityChecksFailed" /* BadSecurityChecksFailed */;
|
|
1522
1505
|
}
|
|
@@ -1551,7 +1534,7 @@ var CertificateManager = class {
|
|
|
1551
1534
|
} else {
|
|
1552
1535
|
debugLog(" the issuer certificate has been found in the issuer.cert folder !");
|
|
1553
1536
|
}
|
|
1554
|
-
const issuerStatus = await this
|
|
1537
|
+
const issuerStatus = await this.#innerVerifyCertificateAsync(issuerCertificate, true, level + 1, options);
|
|
1555
1538
|
if (issuerStatus === "BadCertificateRevocationUnknown" /* BadCertificateRevocationUnknown */) {
|
|
1556
1539
|
return "BadCertificateIssuerRevocationUnknown" /* BadCertificateIssuerRevocationUnknown */;
|
|
1557
1540
|
}
|
|
@@ -1585,7 +1568,7 @@ var CertificateManager = class {
|
|
|
1585
1568
|
debugLog("revokedStatus", revokedStatus);
|
|
1586
1569
|
return revokedStatus;
|
|
1587
1570
|
}
|
|
1588
|
-
const issuerTrustedStatus = await this
|
|
1571
|
+
const issuerTrustedStatus = await this.#checkRejectedOrTrusted(issuerCertificate);
|
|
1589
1572
|
debugLog("issuerTrustedStatus", issuerTrustedStatus);
|
|
1590
1573
|
if (issuerTrustedStatus === "unknown") {
|
|
1591
1574
|
hasTrustedIssuer = false;
|
|
@@ -1604,12 +1587,14 @@ var CertificateManager = class {
|
|
|
1604
1587
|
debugLog("revokedStatus of self signed certificate:", revokedStatus);
|
|
1605
1588
|
}
|
|
1606
1589
|
}
|
|
1607
|
-
const status = await this
|
|
1590
|
+
const status = await this.#checkRejectedOrTrusted(certificate);
|
|
1608
1591
|
if (status === "rejected") {
|
|
1609
|
-
|
|
1592
|
+
if (!(options.acceptCertificateWithValidIssuerChain && hasValidIssuer && hasTrustedIssuer)) {
|
|
1593
|
+
return "BadCertificateUntrusted" /* BadCertificateUntrusted */;
|
|
1594
|
+
}
|
|
1610
1595
|
}
|
|
1611
1596
|
const _c2 = chain[1] ? (0, import_node_opcua_crypto5.exploreCertificateInfo)(chain[1]) : "non";
|
|
1612
|
-
_c2;
|
|
1597
|
+
debugLog("chain[1] info=", _c2);
|
|
1613
1598
|
const certificateInfo = (0, import_node_opcua_crypto5.exploreCertificateInfo)(certificate);
|
|
1614
1599
|
const now = /* @__PURE__ */ new Date();
|
|
1615
1600
|
let isTimeInvalid = false;
|
|
@@ -1632,7 +1617,6 @@ var CertificateManager = class {
|
|
|
1632
1617
|
if (status === "trusted") {
|
|
1633
1618
|
return isTimeInvalid ? "BadCertificateTimeInvalid" /* BadCertificateTimeInvalid */ : "Good" /* Good */;
|
|
1634
1619
|
}
|
|
1635
|
-
(0, import_node_assert10.default)(status === "unknown");
|
|
1636
1620
|
if (hasIssuerKey) {
|
|
1637
1621
|
if (!hasTrustedIssuer) {
|
|
1638
1622
|
return "BadCertificateUntrusted" /* BadCertificateUntrusted */;
|
|
@@ -1640,47 +1624,74 @@ var CertificateManager = class {
|
|
|
1640
1624
|
if (!hasValidIssuer) {
|
|
1641
1625
|
return "BadCertificateUntrusted" /* BadCertificateUntrusted */;
|
|
1642
1626
|
}
|
|
1627
|
+
if (!options.acceptCertificateWithValidIssuerChain) {
|
|
1628
|
+
return "BadCertificateUntrusted" /* BadCertificateUntrusted */;
|
|
1629
|
+
}
|
|
1643
1630
|
return isTimeInvalid ? "BadCertificateTimeInvalid" /* BadCertificateTimeInvalid */ : "Good" /* Good */;
|
|
1644
1631
|
} else {
|
|
1645
1632
|
return "BadCertificateUntrusted" /* BadCertificateUntrusted */;
|
|
1646
1633
|
}
|
|
1647
1634
|
}
|
|
1635
|
+
/**
|
|
1636
|
+
* Internal verification hook called by {@link verifyCertificate}.
|
|
1637
|
+
*
|
|
1638
|
+
* Subclasses can override this to inject additional validation
|
|
1639
|
+
* logic (e.g. application-level policy checks) while still
|
|
1640
|
+
* delegating to the default chain/CRL/trust verification.
|
|
1641
|
+
*
|
|
1642
|
+
* @param certificate - the DER-encoded certificate to verify
|
|
1643
|
+
* @param options - verification options forwarded from the
|
|
1644
|
+
* public API
|
|
1645
|
+
* @returns the verification status code
|
|
1646
|
+
*/
|
|
1648
1647
|
async verifyCertificateAsync(certificate, options) {
|
|
1649
|
-
const status1 = await this
|
|
1648
|
+
const status1 = await this.#innerVerifyCertificateAsync(certificate, false, 0, options);
|
|
1650
1649
|
return status1;
|
|
1651
1650
|
}
|
|
1652
1651
|
/**
|
|
1653
|
-
* Verify certificate
|
|
1654
|
-
*
|
|
1655
|
-
*
|
|
1652
|
+
* Verify a certificate against the PKI trust store.
|
|
1653
|
+
*
|
|
1654
|
+
* This performs a full validation including trust status,
|
|
1655
|
+
* issuer chain, CRL revocation checks, and time validity.
|
|
1656
|
+
*
|
|
1657
|
+
* @param certificate - the DER-encoded certificate to verify
|
|
1658
|
+
* @param options - optional flags to relax validation rules
|
|
1659
|
+
* @returns the verification status code
|
|
1656
1660
|
*/
|
|
1657
1661
|
async verifyCertificate(certificate, options) {
|
|
1658
1662
|
if (!certificate) {
|
|
1659
1663
|
return "BadSecurityChecksFailed" /* BadSecurityChecksFailed */;
|
|
1660
1664
|
}
|
|
1661
|
-
|
|
1665
|
+
try {
|
|
1666
|
+
const status = await this.verifyCertificateAsync(certificate, options || {});
|
|
1667
|
+
return status;
|
|
1668
|
+
} catch (error) {
|
|
1669
|
+
warningLog(`verifyCertificate error: ${error.message}`);
|
|
1670
|
+
return "BadCertificateInvalid" /* BadCertificateInvalid */;
|
|
1671
|
+
}
|
|
1662
1672
|
}
|
|
1663
|
-
|
|
1664
|
-
*
|
|
1665
|
-
*
|
|
1666
|
-
* +---> trusted
|
|
1667
|
-
* +---> rejected
|
|
1668
|
-
* +---> own
|
|
1669
|
-
* +---> cert
|
|
1670
|
-
* +---> own
|
|
1673
|
+
/**
|
|
1674
|
+
* Initialize the PKI directory structure, generate the
|
|
1675
|
+
* private key (if missing), and start file-system watchers.
|
|
1671
1676
|
*
|
|
1677
|
+
* This method is idempotent — subsequent calls are no-ops.
|
|
1678
|
+
* It must be called before any certificate operations.
|
|
1672
1679
|
*/
|
|
1673
1680
|
async initialize() {
|
|
1674
1681
|
if (this.state !== 0 /* Uninitialized */) {
|
|
1675
1682
|
return;
|
|
1676
1683
|
}
|
|
1677
1684
|
this.state = 1 /* Initializing */;
|
|
1678
|
-
|
|
1685
|
+
this.#initializingPromise = this.#initialize();
|
|
1686
|
+
await this.#initializingPromise;
|
|
1687
|
+
this.#initializingPromise = void 0;
|
|
1679
1688
|
this.state = 2 /* Initialized */;
|
|
1689
|
+
_CertificateManager.#activeInstances.add(this);
|
|
1690
|
+
_CertificateManager.#installProcessCleanup();
|
|
1680
1691
|
}
|
|
1681
|
-
async
|
|
1692
|
+
async #initialize() {
|
|
1682
1693
|
this.state = 1 /* Initializing */;
|
|
1683
|
-
const pkiDir = this
|
|
1694
|
+
const pkiDir = this.#location;
|
|
1684
1695
|
mkdirRecursiveSync(pkiDir);
|
|
1685
1696
|
mkdirRecursiveSync(import_node_path6.default.join(pkiDir, "own"));
|
|
1686
1697
|
mkdirRecursiveSync(import_node_path6.default.join(pkiDir, "own/certs"));
|
|
@@ -1694,24 +1705,22 @@ var CertificateManager = class {
|
|
|
1694
1705
|
mkdirRecursiveSync(import_node_path6.default.join(pkiDir, "issuers/crl"));
|
|
1695
1706
|
if (!import_node_fs9.default.existsSync(this.configFile) || !import_node_fs9.default.existsSync(this.privateKey)) {
|
|
1696
1707
|
return await this.withLock2(async () => {
|
|
1697
|
-
|
|
1698
|
-
if (this.state === 4 /* Disposed */) {
|
|
1708
|
+
if (this.state === 3 /* Disposing */ || this.state === 4 /* Disposed */) {
|
|
1699
1709
|
return;
|
|
1700
1710
|
}
|
|
1701
|
-
(0, import_node_assert10.default)(this.state === 1 /* Initializing */);
|
|
1702
1711
|
if (!import_node_fs9.default.existsSync(this.configFile)) {
|
|
1703
1712
|
import_node_fs9.default.writeFileSync(this.configFile, configurationFileSimpleTemplate);
|
|
1704
1713
|
}
|
|
1705
1714
|
if (!import_node_fs9.default.existsSync(this.privateKey)) {
|
|
1706
1715
|
debugLog("generating private key ...");
|
|
1707
1716
|
await (0, import_node_opcua_crypto5.generatePrivateKeyFile)(this.privateKey, this.keySize);
|
|
1708
|
-
await this
|
|
1717
|
+
await this.#readCertificates();
|
|
1709
1718
|
} else {
|
|
1710
|
-
await this
|
|
1719
|
+
await this.#readCertificates();
|
|
1711
1720
|
}
|
|
1712
1721
|
});
|
|
1713
1722
|
} else {
|
|
1714
|
-
await this
|
|
1723
|
+
await this.#readCertificates();
|
|
1715
1724
|
}
|
|
1716
1725
|
}
|
|
1717
1726
|
/**
|
|
@@ -1728,20 +1737,46 @@ var CertificateManager = class {
|
|
|
1728
1737
|
return;
|
|
1729
1738
|
}
|
|
1730
1739
|
if (this.state === 1 /* Initializing */) {
|
|
1731
|
-
|
|
1732
|
-
|
|
1740
|
+
if (this.#initializingPromise) {
|
|
1741
|
+
await this.#initializingPromise;
|
|
1742
|
+
}
|
|
1733
1743
|
}
|
|
1734
1744
|
try {
|
|
1735
1745
|
this.state = 3 /* Disposing */;
|
|
1736
|
-
await Promise.all(this.
|
|
1737
|
-
this.
|
|
1746
|
+
await Promise.all(this.#watchers.map((w) => w.close()));
|
|
1747
|
+
this.#watchers.forEach((w) => {
|
|
1738
1748
|
w.removeAllListeners();
|
|
1739
1749
|
});
|
|
1740
|
-
this.
|
|
1750
|
+
this.#watchers.splice(0);
|
|
1741
1751
|
} finally {
|
|
1742
1752
|
this.state = 4 /* Disposed */;
|
|
1753
|
+
_CertificateManager.#activeInstances.delete(this);
|
|
1743
1754
|
}
|
|
1744
1755
|
}
|
|
1756
|
+
/**
|
|
1757
|
+
* Force a full re-scan of all PKI folders, rebuilding
|
|
1758
|
+
* the in-memory `_thumbs` index from scratch.
|
|
1759
|
+
*
|
|
1760
|
+
* Call this after external processes have modified the
|
|
1761
|
+
* PKI folders (e.g. via `writeTrustList` or CLI tools)
|
|
1762
|
+
* to ensure the CertificateManager sees the latest
|
|
1763
|
+
* state without waiting for file-system events.
|
|
1764
|
+
*/
|
|
1765
|
+
async reloadCertificates() {
|
|
1766
|
+
await Promise.all(this.#watchers.map((w) => w.close()));
|
|
1767
|
+
for (const w of this.#watchers) {
|
|
1768
|
+
w.removeAllListeners();
|
|
1769
|
+
}
|
|
1770
|
+
this.#watchers.splice(0);
|
|
1771
|
+
this.#thumbs.rejected.clear();
|
|
1772
|
+
this.#thumbs.trusted.clear();
|
|
1773
|
+
this.#thumbs.issuers.certs.clear();
|
|
1774
|
+
this.#thumbs.crl.clear();
|
|
1775
|
+
this.#thumbs.issuersCrl.clear();
|
|
1776
|
+
this.#filenameToHash.clear();
|
|
1777
|
+
this.#readCertificatesCalled = false;
|
|
1778
|
+
await this.#readCertificates();
|
|
1779
|
+
}
|
|
1745
1780
|
async withLock2(action) {
|
|
1746
1781
|
const lockFileName = import_node_path6.default.join(this.rootDir, "mutex.lock");
|
|
1747
1782
|
return (0, import_global_mutex.withLock)({ fileToLock: lockFileName }, async () => {
|
|
@@ -1749,12 +1784,18 @@ var CertificateManager = class {
|
|
|
1749
1784
|
});
|
|
1750
1785
|
}
|
|
1751
1786
|
/**
|
|
1787
|
+
* Create a self-signed certificate for this PKI's private key.
|
|
1752
1788
|
*
|
|
1753
|
-
*
|
|
1789
|
+
* The certificate is written to `params.outputFile` or
|
|
1790
|
+
* `own/certs/self_signed_certificate.pem` by default.
|
|
1754
1791
|
*
|
|
1792
|
+
* @param params - certificate parameters (subject, SANs,
|
|
1793
|
+
* validity, etc.)
|
|
1755
1794
|
*/
|
|
1756
1795
|
async createSelfSignedCertificate(params) {
|
|
1757
|
-
|
|
1796
|
+
if (typeof params.applicationUri !== "string") {
|
|
1797
|
+
throw new Error("createSelfSignedCertificate: expecting applicationUri to be a string");
|
|
1798
|
+
}
|
|
1758
1799
|
if (!import_node_fs9.default.existsSync(this.privateKey)) {
|
|
1759
1800
|
throw new Error(`Cannot find private key ${this.privateKey}`);
|
|
1760
1801
|
}
|
|
@@ -1769,22 +1810,31 @@ var CertificateManager = class {
|
|
|
1769
1810
|
await createSelfSignedCertificate(certificateFilename, _params);
|
|
1770
1811
|
});
|
|
1771
1812
|
}
|
|
1813
|
+
/**
|
|
1814
|
+
* Create a Certificate Signing Request (CSR) using this
|
|
1815
|
+
* PKI's private key and configuration.
|
|
1816
|
+
*
|
|
1817
|
+
* The CSR file is written to `own/certs/` with a timestamped
|
|
1818
|
+
* filename.
|
|
1819
|
+
*
|
|
1820
|
+
* @param params - CSR parameters (subject, SANs)
|
|
1821
|
+
* @returns the filesystem path to the generated CSR file
|
|
1822
|
+
*/
|
|
1772
1823
|
async createCertificateRequest(params) {
|
|
1773
|
-
|
|
1824
|
+
if (!params) {
|
|
1825
|
+
throw new Error("params is required");
|
|
1826
|
+
}
|
|
1774
1827
|
const _params = params;
|
|
1775
1828
|
if (Object.prototype.hasOwnProperty.call(_params, "rootDir")) {
|
|
1776
1829
|
throw new Error("rootDir should not be specified ");
|
|
1777
1830
|
}
|
|
1778
|
-
(0, import_node_assert10.default)(!_params.rootDir);
|
|
1779
|
-
(0, import_node_assert10.default)(!_params.configFile);
|
|
1780
|
-
(0, import_node_assert10.default)(!_params.privateKey);
|
|
1781
1831
|
_params.rootDir = import_node_path6.default.resolve(this.rootDir);
|
|
1782
1832
|
_params.configFile = import_node_path6.default.resolve(this.configFile);
|
|
1783
1833
|
_params.privateKey = import_node_path6.default.resolve(this.privateKey);
|
|
1784
1834
|
return await this.withLock2(async () => {
|
|
1785
1835
|
const now = /* @__PURE__ */ new Date();
|
|
1786
|
-
const
|
|
1787
|
-
const certificateSigningRequestFilename = import_node_path6.default.join(this.rootDir, "own/certs", `certificate_${
|
|
1836
|
+
const today = `${now.toISOString().slice(0, 10)}_${now.getTime()}`;
|
|
1837
|
+
const certificateSigningRequestFilename = import_node_path6.default.join(this.rootDir, "own/certs", `certificate_${today}.csr`);
|
|
1788
1838
|
await createCertificateSigningRequestAsync(certificateSigningRequestFilename, _params);
|
|
1789
1839
|
return certificateSigningRequestFilename;
|
|
1790
1840
|
});
|
|
@@ -1805,13 +1855,13 @@ var CertificateManager = class {
|
|
|
1805
1855
|
}
|
|
1806
1856
|
}
|
|
1807
1857
|
const pemCertificate = (0, import_node_opcua_crypto5.toPem)(certificate, "CERTIFICATE");
|
|
1808
|
-
const
|
|
1809
|
-
if (this.
|
|
1858
|
+
const fingerprint = makeFingerprint(certificate);
|
|
1859
|
+
if (this.#thumbs.issuers.certs.has(fingerprint)) {
|
|
1810
1860
|
return "Good" /* Good */;
|
|
1811
1861
|
}
|
|
1812
1862
|
const filename = import_node_path6.default.join(this.issuersCertFolder, `issuer_${buildIdealCertificateName(certificate)}.pem`);
|
|
1813
1863
|
await import_node_fs9.default.promises.writeFile(filename, pemCertificate, "ascii");
|
|
1814
|
-
this.
|
|
1864
|
+
this.#thumbs.issuers.certs.set(fingerprint, { certificate, filename });
|
|
1815
1865
|
if (addInTrustList) {
|
|
1816
1866
|
await this.trustCertificate(certificate);
|
|
1817
1867
|
}
|
|
@@ -1825,18 +1875,18 @@ var CertificateManager = class {
|
|
|
1825
1875
|
async addRevocationList(crl, target = "issuers") {
|
|
1826
1876
|
return await this.withLock2(async () => {
|
|
1827
1877
|
try {
|
|
1828
|
-
const index = target === "trusted" ? this.
|
|
1878
|
+
const index = target === "trusted" ? this.#thumbs.crl : this.#thumbs.issuersCrl;
|
|
1829
1879
|
const folder = target === "trusted" ? this.crlFolder : this.issuersCrlFolder;
|
|
1830
1880
|
const crlInfo = (0, import_node_opcua_crypto5.exploreCertificateRevocationList)(crl);
|
|
1831
1881
|
const key = crlInfo.tbsCertList.issuerFingerprint;
|
|
1832
|
-
if (!index
|
|
1833
|
-
index
|
|
1882
|
+
if (!index.has(key)) {
|
|
1883
|
+
index.set(key, { crls: [], serialNumbers: {} });
|
|
1834
1884
|
}
|
|
1835
1885
|
const pemCertificate = (0, import_node_opcua_crypto5.toPem)(crl, "X509 CRL");
|
|
1836
1886
|
const filename = import_node_path6.default.join(folder, `crl_${buildIdealCertificateName(crl)}.pem`);
|
|
1837
1887
|
await import_node_fs9.default.promises.writeFile(filename, pemCertificate, "ascii");
|
|
1838
|
-
await this
|
|
1839
|
-
await this
|
|
1888
|
+
await this.#onCrlFileAdded(index, filename);
|
|
1889
|
+
await this.#waitAndCheckCRLProcessingStatus();
|
|
1840
1890
|
return "Good" /* Good */;
|
|
1841
1891
|
} catch (err) {
|
|
1842
1892
|
debugLog(err);
|
|
@@ -1865,15 +1915,13 @@ var CertificateManager = class {
|
|
|
1865
1915
|
throw err;
|
|
1866
1916
|
}
|
|
1867
1917
|
}
|
|
1868
|
-
|
|
1869
|
-
delete index[key];
|
|
1870
|
-
}
|
|
1918
|
+
index.clear();
|
|
1871
1919
|
};
|
|
1872
1920
|
if (target === "issuers" || target === "all") {
|
|
1873
|
-
await clearFolder(this.issuersCrlFolder, this.
|
|
1921
|
+
await clearFolder(this.issuersCrlFolder, this.#thumbs.issuersCrl);
|
|
1874
1922
|
}
|
|
1875
1923
|
if (target === "trusted" || target === "all") {
|
|
1876
|
-
await clearFolder(this.crlFolder, this.
|
|
1924
|
+
await clearFolder(this.crlFolder, this.#thumbs.crl);
|
|
1877
1925
|
}
|
|
1878
1926
|
}
|
|
1879
1927
|
/**
|
|
@@ -1882,9 +1930,9 @@ var CertificateManager = class {
|
|
|
1882
1930
|
* @param thumbprint - hex-encoded SHA-1 thumbprint (lowercase)
|
|
1883
1931
|
*/
|
|
1884
1932
|
async hasIssuer(thumbprint) {
|
|
1885
|
-
await this
|
|
1933
|
+
await this.#readCertificates();
|
|
1886
1934
|
const normalized = thumbprint.toLowerCase();
|
|
1887
|
-
return
|
|
1935
|
+
return this.#thumbs.issuers.certs.has(normalized);
|
|
1888
1936
|
}
|
|
1889
1937
|
/**
|
|
1890
1938
|
* Remove a trusted certificate identified by its SHA-1 thumbprint.
|
|
@@ -1894,9 +1942,9 @@ var CertificateManager = class {
|
|
|
1894
1942
|
* @returns the removed certificate buffer, or `null` if not found
|
|
1895
1943
|
*/
|
|
1896
1944
|
async removeTrustedCertificate(thumbprint) {
|
|
1897
|
-
await this
|
|
1945
|
+
await this.#readCertificates();
|
|
1898
1946
|
const normalized = thumbprint.toLowerCase();
|
|
1899
|
-
const entry = this.
|
|
1947
|
+
const entry = this.#thumbs.trusted.get(normalized);
|
|
1900
1948
|
if (!entry) {
|
|
1901
1949
|
return null;
|
|
1902
1950
|
}
|
|
@@ -1907,7 +1955,7 @@ var CertificateManager = class {
|
|
|
1907
1955
|
throw err;
|
|
1908
1956
|
}
|
|
1909
1957
|
}
|
|
1910
|
-
|
|
1958
|
+
this.#thumbs.trusted.delete(normalized);
|
|
1911
1959
|
return entry.certificate;
|
|
1912
1960
|
}
|
|
1913
1961
|
/**
|
|
@@ -1918,9 +1966,9 @@ var CertificateManager = class {
|
|
|
1918
1966
|
* @returns the removed certificate buffer, or `null` if not found
|
|
1919
1967
|
*/
|
|
1920
1968
|
async removeIssuer(thumbprint) {
|
|
1921
|
-
await this
|
|
1969
|
+
await this.#readCertificates();
|
|
1922
1970
|
const normalized = thumbprint.toLowerCase();
|
|
1923
|
-
const entry = this.
|
|
1971
|
+
const entry = this.#thumbs.issuers.certs.get(normalized);
|
|
1924
1972
|
if (!entry) {
|
|
1925
1973
|
return null;
|
|
1926
1974
|
}
|
|
@@ -1931,7 +1979,7 @@ var CertificateManager = class {
|
|
|
1931
1979
|
throw err;
|
|
1932
1980
|
}
|
|
1933
1981
|
}
|
|
1934
|
-
|
|
1982
|
+
this.#thumbs.issuers.certs.delete(normalized);
|
|
1935
1983
|
return entry.certificate;
|
|
1936
1984
|
}
|
|
1937
1985
|
/**
|
|
@@ -1944,7 +1992,7 @@ var CertificateManager = class {
|
|
|
1944
1992
|
const issuerInfo = (0, import_node_opcua_crypto5.exploreCertificate)(issuerCertificate);
|
|
1945
1993
|
const issuerFingerprint = issuerInfo.tbsCertificate.subjectFingerPrint;
|
|
1946
1994
|
const processIndex = async (index) => {
|
|
1947
|
-
const crlData = index
|
|
1995
|
+
const crlData = index.get(issuerFingerprint);
|
|
1948
1996
|
if (!crlData) return;
|
|
1949
1997
|
for (const crlEntry of crlData.crls) {
|
|
1950
1998
|
try {
|
|
@@ -1955,13 +2003,13 @@ var CertificateManager = class {
|
|
|
1955
2003
|
}
|
|
1956
2004
|
}
|
|
1957
2005
|
}
|
|
1958
|
-
delete
|
|
2006
|
+
index.delete(issuerFingerprint);
|
|
1959
2007
|
};
|
|
1960
2008
|
if (target === "issuers" || target === "all") {
|
|
1961
|
-
await processIndex(this.
|
|
2009
|
+
await processIndex(this.#thumbs.issuersCrl);
|
|
1962
2010
|
}
|
|
1963
2011
|
if (target === "trusted" || target === "all") {
|
|
1964
|
-
await processIndex(this.
|
|
2012
|
+
await processIndex(this.#thumbs.crl);
|
|
1965
2013
|
}
|
|
1966
2014
|
}
|
|
1967
2015
|
/**
|
|
@@ -1993,9 +2041,23 @@ var CertificateManager = class {
|
|
|
1993
2041
|
return "BadCertificateInvalid" /* BadCertificateInvalid */;
|
|
1994
2042
|
}
|
|
1995
2043
|
if (certificates.length > 1) {
|
|
2044
|
+
const issuerFolder = this.issuersCertFolder;
|
|
2045
|
+
const issuerThumbprints = /* @__PURE__ */ new Set();
|
|
2046
|
+
const files = await import_node_fs9.default.promises.readdir(issuerFolder);
|
|
2047
|
+
for (const file of files) {
|
|
2048
|
+
const ext = import_node_path6.default.extname(file).toLowerCase();
|
|
2049
|
+
if (ext === ".pem" || ext === ".der") {
|
|
2050
|
+
try {
|
|
2051
|
+
const issuerCert = (0, import_node_opcua_crypto5.readCertificate)(import_node_path6.default.join(issuerFolder, file));
|
|
2052
|
+
const fp = makeFingerprint(issuerCert);
|
|
2053
|
+
issuerThumbprints.add(fp);
|
|
2054
|
+
} catch (_err) {
|
|
2055
|
+
}
|
|
2056
|
+
}
|
|
2057
|
+
}
|
|
1996
2058
|
for (const issuerCert of certificates.slice(1)) {
|
|
1997
2059
|
const thumbprint = makeFingerprint(issuerCert);
|
|
1998
|
-
if (!
|
|
2060
|
+
if (!issuerThumbprints.has(thumbprint)) {
|
|
1999
2061
|
return "BadCertificateChainIncomplete" /* BadCertificateChainIncomplete */;
|
|
2000
2062
|
}
|
|
2001
2063
|
}
|
|
@@ -2016,8 +2078,8 @@ var CertificateManager = class {
|
|
|
2016
2078
|
* signed by this issuer.
|
|
2017
2079
|
*/
|
|
2018
2080
|
async isIssuerInUseByTrustedCertificate(issuerCertificate) {
|
|
2019
|
-
await this
|
|
2020
|
-
for (const entry of
|
|
2081
|
+
await this.#readCertificates();
|
|
2082
|
+
for (const entry of this.#thumbs.trusted.values()) {
|
|
2021
2083
|
if (!entry.certificate) continue;
|
|
2022
2084
|
try {
|
|
2023
2085
|
if ((0, import_node_opcua_crypto5.verifyCertificateSignature)(entry.certificate, issuerCertificate)) {
|
|
@@ -2052,7 +2114,7 @@ var CertificateManager = class {
|
|
|
2052
2114
|
debugLog("Certificate has no extension 3");
|
|
2053
2115
|
return null;
|
|
2054
2116
|
}
|
|
2055
|
-
const issuerCertificates =
|
|
2117
|
+
const issuerCertificates = [...this.#thumbs.issuers.certs.values()];
|
|
2056
2118
|
const selectedIssuerCertificates = findMatchingIssuerKey(issuerCertificates, wantedIssuerKey);
|
|
2057
2119
|
if (selectedIssuerCertificates.length > 0) {
|
|
2058
2120
|
if (selectedIssuerCertificates.length > 1) {
|
|
@@ -2060,7 +2122,7 @@ var CertificateManager = class {
|
|
|
2060
2122
|
}
|
|
2061
2123
|
return selectedIssuerCertificates[0].certificate || null;
|
|
2062
2124
|
}
|
|
2063
|
-
const trustedCertificates =
|
|
2125
|
+
const trustedCertificates = [...this.#thumbs.trusted.values()];
|
|
2064
2126
|
const selectedTrustedCertificates = findMatchingIssuerKey(trustedCertificates, wantedIssuerKey);
|
|
2065
2127
|
if (selectedTrustedCertificates.length > 1) {
|
|
2066
2128
|
warningLog(
|
|
@@ -2072,53 +2134,78 @@ var CertificateManager = class {
|
|
|
2072
2134
|
return selectedTrustedCertificates.length > 0 ? selectedTrustedCertificates[0].certificate : null;
|
|
2073
2135
|
}
|
|
2074
2136
|
/**
|
|
2137
|
+
*
|
|
2138
|
+
* check if the certificate explicitly appear in the trust list, the reject list or none.
|
|
2139
|
+
* In case of being in the reject and trusted list at the same time is consider: rejected.
|
|
2075
2140
|
* @internal
|
|
2076
2141
|
* @private
|
|
2077
2142
|
*/
|
|
2078
|
-
async
|
|
2079
|
-
|
|
2080
|
-
|
|
2081
|
-
|
|
2082
|
-
|
|
2083
|
-
if (Object.prototype.hasOwnProperty.call(this._thumbs.rejected, fingerprint2)) {
|
|
2143
|
+
async #checkRejectedOrTrusted(certificate) {
|
|
2144
|
+
const fingerprint = makeFingerprint(certificate);
|
|
2145
|
+
debugLog("#checkRejectedOrTrusted fingerprint ", short(fingerprint));
|
|
2146
|
+
await this.#readCertificates();
|
|
2147
|
+
if (this.#thumbs.rejected.has(fingerprint)) {
|
|
2084
2148
|
return "rejected";
|
|
2085
2149
|
}
|
|
2086
|
-
if (
|
|
2150
|
+
if (this.#thumbs.trusted.has(fingerprint)) {
|
|
2087
2151
|
return "trusted";
|
|
2088
2152
|
}
|
|
2089
2153
|
return "unknown";
|
|
2090
2154
|
}
|
|
2091
|
-
async
|
|
2092
|
-
|
|
2093
|
-
|
|
2094
|
-
|
|
2095
|
-
|
|
2096
|
-
|
|
2097
|
-
|
|
2098
|
-
|
|
2099
|
-
|
|
2100
|
-
|
|
2101
|
-
debugLog(" cannot find certificate ", fingerprint2.substring(0, 10), " in", this._thumbs, [status]);
|
|
2102
|
-
throw new Error("internal");
|
|
2155
|
+
async #moveCertificate(certificate, newStatus) {
|
|
2156
|
+
await this.withLock2(async () => {
|
|
2157
|
+
const fingerprint = makeFingerprint(certificate);
|
|
2158
|
+
let status = await this.#checkRejectedOrTrusted(certificate);
|
|
2159
|
+
if (status === "unknown") {
|
|
2160
|
+
const pem = (0, import_node_opcua_crypto5.toPem)(certificate, "CERTIFICATE");
|
|
2161
|
+
const filename = import_node_path6.default.join(this.rejectedFolder, `${buildIdealCertificateName(certificate)}.pem`);
|
|
2162
|
+
await import_node_fs9.default.promises.writeFile(filename, pem);
|
|
2163
|
+
this.#thumbs.rejected.set(fingerprint, { certificate, filename });
|
|
2164
|
+
status = "rejected";
|
|
2103
2165
|
}
|
|
2104
|
-
|
|
2105
|
-
|
|
2106
|
-
|
|
2107
|
-
|
|
2108
|
-
|
|
2109
|
-
|
|
2110
|
-
|
|
2111
|
-
|
|
2112
|
-
|
|
2113
|
-
|
|
2114
|
-
|
|
2115
|
-
|
|
2166
|
+
debugLog("#moveCertificate", fingerprint.substring(0, 10), "from", status, "to", newStatus);
|
|
2167
|
+
if (status !== "rejected" && status !== "trusted") {
|
|
2168
|
+
throw new Error(`#moveCertificate: unexpected status '${status}' for certificate ${fingerprint.substring(0, 10)}`);
|
|
2169
|
+
}
|
|
2170
|
+
if (status !== newStatus) {
|
|
2171
|
+
const indexSrc = status === "rejected" ? this.#thumbs.rejected : this.#thumbs.trusted;
|
|
2172
|
+
const srcEntry = indexSrc.get(fingerprint);
|
|
2173
|
+
if (!srcEntry) {
|
|
2174
|
+
debugLog(" cannot find certificate ", fingerprint.substring(0, 10), " in", status);
|
|
2175
|
+
throw new Error(`#moveCertificate: certificate ${fingerprint.substring(0, 10)} not found in ${status} index`);
|
|
2176
|
+
}
|
|
2177
|
+
const destFolder = newStatus === "trusted" ? this.trustedFolder : this.rejectedFolder;
|
|
2178
|
+
const certificateDest = import_node_path6.default.join(destFolder, import_node_path6.default.basename(srcEntry.filename));
|
|
2179
|
+
debugLog("#moveCertificate", fingerprint.substring(0, 10), "old name", srcEntry.filename);
|
|
2180
|
+
debugLog("#moveCertificate", fingerprint.substring(0, 10), "new name", certificateDest);
|
|
2181
|
+
await import_node_fs9.default.promises.rename(srcEntry.filename, certificateDest);
|
|
2182
|
+
indexSrc.delete(fingerprint);
|
|
2183
|
+
const indexDest = newStatus === "trusted" ? this.#thumbs.trusted : this.#thumbs.rejected;
|
|
2184
|
+
indexDest.set(fingerprint, { certificate, filename: certificateDest });
|
|
2185
|
+
}
|
|
2186
|
+
});
|
|
2116
2187
|
}
|
|
2117
|
-
|
|
2188
|
+
#findAssociatedCRLs(issuerCertificate) {
|
|
2118
2189
|
const issuerCertificateInfo = (0, import_node_opcua_crypto5.exploreCertificate)(issuerCertificate);
|
|
2119
2190
|
const key = issuerCertificateInfo.tbsCertificate.subjectFingerPrint;
|
|
2120
|
-
return this.
|
|
2191
|
+
return this.#thumbs.issuersCrl.get(key) ?? this.#thumbs.crl.get(key) ?? null;
|
|
2121
2192
|
}
|
|
2193
|
+
/**
|
|
2194
|
+
* Check whether a certificate has been revoked by its issuer's CRL.
|
|
2195
|
+
*
|
|
2196
|
+
* - Self-signed certificates are never considered revoked.
|
|
2197
|
+
* - If no `issuerCertificate` is provided, the method attempts
|
|
2198
|
+
* to find it via {@link findIssuerCertificate}.
|
|
2199
|
+
*
|
|
2200
|
+
* @param certificate - the DER-encoded certificate to check
|
|
2201
|
+
* @param issuerCertificate - optional issuer certificate; looked
|
|
2202
|
+
* up automatically when omitted
|
|
2203
|
+
* @returns `Good` if not revoked, `BadCertificateRevoked` if the
|
|
2204
|
+
* serial number appears in a CRL,
|
|
2205
|
+
* `BadCertificateRevocationUnknown` if no CRL is available,
|
|
2206
|
+
* or `BadCertificateChainIncomplete` if the issuer cannot be
|
|
2207
|
+
* found.
|
|
2208
|
+
*/
|
|
2122
2209
|
async isCertificateRevoked(certificate, issuerCertificate) {
|
|
2123
2210
|
if (isSelfSigned3(certificate)) {
|
|
2124
2211
|
return "Good" /* Good */;
|
|
@@ -2129,913 +2216,251 @@ var CertificateManager = class {
|
|
|
2129
2216
|
if (!issuerCertificate) {
|
|
2130
2217
|
return "BadCertificateChainIncomplete" /* BadCertificateChainIncomplete */;
|
|
2131
2218
|
}
|
|
2132
|
-
const crls = this
|
|
2219
|
+
const crls = this.#findAssociatedCRLs(issuerCertificate);
|
|
2133
2220
|
if (!crls) {
|
|
2134
2221
|
return "BadCertificateRevocationUnknown" /* BadCertificateRevocationUnknown */;
|
|
2135
2222
|
}
|
|
2136
2223
|
const certInfo = (0, import_node_opcua_crypto5.exploreCertificate)(certificate);
|
|
2137
2224
|
const serialNumber = certInfo.tbsCertificate.serialNumber || certInfo.tbsCertificate.extensions?.authorityKeyIdentifier?.serial || "";
|
|
2138
2225
|
const key = certInfo.tbsCertificate.extensions?.authorityKeyIdentifier?.authorityCertIssuerFingerPrint || "<unknown>";
|
|
2139
|
-
const crl2 = this.
|
|
2226
|
+
const crl2 = this.#thumbs.crl.get(key) ?? null;
|
|
2140
2227
|
if (crls.serialNumbers[serialNumber] || crl2?.serialNumbers[serialNumber]) {
|
|
2141
2228
|
return "BadCertificateRevoked" /* BadCertificateRevoked */;
|
|
2142
2229
|
}
|
|
2143
2230
|
return "Good" /* Good */;
|
|
2144
2231
|
}
|
|
2145
|
-
|
|
2146
|
-
|
|
2147
|
-
queue = [];
|
|
2148
|
-
|
|
2149
|
-
this
|
|
2150
|
-
this
|
|
2151
|
-
if (this
|
|
2152
|
-
this
|
|
2232
|
+
#pendingCrlToProcess = 0;
|
|
2233
|
+
#onCrlProcess;
|
|
2234
|
+
#queue = [];
|
|
2235
|
+
#onCrlFileAdded(index, filename) {
|
|
2236
|
+
this.#queue.push({ index, filename });
|
|
2237
|
+
this.#pendingCrlToProcess += 1;
|
|
2238
|
+
if (this.#pendingCrlToProcess === 1) {
|
|
2239
|
+
this.#processNextCrl();
|
|
2153
2240
|
}
|
|
2154
2241
|
}
|
|
2155
|
-
async
|
|
2242
|
+
async #processNextCrl() {
|
|
2156
2243
|
try {
|
|
2157
|
-
const nextCRL = this
|
|
2244
|
+
const nextCRL = this.#queue.shift();
|
|
2158
2245
|
if (!nextCRL) return;
|
|
2159
2246
|
const { index, filename } = nextCRL;
|
|
2160
2247
|
const crl = await (0, import_node_opcua_crypto5.readCertificateRevocationList)(filename);
|
|
2161
2248
|
const crlInfo = (0, import_node_opcua_crypto5.exploreCertificateRevocationList)(crl);
|
|
2162
2249
|
debugLog(import_chalk6.default.cyan("add CRL in folder "), filename);
|
|
2163
|
-
const
|
|
2164
|
-
|
|
2165
|
-
crls: [],
|
|
2166
|
-
|
|
2167
|
-
};
|
|
2168
|
-
|
|
2169
|
-
const serialNumbers = index[fingerprint2].serialNumbers;
|
|
2250
|
+
const fingerprint = crlInfo.tbsCertList.issuerFingerprint;
|
|
2251
|
+
if (!index.has(fingerprint)) {
|
|
2252
|
+
index.set(fingerprint, { crls: [], serialNumbers: {} });
|
|
2253
|
+
}
|
|
2254
|
+
const data = index.get(fingerprint) || { crls: [], serialNumbers: {} };
|
|
2255
|
+
data.crls.push({ crlInfo, filename });
|
|
2170
2256
|
for (const revokedCertificate of crlInfo.tbsCertList.revokedCertificates) {
|
|
2171
2257
|
const serialNumber = revokedCertificate.userCertificate;
|
|
2172
|
-
if (!serialNumbers[serialNumber]) {
|
|
2173
|
-
serialNumbers[serialNumber] = revokedCertificate.revocationDate;
|
|
2258
|
+
if (!data.serialNumbers[serialNumber]) {
|
|
2259
|
+
data.serialNumbers[serialNumber] = revokedCertificate.revocationDate;
|
|
2174
2260
|
}
|
|
2175
2261
|
}
|
|
2176
|
-
debugLog(import_chalk6.default.cyan("CRL"),
|
|
2262
|
+
debugLog(import_chalk6.default.cyan("CRL"), fingerprint, "serial numbers = ", Object.keys(data.serialNumbers));
|
|
2177
2263
|
} catch (err) {
|
|
2178
2264
|
debugLog("CRL filename error =");
|
|
2179
2265
|
debugLog(err);
|
|
2180
2266
|
}
|
|
2181
|
-
this
|
|
2182
|
-
if (this
|
|
2183
|
-
if (this
|
|
2184
|
-
this
|
|
2185
|
-
this
|
|
2267
|
+
this.#pendingCrlToProcess -= 1;
|
|
2268
|
+
if (this.#pendingCrlToProcess === 0) {
|
|
2269
|
+
if (this.#onCrlProcess) {
|
|
2270
|
+
this.#onCrlProcess();
|
|
2271
|
+
this.#onCrlProcess = void 0;
|
|
2186
2272
|
}
|
|
2187
2273
|
} else {
|
|
2188
|
-
this
|
|
2274
|
+
this.#processNextCrl();
|
|
2189
2275
|
}
|
|
2190
2276
|
}
|
|
2191
|
-
async
|
|
2192
|
-
if (this
|
|
2277
|
+
async #readCertificates() {
|
|
2278
|
+
if (this.#readCertificatesCalled) {
|
|
2193
2279
|
return;
|
|
2194
2280
|
}
|
|
2195
|
-
this
|
|
2196
|
-
const
|
|
2197
|
-
|
|
2198
|
-
|
|
2199
|
-
|
|
2200
|
-
|
|
2201
|
-
stabilityThreshold: 2e3,
|
|
2202
|
-
pollInterval: 600
|
|
2203
|
-
}
|
|
2281
|
+
this.#readCertificatesCalled = true;
|
|
2282
|
+
const usePolling = process.env.OPCUA_PKI_USE_POLLING === "true";
|
|
2283
|
+
const chokidarOptions = {
|
|
2284
|
+
usePolling,
|
|
2285
|
+
...usePolling ? { interval: Math.min(10 * 60 * 1e3, Math.max(100, this.folderPoolingInterval)) } : {},
|
|
2286
|
+
persistent: false
|
|
2204
2287
|
};
|
|
2205
|
-
|
|
2206
|
-
|
|
2207
|
-
|
|
2208
|
-
|
|
2209
|
-
|
|
2210
|
-
|
|
2211
|
-
|
|
2212
|
-
w.on("add", (filename, stat) => {
|
|
2213
|
-
stat;
|
|
2214
|
-
this._on_crl_file_added(index, filename);
|
|
2215
|
-
});
|
|
2216
|
-
w.on("change", (path8, stat) => {
|
|
2217
|
-
debugLog("change in folder ", folder, path8, stat);
|
|
2218
|
-
});
|
|
2219
|
-
this._watchers.push(w);
|
|
2220
|
-
w.on("ready", () => {
|
|
2221
|
-
resolve();
|
|
2222
|
-
});
|
|
2288
|
+
const createUnreffedWatcher = (folder) => {
|
|
2289
|
+
const capturedHandles = [];
|
|
2290
|
+
const origWatch = import_node_fs9.default.watch;
|
|
2291
|
+
import_node_fs9.default.watch = ((...args) => {
|
|
2292
|
+
const handle = origWatch.apply(import_node_fs9.default, args);
|
|
2293
|
+
capturedHandles.push(handle);
|
|
2294
|
+
return handle;
|
|
2223
2295
|
});
|
|
2224
|
-
|
|
2225
|
-
|
|
2226
|
-
|
|
2227
|
-
|
|
2228
|
-
|
|
2229
|
-
debugLog(import_chalk6.default.cyan(`unlink in folder ${folder}`), filename);
|
|
2230
|
-
const h = this._filenameToHash[filename];
|
|
2231
|
-
if (h && index[h]) {
|
|
2232
|
-
delete index[h];
|
|
2296
|
+
const w = import_chokidar.default.watch(folder, chokidarOptions);
|
|
2297
|
+
const unreffAll = () => {
|
|
2298
|
+
import_node_fs9.default.watch = origWatch;
|
|
2299
|
+
for (const h of capturedHandles) {
|
|
2300
|
+
h.unref();
|
|
2233
2301
|
}
|
|
2234
|
-
}
|
|
2235
|
-
w
|
|
2236
|
-
|
|
2237
|
-
|
|
2238
|
-
|
|
2239
|
-
|
|
2240
|
-
|
|
2241
|
-
|
|
2242
|
-
|
|
2243
|
-
|
|
2244
|
-
|
|
2245
|
-
|
|
2246
|
-
|
|
2247
|
-
|
|
2248
|
-
|
|
2249
|
-
|
|
2250
|
-
|
|
2251
|
-
|
|
2252
|
-
);
|
|
2253
|
-
|
|
2254
|
-
|
|
2255
|
-
|
|
2302
|
+
};
|
|
2303
|
+
return { w, capturedHandles, unreffAll };
|
|
2304
|
+
};
|
|
2305
|
+
const promises = [
|
|
2306
|
+
this.#walkAllFiles(this.trustedFolder, this.#thumbs.trusted, createUnreffedWatcher),
|
|
2307
|
+
this.#walkAllFiles(this.issuersCertFolder, this.#thumbs.issuers.certs, createUnreffedWatcher),
|
|
2308
|
+
this.#walkAllFiles(this.rejectedFolder, this.#thumbs.rejected, createUnreffedWatcher),
|
|
2309
|
+
this.#walkCRLFiles(this.crlFolder, this.#thumbs.crl, createUnreffedWatcher),
|
|
2310
|
+
this.#walkCRLFiles(this.issuersCrlFolder, this.#thumbs.issuersCrl, createUnreffedWatcher)
|
|
2311
|
+
];
|
|
2312
|
+
await Promise.all(promises);
|
|
2313
|
+
await this.#waitAndCheckCRLProcessingStatus();
|
|
2314
|
+
}
|
|
2315
|
+
async #walkCRLFiles(folder, index, createUnreffedWatcher) {
|
|
2316
|
+
await new Promise((resolve, _reject) => {
|
|
2317
|
+
const { w, unreffAll } = createUnreffedWatcher(folder);
|
|
2318
|
+
w.on("unlink", (filename) => {
|
|
2319
|
+
for (const [key, data] of index.entries()) {
|
|
2320
|
+
data.crls = data.crls.filter((c) => c.filename !== filename);
|
|
2321
|
+
if (data.crls.length === 0) {
|
|
2322
|
+
index.delete(key);
|
|
2323
|
+
}
|
|
2256
2324
|
}
|
|
2257
2325
|
});
|
|
2258
|
-
w.on("
|
|
2259
|
-
|
|
2260
|
-
debugLog("change in folder ", folder, path8);
|
|
2326
|
+
w.on("add", (filename) => {
|
|
2327
|
+
this.#onCrlFileAdded(index, filename);
|
|
2261
2328
|
});
|
|
2262
|
-
|
|
2263
|
-
|
|
2264
|
-
|
|
2265
|
-
|
|
2266
|
-
|
|
2267
|
-
|
|
2268
|
-
|
|
2329
|
+
w.on("change", (changedPath) => {
|
|
2330
|
+
debugLog("change in folder ", folder, changedPath);
|
|
2331
|
+
});
|
|
2332
|
+
this.#watchers.push(w);
|
|
2333
|
+
w.on("ready", () => {
|
|
2334
|
+
unreffAll();
|
|
2335
|
+
resolve();
|
|
2269
2336
|
});
|
|
2270
|
-
}
|
|
2271
|
-
const promises = [
|
|
2272
|
-
_walkAllFiles.bind(this, this.trustedFolder, this._thumbs.trusted)(),
|
|
2273
|
-
_walkAllFiles.bind(this, this.issuersCertFolder, this._thumbs.issuers.certs)(),
|
|
2274
|
-
_walkAllFiles.bind(this, this.rejectedFolder, this._thumbs.rejected)(),
|
|
2275
|
-
_walkCRLFiles.bind(this, this.crlFolder, this._thumbs.crl)(),
|
|
2276
|
-
_walkCRLFiles.bind(this, this.issuersCrlFolder, this._thumbs.issuersCrl)()
|
|
2277
|
-
];
|
|
2278
|
-
await Promise.all(promises);
|
|
2279
|
-
await this.waitAndCheckCRLProcessingStatus();
|
|
2280
|
-
}
|
|
2281
|
-
// make sure that all crls have been processed.
|
|
2282
|
-
async waitAndCheckCRLProcessingStatus() {
|
|
2283
|
-
return new Promise((resolve, reject) => {
|
|
2284
|
-
if (this._pending_crl_to_process === 0) {
|
|
2285
|
-
setImmediate(resolve);
|
|
2286
|
-
return;
|
|
2287
|
-
}
|
|
2288
|
-
if (this._on_crl_process) {
|
|
2289
|
-
return reject(new Error("Internal Error"));
|
|
2290
|
-
}
|
|
2291
|
-
this._on_crl_process = resolve;
|
|
2292
|
-
});
|
|
2293
|
-
}
|
|
2294
|
-
};
|
|
2295
|
-
|
|
2296
|
-
// packages/node-opcua-pki/lib/ca/crypto_create_CA.ts
|
|
2297
|
-
var import_command_line_args = __toESM(require("command-line-args"));
|
|
2298
|
-
var import_command_line_usage = __toESM(require("command-line-usage"));
|
|
2299
|
-
var epilog = "Copyright (c) sterfive - node-opcua - 2017-2026";
|
|
2300
|
-
function get_offset_date(date, nbDays) {
|
|
2301
|
-
const d = new Date(date.getTime());
|
|
2302
|
-
d.setDate(d.getDate() + nbDays);
|
|
2303
|
-
return d;
|
|
2304
|
-
}
|
|
2305
|
-
var today = /* @__PURE__ */ new Date();
|
|
2306
|
-
var yesterday = get_offset_date(today, -1);
|
|
2307
|
-
var two_years_ago = get_offset_date(today, -2 * 365);
|
|
2308
|
-
var next_year = get_offset_date(today, 365);
|
|
2309
|
-
var gLocalConfig = {};
|
|
2310
|
-
var g_certificateAuthority;
|
|
2311
|
-
async function construct_CertificateAuthority2(subject) {
|
|
2312
|
-
(0, import_node_assert11.default)(typeof gLocalConfig.CAFolder === "string", "expecting a CAFolder in config");
|
|
2313
|
-
(0, import_node_assert11.default)(typeof gLocalConfig.keySize === "number", "expecting a keySize in config");
|
|
2314
|
-
if (!g_certificateAuthority) {
|
|
2315
|
-
g_certificateAuthority = new CertificateAuthority({
|
|
2316
|
-
keySize: gLocalConfig.keySize,
|
|
2317
|
-
location: gLocalConfig.CAFolder,
|
|
2318
|
-
subject
|
|
2319
|
-
});
|
|
2320
|
-
await g_certificateAuthority.initialize();
|
|
2321
|
-
}
|
|
2322
|
-
}
|
|
2323
|
-
var certificateManager;
|
|
2324
|
-
async function construct_CertificateManager() {
|
|
2325
|
-
(0, import_node_assert11.default)(typeof gLocalConfig.PKIFolder === "string", "expecting a PKIFolder in config");
|
|
2326
|
-
if (!certificateManager) {
|
|
2327
|
-
certificateManager = new CertificateManager({
|
|
2328
|
-
keySize: gLocalConfig.keySize,
|
|
2329
|
-
location: gLocalConfig.PKIFolder
|
|
2330
2337
|
});
|
|
2331
|
-
await certificateManager.initialize();
|
|
2332
2338
|
}
|
|
2333
|
-
|
|
2334
|
-
|
|
2335
|
-
|
|
2336
|
-
|
|
2337
|
-
|
|
2338
|
-
|
|
2339
|
-
|
|
2340
|
-
const rootFolder = find_module_root_folder();
|
|
2341
|
-
const configName = "pki_config.example.js";
|
|
2342
|
-
let default_config_template2 = import_node_path7.default.join(rootFolder, "bin", configName);
|
|
2343
|
-
if (!import_node_fs10.default.existsSync(default_config_template2)) {
|
|
2344
|
-
default_config_template2 = import_node_path7.default.join(__dirname, "..", configName);
|
|
2345
|
-
if (!import_node_fs10.default.existsSync(default_config_template2)) {
|
|
2346
|
-
default_config_template2 = import_node_path7.default.join(__dirname, `../bin/${configName}`);
|
|
2339
|
+
async #walkAllFiles(folder, index, createUnreffedWatcher) {
|
|
2340
|
+
const { w, unreffAll } = createUnreffedWatcher(folder);
|
|
2341
|
+
w.on("unlink", (filename) => {
|
|
2342
|
+
debugLog(import_chalk6.default.cyan(`unlink in folder ${folder}`), filename);
|
|
2343
|
+
const h = this.#filenameToHash.get(filename);
|
|
2344
|
+
if (h && index.has(h)) {
|
|
2345
|
+
index.delete(h);
|
|
2347
2346
|
}
|
|
2348
|
-
}
|
|
2349
|
-
return default_config_template2;
|
|
2350
|
-
}
|
|
2351
|
-
const default_config_template = find_default_config_template();
|
|
2352
|
-
(0, import_node_assert11.default)(import_node_fs10.default.existsSync(default_config_template));
|
|
2353
|
-
const default_config_template_content = import_node_fs10.default.readFileSync(default_config_template, "utf8");
|
|
2354
|
-
return default_config_template_content;
|
|
2355
|
-
}
|
|
2356
|
-
function find_module_root_folder() {
|
|
2357
|
-
let rootFolder = import_node_path7.default.join(__dirname);
|
|
2358
|
-
for (let i = 0; i < 4; i++) {
|
|
2359
|
-
if (import_node_fs10.default.existsSync(import_node_path7.default.join(rootFolder, "package.json"))) {
|
|
2360
|
-
return rootFolder;
|
|
2361
|
-
}
|
|
2362
|
-
rootFolder = import_node_path7.default.join(rootFolder, "..");
|
|
2363
|
-
}
|
|
2364
|
-
(0, import_node_assert11.default)(import_node_fs10.default.existsSync(import_node_path7.default.join(rootFolder, "package.json")), "root folder must have a package.json file");
|
|
2365
|
-
return rootFolder;
|
|
2366
|
-
}
|
|
2367
|
-
async function readConfiguration(argv) {
|
|
2368
|
-
if (argv.silent) {
|
|
2369
|
-
g_config.silent = true;
|
|
2370
|
-
} else {
|
|
2371
|
-
g_config.silent = false;
|
|
2372
|
-
}
|
|
2373
|
-
const fqdn2 = await extractFullyQualifiedDomainName();
|
|
2374
|
-
const hostname = import_node_os4.default.hostname();
|
|
2375
|
-
let certificateDir;
|
|
2376
|
-
function performSubstitution(str) {
|
|
2377
|
-
str = str.replace("{CWD}", process.cwd());
|
|
2378
|
-
if (certificateDir) {
|
|
2379
|
-
str = str.replace("{root}", certificateDir);
|
|
2380
|
-
}
|
|
2381
|
-
if (gLocalConfig?.PKIFolder) {
|
|
2382
|
-
str = str.replace("{PKIFolder}", gLocalConfig.PKIFolder);
|
|
2383
|
-
}
|
|
2384
|
-
str = str.replace("{hostname}", hostname);
|
|
2385
|
-
str = str.replace("%FQDN%", fqdn2);
|
|
2386
|
-
return str;
|
|
2387
|
-
}
|
|
2388
|
-
function prepare(file) {
|
|
2389
|
-
const tmp = import_node_path7.default.resolve(performSubstitution(file));
|
|
2390
|
-
return makePath(tmp);
|
|
2391
|
-
}
|
|
2392
|
-
certificateDir = argv.root;
|
|
2393
|
-
(0, import_node_assert11.default)(typeof certificateDir === "string");
|
|
2394
|
-
certificateDir = prepare(certificateDir);
|
|
2395
|
-
mkdirRecursiveSync(certificateDir);
|
|
2396
|
-
(0, import_node_assert11.default)(import_node_fs10.default.existsSync(certificateDir));
|
|
2397
|
-
const default_config = import_node_path7.default.join(certificateDir, "config.js");
|
|
2398
|
-
if (!import_node_fs10.default.existsSync(default_config)) {
|
|
2399
|
-
debugLog(import_chalk7.default.yellow(" Creating default g_config file "), import_chalk7.default.cyan(default_config));
|
|
2400
|
-
const default_config_template_content = default_template_content();
|
|
2401
|
-
import_node_fs10.default.writeFileSync(default_config, default_config_template_content);
|
|
2402
|
-
} else {
|
|
2403
|
-
debugLog(import_chalk7.default.yellow(" using g_config file "), import_chalk7.default.cyan(default_config));
|
|
2404
|
-
}
|
|
2405
|
-
if (!import_node_fs10.default.existsSync(default_config)) {
|
|
2406
|
-
debugLog(import_chalk7.default.redBright(" cannot find config file ", default_config));
|
|
2407
|
-
}
|
|
2408
|
-
const defaultRandomFile = import_node_path7.default.join(import_node_path7.default.dirname(default_config), "random.rnd");
|
|
2409
|
-
setEnv("RANDFILE", defaultRandomFile);
|
|
2410
|
-
const _require = (0, import_node_module.createRequire)(__filename);
|
|
2411
|
-
gLocalConfig = _require(default_config);
|
|
2412
|
-
gLocalConfig.subject = new import_node_opcua_crypto6.Subject(gLocalConfig.subject || "");
|
|
2413
|
-
if (argv.subject) {
|
|
2414
|
-
gLocalConfig.subject = new import_node_opcua_crypto6.Subject(argv.subject);
|
|
2415
|
-
}
|
|
2416
|
-
if (!gLocalConfig.subject.commonName) {
|
|
2417
|
-
throw new Error("subject must have a Common Name");
|
|
2418
|
-
}
|
|
2419
|
-
gLocalConfig.certificateDir = certificateDir;
|
|
2420
|
-
let CAFolder = argv.CAFolder || import_node_path7.default.join(certificateDir, "CA");
|
|
2421
|
-
CAFolder = prepare(CAFolder);
|
|
2422
|
-
gLocalConfig.CAFolder = CAFolder;
|
|
2423
|
-
gLocalConfig.PKIFolder = import_node_path7.default.join(gLocalConfig.certificateDir, "PKI");
|
|
2424
|
-
if (argv.PKIFolder) {
|
|
2425
|
-
gLocalConfig.PKIFolder = prepare(argv.PKIFolder);
|
|
2426
|
-
}
|
|
2427
|
-
gLocalConfig.PKIFolder = prepare(gLocalConfig.PKIFolder);
|
|
2428
|
-
if (argv.privateKey) {
|
|
2429
|
-
gLocalConfig.privateKey = prepare(argv.privateKey);
|
|
2430
|
-
}
|
|
2431
|
-
if (argv.applicationUri) {
|
|
2432
|
-
gLocalConfig.applicationUri = performSubstitution(argv.applicationUri);
|
|
2433
|
-
}
|
|
2434
|
-
if (argv.output) {
|
|
2435
|
-
gLocalConfig.outputFile = argv.output;
|
|
2436
|
-
}
|
|
2437
|
-
gLocalConfig.altNames = [];
|
|
2438
|
-
if (argv.altNames) {
|
|
2439
|
-
gLocalConfig.altNames = argv.altNames.split(";");
|
|
2440
|
-
}
|
|
2441
|
-
gLocalConfig.dns = [getFullyQualifiedDomainName()];
|
|
2442
|
-
if (argv.dns) {
|
|
2443
|
-
gLocalConfig.dns = argv.dns.split(",").map(performSubstitution);
|
|
2444
|
-
}
|
|
2445
|
-
gLocalConfig.ip = [];
|
|
2446
|
-
if (argv.ip) {
|
|
2447
|
-
gLocalConfig.ip = argv.ip.split(",");
|
|
2448
|
-
}
|
|
2449
|
-
if (argv.keySize) {
|
|
2450
|
-
const v = argv.keySize;
|
|
2451
|
-
if (v !== 1024 && v !== 2048 && v !== 3072 && v !== 4096) {
|
|
2452
|
-
throw new Error(`invalid keysize specified ${v} should be 1024,2048,3072 or 4096`);
|
|
2453
|
-
}
|
|
2454
|
-
gLocalConfig.keySize = argv.keySize;
|
|
2455
|
-
}
|
|
2456
|
-
if (argv.validity) {
|
|
2457
|
-
gLocalConfig.validity = argv.validity;
|
|
2458
|
-
}
|
|
2459
|
-
}
|
|
2460
|
-
async function createDefaultCertificate(base_name, prefix, key_length, applicationUri, dev) {
|
|
2461
|
-
(0, import_node_assert11.default)(key_length === 1024 || key_length === 2048 || key_length === 3072 || key_length === 4096);
|
|
2462
|
-
const private_key_file = makePath(base_name, `${prefix}key_${key_length}.pem`);
|
|
2463
|
-
const public_key_file = makePath(base_name, `${prefix}public_key_${key_length}.pub`);
|
|
2464
|
-
const certificate_file = makePath(base_name, `${prefix}cert_${key_length}.pem`);
|
|
2465
|
-
const certificate_file_outofdate = makePath(base_name, `${prefix}cert_${key_length}_outofdate.pem`);
|
|
2466
|
-
const certificate_file_not_active_yet = makePath(base_name, `${prefix}cert_${key_length}_not_active_yet.pem`);
|
|
2467
|
-
const certificate_revoked = makePath(base_name, `${prefix}cert_${key_length}_revoked.pem`);
|
|
2468
|
-
const self_signed_certificate_file = makePath(base_name, `${prefix}selfsigned_cert_${key_length}.pem`);
|
|
2469
|
-
const fqdn2 = getFullyQualifiedDomainName();
|
|
2470
|
-
const hostname = import_node_os4.default.hostname();
|
|
2471
|
-
const dns2 = [
|
|
2472
|
-
// for conformance reason, localhost shall not be present in the DNS field of COP
|
|
2473
|
-
// ***FORBIDEN** "localhost",
|
|
2474
|
-
getFullyQualifiedDomainName()
|
|
2475
|
-
];
|
|
2476
|
-
if (hostname !== fqdn2) {
|
|
2477
|
-
dns2.push(hostname);
|
|
2478
|
-
}
|
|
2479
|
-
const ip = [];
|
|
2480
|
-
async function createCertificateIfNotExist(certificate, private_key, applicationUri2, startDate, validity) {
|
|
2481
|
-
if (import_node_fs10.default.existsSync(certificate)) {
|
|
2482
|
-
warningLog(import_chalk7.default.yellow(" certificate"), import_chalk7.default.cyan(certificate), import_chalk7.default.yellow(" already exists => skipping"));
|
|
2483
|
-
return "";
|
|
2484
|
-
} else {
|
|
2485
|
-
return await createCertificate(certificate, private_key, applicationUri2, startDate, validity);
|
|
2486
|
-
}
|
|
2487
|
-
}
|
|
2488
|
-
async function createCertificate(certificate, privateKey, applicationUri2, startDate, validity) {
|
|
2489
|
-
const certificateSigningRequestFile = `${certificate}.csr`;
|
|
2490
|
-
const configFile = makePath(base_name, "../certificates/PKI/own/openssl.cnf");
|
|
2491
|
-
const dns3 = [import_node_os4.default.hostname()];
|
|
2492
|
-
const ip2 = ["127.0.0.1"];
|
|
2493
|
-
const params = {
|
|
2494
|
-
applicationUri: applicationUri2,
|
|
2495
|
-
privateKey,
|
|
2496
|
-
rootDir: ".",
|
|
2497
|
-
configFile,
|
|
2498
|
-
dns: dns3,
|
|
2499
|
-
ip: ip2,
|
|
2500
|
-
purpose: import_node_opcua_crypto6.CertificatePurpose.ForApplication
|
|
2501
|
-
};
|
|
2502
|
-
await createCertificateSigningRequestWithOpenSSL(certificateSigningRequestFile, params);
|
|
2503
|
-
return await g_certificateAuthority.signCertificateRequest(certificate, certificateSigningRequestFile, {
|
|
2504
|
-
applicationUri: applicationUri2,
|
|
2505
|
-
dns: dns3,
|
|
2506
|
-
ip: ip2,
|
|
2507
|
-
startDate,
|
|
2508
|
-
validity
|
|
2509
2347
|
});
|
|
2510
|
-
|
|
2511
|
-
|
|
2512
|
-
|
|
2513
|
-
|
|
2514
|
-
|
|
2515
|
-
|
|
2516
|
-
|
|
2517
|
-
|
|
2348
|
+
w.on("add", (filename) => {
|
|
2349
|
+
debugLog(import_chalk6.default.cyan(`add in folder ${folder}`), filename);
|
|
2350
|
+
try {
|
|
2351
|
+
const certificate = (0, import_node_opcua_crypto5.readCertificate)(filename);
|
|
2352
|
+
const info = (0, import_node_opcua_crypto5.exploreCertificate)(certificate);
|
|
2353
|
+
const fingerprint = makeFingerprint(certificate);
|
|
2354
|
+
index.set(fingerprint, { certificate, filename, info });
|
|
2355
|
+
this.#filenameToHash.set(filename, fingerprint);
|
|
2356
|
+
debugLog(
|
|
2357
|
+
import_chalk6.default.magenta("CERT"),
|
|
2358
|
+
info.tbsCertificate.subjectFingerPrint,
|
|
2359
|
+
info.tbsCertificate.serialNumber,
|
|
2360
|
+
info.tbsCertificate.extensions?.authorityKeyIdentifier?.authorityCertIssuerFingerPrint
|
|
2361
|
+
);
|
|
2362
|
+
} catch (err) {
|
|
2363
|
+
debugLog(`Walk files in folder ${folder} with file ${filename}`);
|
|
2364
|
+
debugLog(err);
|
|
2365
|
+
}
|
|
2518
2366
|
});
|
|
2519
|
-
|
|
2520
|
-
|
|
2521
|
-
|
|
2522
|
-
|
|
2523
|
-
|
|
2524
|
-
|
|
2525
|
-
|
|
2526
|
-
|
|
2527
|
-
} else {
|
|
2528
|
-
await (0, import_node_opcua_crypto6.generatePrivateKeyFile)(privateKey, keyLength);
|
|
2529
|
-
}
|
|
2530
|
-
}
|
|
2531
|
-
displaySubtitle(` create private key :${private_key_file}`);
|
|
2532
|
-
await createPrivateKeyIfNotExist(private_key_file, key_length);
|
|
2533
|
-
displaySubtitle(` extract public key ${public_key_file} from private key `);
|
|
2534
|
-
await getPublicKeyFromPrivateKey(private_key_file, public_key_file);
|
|
2535
|
-
displaySubtitle(` create Certificate ${certificate_file}`);
|
|
2536
|
-
await createCertificateIfNotExist(certificate_file, private_key_file, applicationUri, yesterday, 365);
|
|
2537
|
-
displaySubtitle(` create self signed Certificate ${self_signed_certificate_file}`);
|
|
2538
|
-
if (import_node_fs10.default.existsSync(self_signed_certificate_file)) {
|
|
2539
|
-
return;
|
|
2540
|
-
}
|
|
2541
|
-
await createSelfSignedCertificate2(self_signed_certificate_file, private_key_file, applicationUri, yesterday, 365);
|
|
2542
|
-
if (dev) {
|
|
2543
|
-
await createCertificateIfNotExist(certificate_file_outofdate, private_key_file, applicationUri, two_years_ago, 365);
|
|
2544
|
-
await createCertificateIfNotExist(certificate_file_not_active_yet, private_key_file, applicationUri, next_year, 365);
|
|
2545
|
-
if (!import_node_fs10.default.existsSync(certificate_revoked)) {
|
|
2546
|
-
const certificate = await createCertificateIfNotExist(
|
|
2547
|
-
certificate_revoked,
|
|
2548
|
-
private_key_file,
|
|
2549
|
-
`${applicationUri}Revoked`,
|
|
2550
|
-
// make sure we used a uniq URI here
|
|
2551
|
-
yesterday,
|
|
2552
|
-
365
|
|
2553
|
-
);
|
|
2554
|
-
warningLog(" certificate to revoke => ", certificate);
|
|
2555
|
-
revoke_certificate(certificate_revoked);
|
|
2556
|
-
}
|
|
2557
|
-
}
|
|
2558
|
-
}
|
|
2559
|
-
async function wrap(func) {
|
|
2560
|
-
try {
|
|
2561
|
-
await func();
|
|
2562
|
-
} catch (err) {
|
|
2563
|
-
console.log(err.message);
|
|
2564
|
-
}
|
|
2565
|
-
}
|
|
2566
|
-
async function create_default_certificates(dev) {
|
|
2567
|
-
(0, import_node_assert11.default)(gLocalConfig);
|
|
2568
|
-
const base_name = gLocalConfig.certificateDir || "";
|
|
2569
|
-
(0, import_node_assert11.default)(import_node_fs10.default.existsSync(base_name));
|
|
2570
|
-
let clientURN;
|
|
2571
|
-
let serverURN;
|
|
2572
|
-
let discoveryServerURN;
|
|
2573
|
-
wrap(async () => {
|
|
2574
|
-
await extractFullyQualifiedDomainName();
|
|
2575
|
-
const hostname = import_node_os4.default.hostname();
|
|
2576
|
-
const fqdn2 = getFullyQualifiedDomainName();
|
|
2577
|
-
warningLog(import_chalk7.default.yellow(" hostname = "), import_chalk7.default.cyan(hostname));
|
|
2578
|
-
warningLog(import_chalk7.default.yellow(" fqdn = "), import_chalk7.default.cyan(fqdn2));
|
|
2579
|
-
clientURN = makeApplicationUrn(hostname, "NodeOPCUA-Client");
|
|
2580
|
-
serverURN = makeApplicationUrn(hostname, "NodeOPCUA-Server");
|
|
2581
|
-
discoveryServerURN = makeApplicationUrn(hostname, "NodeOPCUA-DiscoveryServer");
|
|
2582
|
-
displayTitle("Create Application Certificate for Server & its private key");
|
|
2583
|
-
await createDefaultCertificate(base_name, "client_", 1024, clientURN, dev);
|
|
2584
|
-
await createDefaultCertificate(base_name, "client_", 2048, clientURN, dev);
|
|
2585
|
-
await createDefaultCertificate(base_name, "client_", 3072, clientURN, dev);
|
|
2586
|
-
await createDefaultCertificate(base_name, "client_", 4096, clientURN, dev);
|
|
2587
|
-
displayTitle("Create Application Certificate for Client & its private key");
|
|
2588
|
-
await createDefaultCertificate(base_name, "server_", 1024, serverURN, dev);
|
|
2589
|
-
await createDefaultCertificate(base_name, "server_", 2048, serverURN, dev);
|
|
2590
|
-
await createDefaultCertificate(base_name, "server_", 3072, serverURN, dev);
|
|
2591
|
-
await createDefaultCertificate(base_name, "server_", 4096, serverURN, dev);
|
|
2592
|
-
displayTitle("Create Application Certificate for DiscoveryServer & its private key");
|
|
2593
|
-
await createDefaultCertificate(base_name, "discoveryServer_", 1024, discoveryServerURN, dev);
|
|
2594
|
-
await createDefaultCertificate(base_name, "discoveryServer_", 2048, discoveryServerURN, dev);
|
|
2595
|
-
await createDefaultCertificate(base_name, "discoveryServer_", 3072, discoveryServerURN, dev);
|
|
2596
|
-
await createDefaultCertificate(base_name, "discoveryServer_", 4096, discoveryServerURN, dev);
|
|
2597
|
-
});
|
|
2598
|
-
}
|
|
2599
|
-
async function createDefaultCertificates(dev) {
|
|
2600
|
-
await construct_CertificateAuthority2("");
|
|
2601
|
-
await construct_CertificateManager();
|
|
2602
|
-
await create_default_certificates(dev);
|
|
2603
|
-
}
|
|
2604
|
-
var commonOptions = [
|
|
2605
|
-
{
|
|
2606
|
-
name: "root",
|
|
2607
|
-
alias: "r",
|
|
2608
|
-
type: String,
|
|
2609
|
-
defaultValue: "{CWD}/certificates",
|
|
2610
|
-
description: "the location of the Certificate folder"
|
|
2611
|
-
},
|
|
2612
|
-
{
|
|
2613
|
-
name: "CAFolder",
|
|
2614
|
-
alias: "c",
|
|
2615
|
-
type: String,
|
|
2616
|
-
defaultValue: "{root}/CA",
|
|
2617
|
-
description: "the location of the Certificate Authority folder"
|
|
2618
|
-
},
|
|
2619
|
-
{ name: "PKIFolder", type: String, defaultValue: "{root}/PKI", description: "the location of the Public Key Infrastructure" },
|
|
2620
|
-
{ name: "silent", type: Boolean, defaultValue: false, description: "minimize output" },
|
|
2621
|
-
{
|
|
2622
|
-
name: "privateKey",
|
|
2623
|
-
alias: "p",
|
|
2624
|
-
type: String,
|
|
2625
|
-
defaultValue: "{PKIFolder}/own/private_key.pem",
|
|
2626
|
-
description: "the private key to use to generate certificate"
|
|
2627
|
-
},
|
|
2628
|
-
{
|
|
2629
|
-
name: "keySize",
|
|
2630
|
-
alias: "k",
|
|
2631
|
-
type: Number,
|
|
2632
|
-
defaultValue: 2048,
|
|
2633
|
-
description: "the private key size in bits (1024|2048|3072|4096)"
|
|
2634
|
-
},
|
|
2635
|
-
{ name: "help", alias: "h", type: Boolean, description: "display this help" }
|
|
2636
|
-
];
|
|
2637
|
-
function getOptions(names) {
|
|
2638
|
-
return commonOptions.filter((o) => names.includes(o.name) || o.name === "help" || o.name === "silent");
|
|
2639
|
-
}
|
|
2640
|
-
function showHelp(command, description, options, usage) {
|
|
2641
|
-
const sections = [
|
|
2642
|
-
{
|
|
2643
|
-
header: `Command: ${command}`,
|
|
2644
|
-
content: description
|
|
2645
|
-
},
|
|
2646
|
-
{
|
|
2647
|
-
header: "Usage",
|
|
2648
|
-
content: usage || `$0 ${command} [options]`
|
|
2649
|
-
},
|
|
2650
|
-
{
|
|
2651
|
-
header: "Options",
|
|
2652
|
-
optionList: options
|
|
2653
|
-
}
|
|
2654
|
-
];
|
|
2655
|
-
console.log((0, import_command_line_usage.default)(sections));
|
|
2656
|
-
}
|
|
2657
|
-
async function main(argumentsList) {
|
|
2658
|
-
const mainDefinitions = [{ name: "command", defaultOption: true }];
|
|
2659
|
-
let mainOptions;
|
|
2660
|
-
try {
|
|
2661
|
-
mainOptions = (0, import_command_line_args.default)(mainDefinitions, { argv: argumentsList, stopAtFirstUnknown: true });
|
|
2662
|
-
} catch (err) {
|
|
2663
|
-
console.log(err.message);
|
|
2664
|
-
return;
|
|
2665
|
-
}
|
|
2666
|
-
const argv = mainOptions._unknown || [];
|
|
2667
|
-
const command = mainOptions.command;
|
|
2668
|
-
if (!command || command === "help") {
|
|
2669
|
-
console.log(
|
|
2670
|
-
(0, import_command_line_usage.default)([
|
|
2671
|
-
{
|
|
2672
|
-
header: "node-opcua-pki",
|
|
2673
|
-
content: `PKI management for node-opcua
|
|
2674
|
-
|
|
2675
|
-
${epilog}`
|
|
2676
|
-
},
|
|
2677
|
-
{
|
|
2678
|
-
header: "Commands",
|
|
2679
|
-
content: [
|
|
2680
|
-
{ name: "demo", summary: "create default certificate for node-opcua demos" },
|
|
2681
|
-
{ name: "createCA", summary: "create a Certificate Authority" },
|
|
2682
|
-
{ name: "createPKI", summary: "create a Public Key Infrastructure" },
|
|
2683
|
-
{ name: "certificate", summary: "create a new certificate" },
|
|
2684
|
-
{ name: "revoke <certificateFile>", summary: "revoke a existing certificate" },
|
|
2685
|
-
{ name: "csr", summary: "create a certificate signing request" },
|
|
2686
|
-
{ name: "sign", summary: "validate a certificate signing request and generate a certificate" },
|
|
2687
|
-
{ name: "dump <certificateFile>", summary: "display a certificate" },
|
|
2688
|
-
{ name: "toder <pemCertificate>", summary: "convert a certificate to a DER format with finger print" },
|
|
2689
|
-
{ name: "fingerprint <certificateFile>", summary: "print the certificate fingerprint" },
|
|
2690
|
-
{ name: "version", summary: "display the version number" }
|
|
2691
|
-
]
|
|
2692
|
-
}
|
|
2693
|
-
])
|
|
2694
|
-
);
|
|
2695
|
-
return;
|
|
2696
|
-
}
|
|
2697
|
-
if (command === "version") {
|
|
2698
|
-
const rootFolder = find_module_root_folder();
|
|
2699
|
-
const pkg = JSON.parse(import_node_fs10.default.readFileSync(import_node_path7.default.join(rootFolder, "package.json"), "utf-8"));
|
|
2700
|
-
console.log(pkg.version);
|
|
2701
|
-
return;
|
|
2702
|
-
}
|
|
2703
|
-
if (command === "demo") {
|
|
2704
|
-
const optionsDef = [
|
|
2705
|
-
...getOptions(["root", "silent"]),
|
|
2706
|
-
{ name: "dev", type: Boolean, description: "create all sort of fancy certificates for dev testing purposes" },
|
|
2707
|
-
{ name: "clean", type: Boolean, description: "Purge existing directory [use with care!]" }
|
|
2708
|
-
];
|
|
2709
|
-
const local_argv = (0, import_command_line_args.default)(optionsDef, { argv });
|
|
2710
|
-
if (local_argv.help)
|
|
2711
|
-
return showHelp(
|
|
2712
|
-
"demo",
|
|
2713
|
-
"create default certificate for node-opcua demos",
|
|
2714
|
-
optionsDef,
|
|
2715
|
-
"$0 demo [--dev] [--silent] [--clean]"
|
|
2716
|
-
);
|
|
2717
|
-
await wrap(async () => {
|
|
2718
|
-
await ensure_openssl_installed();
|
|
2719
|
-
displayChapter("Create Demo certificates");
|
|
2720
|
-
displayTitle("reading configuration");
|
|
2721
|
-
await readConfiguration(local_argv);
|
|
2722
|
-
if (local_argv.clean) {
|
|
2723
|
-
displayTitle("Cleaning old certificates");
|
|
2724
|
-
(0, import_node_assert11.default)(gLocalConfig);
|
|
2725
|
-
const certificateDir = gLocalConfig.certificateDir || "";
|
|
2726
|
-
const files = await import_node_fs10.default.promises.readdir(certificateDir);
|
|
2727
|
-
for (const file of files) {
|
|
2728
|
-
if (file.includes(".pem") || file.includes(".pub")) {
|
|
2729
|
-
await import_node_fs10.default.promises.unlink(import_node_path7.default.join(certificateDir, file));
|
|
2730
|
-
}
|
|
2367
|
+
w.on("change", (changedPath) => {
|
|
2368
|
+
debugLog(import_chalk6.default.cyan(`change in folder ${folder}`), changedPath);
|
|
2369
|
+
try {
|
|
2370
|
+
const certificate = (0, import_node_opcua_crypto5.readCertificate)(changedPath);
|
|
2371
|
+
const newFingerprint = makeFingerprint(certificate);
|
|
2372
|
+
const oldHash = this.#filenameToHash.get(changedPath);
|
|
2373
|
+
if (oldHash && oldHash !== newFingerprint) {
|
|
2374
|
+
index.delete(oldHash);
|
|
2731
2375
|
}
|
|
2732
|
-
|
|
2376
|
+
index.set(newFingerprint, { certificate, filename: changedPath, info: (0, import_node_opcua_crypto5.exploreCertificate)(certificate) });
|
|
2377
|
+
this.#filenameToHash.set(changedPath, newFingerprint);
|
|
2378
|
+
} catch (err) {
|
|
2379
|
+
debugLog(`change event: failed to re-read ${changedPath}`, err);
|
|
2733
2380
|
}
|
|
2734
|
-
displayTitle("create certificates");
|
|
2735
|
-
await createDefaultCertificates(local_argv.dev);
|
|
2736
|
-
displayChapter("Demo certificates CREATED");
|
|
2737
|
-
});
|
|
2738
|
-
return;
|
|
2739
|
-
}
|
|
2740
|
-
if (command === "createCA") {
|
|
2741
|
-
const optionsDef = [
|
|
2742
|
-
...getOptions(["root", "CAFolder", "keySize", "silent"]),
|
|
2743
|
-
{ name: "subject", type: String, defaultValue: defaultSubject, description: "the CA certificate subject" }
|
|
2744
|
-
];
|
|
2745
|
-
const local_argv = (0, import_command_line_args.default)(optionsDef, { argv });
|
|
2746
|
-
if (local_argv.help) return showHelp("createCA", "create a Certificate Authority", optionsDef);
|
|
2747
|
-
await wrap(async () => {
|
|
2748
|
-
await ensure_openssl_installed();
|
|
2749
|
-
await readConfiguration(local_argv);
|
|
2750
|
-
await construct_CertificateAuthority2(local_argv.subject);
|
|
2751
|
-
});
|
|
2752
|
-
return;
|
|
2753
|
-
}
|
|
2754
|
-
if (command === "createPKI") {
|
|
2755
|
-
const optionsDef = getOptions(["root", "PKIFolder", "keySize", "silent"]);
|
|
2756
|
-
const local_argv = (0, import_command_line_args.default)(optionsDef, { argv });
|
|
2757
|
-
if (local_argv.help) return showHelp("createPKI", "create a Public Key Infrastructure", optionsDef);
|
|
2758
|
-
await wrap(async () => {
|
|
2759
|
-
await readConfiguration(local_argv);
|
|
2760
|
-
await construct_CertificateManager();
|
|
2761
2381
|
});
|
|
2762
|
-
|
|
2763
|
-
|
|
2764
|
-
|
|
2765
|
-
|
|
2766
|
-
|
|
2767
|
-
|
|
2768
|
-
|
|
2769
|
-
|
|
2770
|
-
type: String,
|
|
2771
|
-
defaultValue: "urn:{hostname}:Node-OPCUA-Server",
|
|
2772
|
-
description: "the application URI"
|
|
2773
|
-
},
|
|
2774
|
-
{
|
|
2775
|
-
name: "output",
|
|
2776
|
-
alias: "o",
|
|
2777
|
-
type: String,
|
|
2778
|
-
defaultValue: "my_certificate.pem",
|
|
2779
|
-
description: "the name of the generated certificate =>"
|
|
2780
|
-
},
|
|
2781
|
-
{
|
|
2782
|
-
name: "selfSigned",
|
|
2783
|
-
alias: "s",
|
|
2784
|
-
type: Boolean,
|
|
2785
|
-
defaultValue: false,
|
|
2786
|
-
description: "if true, certificate will be self-signed"
|
|
2787
|
-
},
|
|
2788
|
-
{ name: "validity", alias: "v", type: Number, description: "the certificate validity in days" },
|
|
2789
|
-
{
|
|
2790
|
-
name: "dns",
|
|
2791
|
-
type: String,
|
|
2792
|
-
defaultValue: "{hostname}",
|
|
2793
|
-
description: "the list of valid domain name (comma separated)"
|
|
2794
|
-
},
|
|
2795
|
-
{ name: "ip", type: String, defaultValue: "", description: "the list of valid IPs (comma separated)" },
|
|
2796
|
-
{
|
|
2797
|
-
name: "subject",
|
|
2798
|
-
type: String,
|
|
2799
|
-
defaultValue: "",
|
|
2800
|
-
description: "the certificate subject ( for instance C=FR/ST=Centre/L=Orleans/O=SomeOrganization/CN=Hello )"
|
|
2801
|
-
}
|
|
2802
|
-
];
|
|
2803
|
-
const local_argv = (0, import_command_line_args.default)(optionsDef, { argv });
|
|
2804
|
-
if (local_argv.help || !local_argv.applicationUri || !local_argv.output)
|
|
2805
|
-
return showHelp("certificate", "create a new certificate", optionsDef);
|
|
2806
|
-
async function command_certificate(local_argv2) {
|
|
2807
|
-
const selfSigned = !!local_argv2.selfSigned;
|
|
2808
|
-
if (!selfSigned) {
|
|
2809
|
-
await command_full_certificate(local_argv2);
|
|
2810
|
-
} else {
|
|
2811
|
-
await command_selfsigned_certificate(local_argv2);
|
|
2812
|
-
}
|
|
2813
|
-
}
|
|
2814
|
-
async function command_selfsigned_certificate(local_argv2) {
|
|
2815
|
-
const _fqdn = await extractFullyQualifiedDomainName();
|
|
2816
|
-
await readConfiguration(local_argv2);
|
|
2817
|
-
await construct_CertificateManager();
|
|
2818
|
-
displaySubtitle(` create self signed Certificate ${gLocalConfig.outputFile}`);
|
|
2819
|
-
let subject = local_argv2.subject && local_argv2.subject.length > 1 ? new import_node_opcua_crypto6.Subject(local_argv2.subject) : gLocalConfig.subject || "";
|
|
2820
|
-
subject = JSON.parse(JSON.stringify(subject));
|
|
2821
|
-
const params = {
|
|
2822
|
-
applicationUri: gLocalConfig.applicationUri || "",
|
|
2823
|
-
dns: gLocalConfig.dns || [],
|
|
2824
|
-
ip: gLocalConfig.ip || [],
|
|
2825
|
-
outputFile: gLocalConfig.outputFile || "self_signed_certificate.pem",
|
|
2826
|
-
startDate: gLocalConfig.startDate || /* @__PURE__ */ new Date(),
|
|
2827
|
-
subject,
|
|
2828
|
-
validity: gLocalConfig.validity || 365
|
|
2829
|
-
};
|
|
2830
|
-
await certificateManager.createSelfSignedCertificate(params);
|
|
2831
|
-
}
|
|
2832
|
-
async function command_full_certificate(local_argv2) {
|
|
2833
|
-
await readConfiguration(local_argv2);
|
|
2834
|
-
await construct_CertificateManager();
|
|
2835
|
-
await construct_CertificateAuthority2("");
|
|
2836
|
-
(0, import_node_assert11.default)(import_node_fs10.default.existsSync(gLocalConfig.CAFolder || ""), " CA folder must exist");
|
|
2837
|
-
gLocalConfig.privateKey = void 0;
|
|
2838
|
-
gLocalConfig.subject = local_argv2.subject && local_argv2.subject.length > 1 ? local_argv2.subject : gLocalConfig.subject;
|
|
2839
|
-
const csr_file = await certificateManager.createCertificateRequest(
|
|
2840
|
-
gLocalConfig
|
|
2841
|
-
);
|
|
2842
|
-
if (!csr_file) {
|
|
2843
|
-
return;
|
|
2844
|
-
}
|
|
2845
|
-
warningLog(" csr_file = ", csr_file);
|
|
2846
|
-
const certificate = csr_file.replace(".csr", ".pem");
|
|
2847
|
-
if (import_node_fs10.default.existsSync(certificate)) {
|
|
2848
|
-
throw new Error(` File ${certificate} already exist`);
|
|
2849
|
-
}
|
|
2850
|
-
await g_certificateAuthority.signCertificateRequest(
|
|
2851
|
-
certificate,
|
|
2852
|
-
csr_file,
|
|
2853
|
-
gLocalConfig
|
|
2854
|
-
);
|
|
2855
|
-
(0, import_node_assert11.default)(typeof gLocalConfig.outputFile === "string");
|
|
2856
|
-
import_node_fs10.default.writeFileSync(gLocalConfig.outputFile || "", import_node_fs10.default.readFileSync(certificate, "ascii"));
|
|
2857
|
-
}
|
|
2858
|
-
await wrap(async () => await command_certificate(local_argv));
|
|
2859
|
-
return;
|
|
2860
|
-
}
|
|
2861
|
-
if (command === "revoke") {
|
|
2862
|
-
const optionsDef = [{ name: "certificateFile", type: String, defaultOption: true }, ...getOptions(["root", "CAFolder"])];
|
|
2863
|
-
const local_argv = (0, import_command_line_args.default)(optionsDef, { argv });
|
|
2864
|
-
if (local_argv.help || !local_argv.certificateFile)
|
|
2865
|
-
return showHelp(
|
|
2866
|
-
"revoke <certificateFile>",
|
|
2867
|
-
"revoke a existing certificate",
|
|
2868
|
-
optionsDef,
|
|
2869
|
-
"$0 revoke my_certificate.pem"
|
|
2870
|
-
);
|
|
2871
|
-
async function revoke_certificate(certificate) {
|
|
2872
|
-
await g_certificateAuthority.revokeCertificate(certificate, {});
|
|
2873
|
-
}
|
|
2874
|
-
await wrap(async () => {
|
|
2875
|
-
const certificate = import_node_path7.default.resolve(local_argv.certificateFile);
|
|
2876
|
-
warningLog(import_chalk7.default.yellow(" Certificate to revoke : "), import_chalk7.default.cyan(certificate));
|
|
2877
|
-
if (!import_node_fs10.default.existsSync(certificate)) {
|
|
2878
|
-
throw new Error(`cannot find certificate to revoke ${certificate}`);
|
|
2879
|
-
}
|
|
2880
|
-
await readConfiguration(local_argv);
|
|
2881
|
-
await construct_CertificateAuthority2("");
|
|
2882
|
-
await revoke_certificate(certificate);
|
|
2883
|
-
warningLog("done ... ");
|
|
2884
|
-
warningLog(" crl = ", g_certificateAuthority.revocationList);
|
|
2885
|
-
warningLog("\nyou should now publish the new Certificate Revocation List");
|
|
2382
|
+
this.#watchers.push(w);
|
|
2383
|
+
await new Promise((resolve, _reject) => {
|
|
2384
|
+
w.on("ready", () => {
|
|
2385
|
+
unreffAll();
|
|
2386
|
+
debugLog("ready");
|
|
2387
|
+
debugLog([...index.keys()].map((k) => k.substring(0, 10)));
|
|
2388
|
+
resolve();
|
|
2389
|
+
});
|
|
2886
2390
|
});
|
|
2887
|
-
return;
|
|
2888
2391
|
}
|
|
2889
|
-
|
|
2890
|
-
|
|
2891
|
-
|
|
2892
|
-
{
|
|
2893
|
-
|
|
2894
|
-
alias: "a",
|
|
2895
|
-
type: String,
|
|
2896
|
-
defaultValue: "urn:{hostname}:Node-OPCUA-Server",
|
|
2897
|
-
description: "the application URI"
|
|
2898
|
-
},
|
|
2899
|
-
{
|
|
2900
|
-
name: "output",
|
|
2901
|
-
alias: "o",
|
|
2902
|
-
type: String,
|
|
2903
|
-
defaultValue: "my_certificate_signing_request.csr",
|
|
2904
|
-
description: "the name of the generated signing_request"
|
|
2905
|
-
},
|
|
2906
|
-
{
|
|
2907
|
-
name: "dns",
|
|
2908
|
-
type: String,
|
|
2909
|
-
defaultValue: "{hostname}",
|
|
2910
|
-
description: "the list of valid domain name (comma separated)"
|
|
2911
|
-
},
|
|
2912
|
-
{ name: "ip", type: String, defaultValue: "", description: "the list of valid IPs (comma separated)" },
|
|
2913
|
-
{
|
|
2914
|
-
name: "subject",
|
|
2915
|
-
type: String,
|
|
2916
|
-
defaultValue: "/CN=Certificate",
|
|
2917
|
-
description: "the certificate subject ( for instance /C=FR/ST=Centre/L=Orleans/O=SomeOrganization/CN=Hello )"
|
|
2918
|
-
}
|
|
2919
|
-
];
|
|
2920
|
-
const local_argv = (0, import_command_line_args.default)(optionsDef, { argv });
|
|
2921
|
-
if (local_argv.help) return showHelp("csr", "create a certificate signing request", optionsDef);
|
|
2922
|
-
await wrap(async () => {
|
|
2923
|
-
await readConfiguration(local_argv);
|
|
2924
|
-
if (!import_node_fs10.default.existsSync(gLocalConfig.PKIFolder || "")) {
|
|
2925
|
-
warningLog("PKI folder must exist");
|
|
2926
|
-
}
|
|
2927
|
-
await construct_CertificateManager();
|
|
2928
|
-
if (!gLocalConfig.outputFile || import_node_fs10.default.existsSync(gLocalConfig.outputFile)) {
|
|
2929
|
-
throw new Error(` File ${gLocalConfig.outputFile} already exist`);
|
|
2930
|
-
}
|
|
2931
|
-
gLocalConfig.privateKey = void 0;
|
|
2932
|
-
gLocalConfig.subject = local_argv.subject && local_argv.subject.length > 1 ? local_argv.subject : gLocalConfig.subject;
|
|
2933
|
-
const internal_csr_file = await certificateManager.createCertificateRequest(
|
|
2934
|
-
gLocalConfig
|
|
2935
|
-
);
|
|
2936
|
-
if (!internal_csr_file) {
|
|
2937
|
-
return;
|
|
2938
|
-
}
|
|
2939
|
-
if (!gLocalConfig.outputFile) {
|
|
2940
|
-
warningLog("please specify a output file");
|
|
2392
|
+
// make sure that all crls have been processed.
|
|
2393
|
+
async #waitAndCheckCRLProcessingStatus() {
|
|
2394
|
+
return new Promise((resolve, reject) => {
|
|
2395
|
+
if (this.#pendingCrlToProcess === 0) {
|
|
2396
|
+
setImmediate(resolve);
|
|
2941
2397
|
return;
|
|
2942
2398
|
}
|
|
2943
|
-
|
|
2944
|
-
|
|
2945
|
-
warningLog("Subject = ", gLocalConfig.subject);
|
|
2946
|
-
warningLog("applicationUri = ", gLocalConfig.applicationUri);
|
|
2947
|
-
warningLog("altNames = ", gLocalConfig.altNames);
|
|
2948
|
-
warningLog("dns = ", gLocalConfig.dns);
|
|
2949
|
-
warningLog("ip = ", gLocalConfig.ip);
|
|
2950
|
-
warningLog("CSR file = ", gLocalConfig.outputFile);
|
|
2951
|
-
});
|
|
2952
|
-
return;
|
|
2953
|
-
}
|
|
2954
|
-
if (command === "sign") {
|
|
2955
|
-
const optionsDef = [
|
|
2956
|
-
...getOptions(["root", "CAFolder", "silent"]),
|
|
2957
|
-
{ name: "csr", alias: "i", type: String, defaultValue: "my_certificate_signing_request.csr", description: "the csr" },
|
|
2958
|
-
{
|
|
2959
|
-
name: "output",
|
|
2960
|
-
alias: "o",
|
|
2961
|
-
type: String,
|
|
2962
|
-
defaultValue: "my_certificate.pem",
|
|
2963
|
-
description: "the name of the generated certificate"
|
|
2964
|
-
},
|
|
2965
|
-
{ name: "validity", alias: "v", type: Number, defaultValue: 365, description: "the certificate validity in days" }
|
|
2966
|
-
];
|
|
2967
|
-
const local_argv = (0, import_command_line_args.default)(optionsDef, { argv });
|
|
2968
|
-
if (local_argv.help || !local_argv.csr || !local_argv.output)
|
|
2969
|
-
return showHelp("sign", "validate a certificate signing request and generate a certificate", optionsDef);
|
|
2970
|
-
await wrap(async () => {
|
|
2971
|
-
await readConfiguration(local_argv);
|
|
2972
|
-
if (!import_node_fs10.default.existsSync(gLocalConfig.CAFolder || "")) {
|
|
2973
|
-
throw new Error(`CA folder must exist:${gLocalConfig.CAFolder}`);
|
|
2974
|
-
}
|
|
2975
|
-
await construct_CertificateAuthority2("");
|
|
2976
|
-
const csr_file = import_node_path7.default.resolve(local_argv.csr || "");
|
|
2977
|
-
if (!import_node_fs10.default.existsSync(csr_file)) {
|
|
2978
|
-
throw new Error(`Certificate signing request doesn't exist: ${csr_file}`);
|
|
2979
|
-
}
|
|
2980
|
-
const certificate = import_node_path7.default.resolve(local_argv.output || csr_file.replace(".csr", ".pem"));
|
|
2981
|
-
if (import_node_fs10.default.existsSync(certificate)) {
|
|
2982
|
-
throw new Error(` File ${certificate} already exist`);
|
|
2399
|
+
if (this.#onCrlProcess) {
|
|
2400
|
+
return reject(new Error("Internal Error"));
|
|
2983
2401
|
}
|
|
2984
|
-
|
|
2985
|
-
certificate,
|
|
2986
|
-
csr_file,
|
|
2987
|
-
gLocalConfig
|
|
2988
|
-
);
|
|
2989
|
-
(0, import_node_assert11.default)(typeof gLocalConfig.outputFile === "string");
|
|
2990
|
-
import_node_fs10.default.writeFileSync(gLocalConfig.outputFile || "", import_node_fs10.default.readFileSync(certificate, "ascii"));
|
|
2991
|
-
});
|
|
2992
|
-
return;
|
|
2993
|
-
}
|
|
2994
|
-
if (command === "dump") {
|
|
2995
|
-
const optionsDef = [
|
|
2996
|
-
{ name: "certificateFile", type: String, defaultOption: true },
|
|
2997
|
-
{ name: "help", alias: "h", type: Boolean }
|
|
2998
|
-
];
|
|
2999
|
-
const local_argv = (0, import_command_line_args.default)(optionsDef, { argv });
|
|
3000
|
-
if (local_argv.help || !local_argv.certificateFile)
|
|
3001
|
-
return showHelp("dump <certificateFile>", "display a certificate", optionsDef);
|
|
3002
|
-
await wrap(async () => {
|
|
3003
|
-
const data = await dumpCertificate(local_argv.certificateFile);
|
|
3004
|
-
warningLog(data);
|
|
3005
|
-
});
|
|
3006
|
-
return;
|
|
3007
|
-
}
|
|
3008
|
-
if (command === "toder") {
|
|
3009
|
-
const optionsDef = [
|
|
3010
|
-
{ name: "pemCertificate", type: String, defaultOption: true },
|
|
3011
|
-
{ name: "help", alias: "h", type: Boolean }
|
|
3012
|
-
];
|
|
3013
|
-
const local_argv = (0, import_command_line_args.default)(optionsDef, { argv });
|
|
3014
|
-
if (local_argv.help || !local_argv.pemCertificate)
|
|
3015
|
-
return showHelp("toder <pemCertificate>", "convert a certificate to a DER format with finger print", optionsDef);
|
|
3016
|
-
await wrap(async () => {
|
|
3017
|
-
await toDer(local_argv.pemCertificate);
|
|
2402
|
+
this.#onCrlProcess = resolve;
|
|
3018
2403
|
});
|
|
3019
|
-
return;
|
|
3020
|
-
}
|
|
3021
|
-
if (command === "fingerprint") {
|
|
3022
|
-
const optionsDef = [
|
|
3023
|
-
{ name: "certificateFile", type: String, defaultOption: true },
|
|
3024
|
-
{ name: "help", alias: "h", type: Boolean }
|
|
3025
|
-
];
|
|
3026
|
-
const local_argv = (0, import_command_line_args.default)(optionsDef, { argv });
|
|
3027
|
-
if (local_argv.help || !local_argv.certificateFile)
|
|
3028
|
-
return showHelp("fingerprint <certificateFile>", "print the certificate fingerprint", optionsDef);
|
|
3029
|
-
await wrap(async () => {
|
|
3030
|
-
const certificate = local_argv.certificateFile;
|
|
3031
|
-
const data = await fingerprint(certificate);
|
|
3032
|
-
if (!data) return;
|
|
3033
|
-
const s = data.split("=")[1].split(":").join("").trim();
|
|
3034
|
-
warningLog(s);
|
|
3035
|
-
});
|
|
3036
|
-
return;
|
|
3037
2404
|
}
|
|
3038
|
-
|
|
2405
|
+
};
|
|
2406
|
+
|
|
2407
|
+
// packages/node-opcua-pki/lib/pki/toolbox_pfx.ts
|
|
2408
|
+
var import_node_assert9 = __toESM(require("assert"));
|
|
2409
|
+
var import_node_fs10 = __toESM(require("fs"));
|
|
2410
|
+
var q2 = quote;
|
|
2411
|
+
var n3 = makePath;
|
|
2412
|
+
async function createPFX(options) {
|
|
2413
|
+
const { certificateFile, privateKeyFile, outputFile, passphrase = "", caCertificateFiles } = options;
|
|
2414
|
+
(0, import_node_assert9.default)(import_node_fs10.default.existsSync(certificateFile), `Certificate file does not exist: ${certificateFile}`);
|
|
2415
|
+
(0, import_node_assert9.default)(import_node_fs10.default.existsSync(privateKeyFile), `Private key file does not exist: ${privateKeyFile}`);
|
|
2416
|
+
let cmd = `pkcs12 -export`;
|
|
2417
|
+
cmd += ` -in ${q2(n3(certificateFile))}`;
|
|
2418
|
+
cmd += ` -inkey ${q2(n3(privateKeyFile))}`;
|
|
2419
|
+
if (caCertificateFiles) {
|
|
2420
|
+
for (const caFile of caCertificateFiles) {
|
|
2421
|
+
(0, import_node_assert9.default)(import_node_fs10.default.existsSync(caFile), `CA certificate file does not exist: ${caFile}`);
|
|
2422
|
+
cmd += ` -certfile ${q2(n3(caFile))}`;
|
|
2423
|
+
}
|
|
2424
|
+
}
|
|
2425
|
+
cmd += ` -out ${q2(n3(outputFile))}`;
|
|
2426
|
+
cmd += ` -passout pass:${passphrase}`;
|
|
2427
|
+
await execute_openssl(cmd, {});
|
|
2428
|
+
}
|
|
2429
|
+
async function extractCertificateFromPFX(options) {
|
|
2430
|
+
const { pfxFile, passphrase = "" } = options;
|
|
2431
|
+
(0, import_node_assert9.default)(import_node_fs10.default.existsSync(pfxFile), `PFX file does not exist: ${pfxFile}`);
|
|
2432
|
+
const cmd = `pkcs12 -in ${q2(n3(pfxFile))} -clcerts -nokeys -nodes -passin pass:${passphrase}`;
|
|
2433
|
+
return await execute_openssl(cmd, {});
|
|
2434
|
+
}
|
|
2435
|
+
async function extractPrivateKeyFromPFX(options) {
|
|
2436
|
+
const { pfxFile, passphrase = "" } = options;
|
|
2437
|
+
(0, import_node_assert9.default)(import_node_fs10.default.existsSync(pfxFile), `PFX file does not exist: ${pfxFile}`);
|
|
2438
|
+
const cmd = `pkcs12 -in ${q2(n3(pfxFile))} -nocerts -nodes -passin pass:${passphrase}`;
|
|
2439
|
+
return await execute_openssl(cmd, {});
|
|
2440
|
+
}
|
|
2441
|
+
async function extractCACertificatesFromPFX(options) {
|
|
2442
|
+
const { pfxFile, passphrase = "" } = options;
|
|
2443
|
+
(0, import_node_assert9.default)(import_node_fs10.default.existsSync(pfxFile), `PFX file does not exist: ${pfxFile}`);
|
|
2444
|
+
const cmd = `pkcs12 -in ${q2(n3(pfxFile))} -cacerts -nokeys -nodes -passin pass:${passphrase}`;
|
|
2445
|
+
return await execute_openssl(cmd, {});
|
|
2446
|
+
}
|
|
2447
|
+
async function extractAllFromPFX(options) {
|
|
2448
|
+
const [certificate, privateKey, caCertificates] = await Promise.all([
|
|
2449
|
+
extractCertificateFromPFX(options),
|
|
2450
|
+
extractPrivateKeyFromPFX(options),
|
|
2451
|
+
extractCACertificatesFromPFX(options)
|
|
2452
|
+
]);
|
|
2453
|
+
return { certificate, privateKey, caCertificates };
|
|
2454
|
+
}
|
|
2455
|
+
async function convertPFXtoPEM(pfxFile, pemFile, passphrase = "") {
|
|
2456
|
+
(0, import_node_assert9.default)(import_node_fs10.default.existsSync(pfxFile), `PFX file does not exist: ${pfxFile}`);
|
|
2457
|
+
const cmd = `pkcs12 -in ${q2(n3(pfxFile))} -out ${q2(n3(pemFile))} -nodes -passin pass:${passphrase}`;
|
|
2458
|
+
await execute_openssl(cmd, {});
|
|
2459
|
+
}
|
|
2460
|
+
async function dumpPFX(pfxFile, passphrase = "") {
|
|
2461
|
+
(0, import_node_assert9.default)(import_node_fs10.default.existsSync(pfxFile), `PFX file does not exist: ${pfxFile}`);
|
|
2462
|
+
const cmd = `pkcs12 -in ${q2(n3(pfxFile))} -info -nodes -passin pass:${passphrase}`;
|
|
2463
|
+
return await execute_openssl(cmd, {});
|
|
3039
2464
|
}
|
|
3040
2465
|
// Annotate the CommonJS export names for ESM import in node:
|
|
3041
2466
|
0 && (module.exports = {
|
|
@@ -3046,22 +2471,15 @@ ${epilog}`
|
|
|
3046
2471
|
VerificationStatus,
|
|
3047
2472
|
adjustApplicationUri,
|
|
3048
2473
|
adjustDate,
|
|
3049
|
-
|
|
3050
|
-
|
|
3051
|
-
|
|
3052
|
-
|
|
3053
|
-
|
|
3054
|
-
|
|
3055
|
-
|
|
3056
|
-
displayTitle,
|
|
3057
|
-
doDebug,
|
|
2474
|
+
convertPFXtoPEM,
|
|
2475
|
+
createPFX,
|
|
2476
|
+
dumpPFX,
|
|
2477
|
+
extractAllFromPFX,
|
|
2478
|
+
extractCACertificatesFromPFX,
|
|
2479
|
+
extractCertificateFromPFX,
|
|
2480
|
+
extractPrivateKeyFromPFX,
|
|
3058
2481
|
findIssuerCertificateInChain,
|
|
3059
|
-
g_config,
|
|
3060
2482
|
install_prerequisite,
|
|
3061
|
-
|
|
3062
|
-
mkdirRecursiveSync,
|
|
3063
|
-
pki_main,
|
|
3064
|
-
quote,
|
|
3065
|
-
warningLog
|
|
2483
|
+
quote
|
|
3066
2484
|
});
|
|
3067
2485
|
//# sourceMappingURL=index.js.map
|