node-opcua-pki 6.13.0 → 6.15.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/bin/pki.mjs +144 -7
- package/dist/bin/pki.mjs.map +1 -1
- package/dist/index.d.mts +76 -0
- package/dist/index.d.ts +76 -0
- package/dist/index.js +144 -7
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +144 -7
- package/dist/index.mjs.map +1 -1
- package/package.json +2 -2
package/dist/index.mjs
CHANGED
|
@@ -120,9 +120,15 @@ function setEnv(varName, value) {
|
|
|
120
120
|
process.env[varName] = value;
|
|
121
121
|
}
|
|
122
122
|
}
|
|
123
|
+
function hasEnv(varName) {
|
|
124
|
+
return Object.prototype.hasOwnProperty.call(exportedEnvVars, varName);
|
|
125
|
+
}
|
|
123
126
|
function getEnv(varName) {
|
|
124
127
|
return exportedEnvVars[varName];
|
|
125
128
|
}
|
|
129
|
+
function unsetEnv(varName) {
|
|
130
|
+
delete exportedEnvVars[varName];
|
|
131
|
+
}
|
|
126
132
|
function getEnvironmentVarNames() {
|
|
127
133
|
return Object.keys(exportedEnvVars).map((varName) => {
|
|
128
134
|
return { key: varName, pattern: `\\$ENV\\:\\:${varName}` };
|
|
@@ -639,10 +645,17 @@ function openssl_require2DigitYearInDate() {
|
|
|
639
645
|
}
|
|
640
646
|
g_config.opensslVersion = "";
|
|
641
647
|
var _counter = 0;
|
|
648
|
+
function stripConditionalBlocks(template) {
|
|
649
|
+
return template.replace(/\{\{#([A-Z_][A-Z0-9_]*)\}\}([\s\S]*?)\{\{\/\1\}\}\r?\n?/g, (_match, key, content) => {
|
|
650
|
+
const keep = hasEnv(key) && getEnv(key) !== "";
|
|
651
|
+
return keep ? content : "";
|
|
652
|
+
});
|
|
653
|
+
}
|
|
642
654
|
function generateStaticConfig(configPath, options) {
|
|
643
655
|
const prePath = options?.cwd || "";
|
|
644
656
|
const originalFilename = !path3.isAbsolute(configPath) ? path3.join(prePath, configPath) : configPath;
|
|
645
657
|
let staticConfig = fs5.readFileSync(originalFilename, { encoding: "utf8" });
|
|
658
|
+
staticConfig = stripConditionalBlocks(staticConfig);
|
|
646
659
|
for (const envVar of getEnvironmentVarNames()) {
|
|
647
660
|
staticConfig = staticConfig.replace(new RegExp(envVar.pattern, "gi"), getEnv(envVar.key));
|
|
648
661
|
}
|
|
@@ -797,7 +810,9 @@ nsComment = ''OpenSSL Generated Certificate''
|
|
|
797
810
|
#nsSslServerName =
|
|
798
811
|
keyUsage = critical, digitalSignature, nonRepudiation, keyEncipherment, dataEncipherment, keyAgreement
|
|
799
812
|
extendedKeyUsage = critical,serverAuth ,clientAuth
|
|
800
|
-
|
|
813
|
+
{{#CDP_URL}}crlDistributionPoints = URI:$ENV::CDP_URL
|
|
814
|
+
{{/CDP_URL}}{{#AIA_VALUE}}authorityInfoAccess = $ENV::AIA_VALUE
|
|
815
|
+
{{/AIA_VALUE}}
|
|
801
816
|
[ v3_req ]
|
|
802
817
|
basicConstraints = critical, CA:FALSE
|
|
803
818
|
keyUsage = nonRepudiation, digitalSignature, keyEncipherment, dataEncipherment, keyAgreement
|
|
@@ -808,21 +823,21 @@ nsComment = "CA Generated by Node-OPCUA Certificate utility usin
|
|
|
808
823
|
subjectKeyIdentifier = hash
|
|
809
824
|
basicConstraints = CA:TRUE
|
|
810
825
|
keyUsage = critical, cRLSign, keyCertSign
|
|
826
|
+
subjectAltName = $ENV::ALTNAME
|
|
811
827
|
nsComment = "CA CSR generated by Node-OPCUA Certificate utility using openssl"
|
|
812
828
|
[ v3_ca ]
|
|
813
829
|
subjectKeyIdentifier = hash
|
|
814
830
|
authorityKeyIdentifier = keyid:always,issuer:always
|
|
815
831
|
basicConstraints = CA:TRUE
|
|
816
832
|
keyUsage = critical, cRLSign, keyCertSign
|
|
833
|
+
subjectAltName = $ENV::ALTNAME
|
|
817
834
|
nsComment = "CA Certificate generated by Node-OPCUA Certificate utility using openssl"
|
|
818
835
|
#nsCertType = sslCA, emailCA
|
|
819
|
-
#subjectAltName = email:copy
|
|
820
836
|
#issuerAltName = issuer:copy
|
|
821
837
|
#obj = DER:02:03
|
|
822
|
-
crlDistributionPoints =
|
|
823
|
-
|
|
824
|
-
|
|
825
|
-
[ v3_selfsigned]
|
|
838
|
+
{{#CDP_URL}}crlDistributionPoints = URI:$ENV::CDP_URL
|
|
839
|
+
{{/CDP_URL}}{{#AIA_VALUE}}authorityInfoAccess = $ENV::AIA_VALUE
|
|
840
|
+
{{/AIA_VALUE}}[ v3_selfsigned]
|
|
826
841
|
basicConstraints = critical, CA:FALSE
|
|
827
842
|
keyUsage = nonRepudiation, digitalSignature, keyEncipherment, dataEncipherment, keyAgreement
|
|
828
843
|
extendedKeyUsage = critical,serverAuth ,clientAuth
|
|
@@ -903,7 +918,9 @@ async function construct_CertificateAuthority(certificateAuthority) {
|
|
|
903
918
|
await fs7.promises.writeFile(caConfigFile, data);
|
|
904
919
|
}
|
|
905
920
|
const subjectOpt = ` -subj "${subject.toString()}" `;
|
|
906
|
-
|
|
921
|
+
const caCommonName = subject.commonName || "NodeOPCUA-CA";
|
|
922
|
+
setEnv("ALTNAME", `URI:urn:${caCommonName}`);
|
|
923
|
+
certificateAuthority._wireRevocationEnvVars();
|
|
907
924
|
const options = { cwd: caRootDir };
|
|
908
925
|
const configFile = generateStaticConfig("conf/caconfig.cnf", options);
|
|
909
926
|
const configOption = ` -config ${q3(n4(configFile))}`;
|
|
@@ -957,6 +974,33 @@ function parseOpenSSLDate(dateStr) {
|
|
|
957
974
|
const sec = raw.substring(10, 12);
|
|
958
975
|
return `${year}-${month}-${day}T${hour}:${min}:${sec}Z`;
|
|
959
976
|
}
|
|
977
|
+
function validateRevocationUrl(url2, fieldName) {
|
|
978
|
+
if (url2 === void 0) {
|
|
979
|
+
return void 0;
|
|
980
|
+
}
|
|
981
|
+
if (url2 === "") {
|
|
982
|
+
throw new Error(`${fieldName} must not be empty \u2014 pass undefined to disable the extension`);
|
|
983
|
+
}
|
|
984
|
+
let parsed;
|
|
985
|
+
try {
|
|
986
|
+
parsed = new URL(url2);
|
|
987
|
+
} catch {
|
|
988
|
+
throw new Error(`${fieldName} is not a valid URL: ${url2}`);
|
|
989
|
+
}
|
|
990
|
+
if (parsed.protocol !== "http:" && parsed.protocol !== "https:") {
|
|
991
|
+
throw new Error(`${fieldName} must use http: or https: (got ${parsed.protocol} in ${url2})`);
|
|
992
|
+
}
|
|
993
|
+
if (!parsed.pathname || parsed.pathname === "/") {
|
|
994
|
+
throw new Error(`${fieldName} must include a path component (got ${url2})`);
|
|
995
|
+
}
|
|
996
|
+
const isLoopback = parsed.hostname === "localhost" || parsed.hostname === "::1" || parsed.hostname.startsWith("127.");
|
|
997
|
+
if (isLoopback) {
|
|
998
|
+
console.warn(
|
|
999
|
+
`[node-opcua-pki] ${fieldName} points at loopback (${url2}) \u2014 certificates issued with this URL will be unreachable from any other host.`
|
|
1000
|
+
);
|
|
1001
|
+
}
|
|
1002
|
+
return url2;
|
|
1003
|
+
}
|
|
960
1004
|
var CertificateAuthority = class {
|
|
961
1005
|
/** RSA key size used when generating the CA private key. */
|
|
962
1006
|
keySize;
|
|
@@ -966,6 +1010,10 @@ var CertificateAuthority = class {
|
|
|
966
1010
|
subject;
|
|
967
1011
|
/** @internal Parent CA (undefined for root CAs). */
|
|
968
1012
|
_issuerCA;
|
|
1013
|
+
/** @internal Configured CDP / AIA URLs (US-202). */
|
|
1014
|
+
_crlDistributionUrl;
|
|
1015
|
+
_ocspResponderUrl;
|
|
1016
|
+
_caIssuersUrl;
|
|
969
1017
|
constructor(options) {
|
|
970
1018
|
assert7(Object.prototype.hasOwnProperty.call(options, "location"));
|
|
971
1019
|
assert7(Object.prototype.hasOwnProperty.call(options, "keySize"));
|
|
@@ -973,6 +1021,93 @@ var CertificateAuthority = class {
|
|
|
973
1021
|
this.keySize = options.keySize || 2048;
|
|
974
1022
|
this.subject = new Subject2(options.subject || defaultSubject);
|
|
975
1023
|
this._issuerCA = options.issuerCA;
|
|
1024
|
+
if (options.crlDistributionUrl !== void 0) {
|
|
1025
|
+
this.setCrlDistributionUrl(options.crlDistributionUrl);
|
|
1026
|
+
}
|
|
1027
|
+
if (options.ocspResponderUrl !== void 0) {
|
|
1028
|
+
this.setOcspResponderUrl(options.ocspResponderUrl);
|
|
1029
|
+
}
|
|
1030
|
+
if (options.caIssuersUrl !== void 0) {
|
|
1031
|
+
this.setCaIssuersUrl(options.caIssuersUrl);
|
|
1032
|
+
}
|
|
1033
|
+
}
|
|
1034
|
+
/**
|
|
1035
|
+
* Public URL where the CRL produced by this CA is reachable, or
|
|
1036
|
+
* `undefined` if no CDP extension should be emitted on issued certs.
|
|
1037
|
+
*/
|
|
1038
|
+
get crlDistributionUrl() {
|
|
1039
|
+
return this._crlDistributionUrl;
|
|
1040
|
+
}
|
|
1041
|
+
/**
|
|
1042
|
+
* Public URL of the OCSP responder, or `undefined` if no AIA OCSP
|
|
1043
|
+
* leg should be emitted on issued certs.
|
|
1044
|
+
*/
|
|
1045
|
+
get ocspResponderUrl() {
|
|
1046
|
+
return this._ocspResponderUrl;
|
|
1047
|
+
}
|
|
1048
|
+
/**
|
|
1049
|
+
* Public URL where the issuer's certificate can be fetched, or
|
|
1050
|
+
* `undefined` if no AIA caIssuers leg should be emitted.
|
|
1051
|
+
*/
|
|
1052
|
+
get caIssuersUrl() {
|
|
1053
|
+
return this._caIssuersUrl;
|
|
1054
|
+
}
|
|
1055
|
+
/**
|
|
1056
|
+
* Configure the URL embedded as `crlDistributionPoints` in every
|
|
1057
|
+
* subsequently-issued certificate. Pass `undefined` to disable
|
|
1058
|
+
* the extension entirely. Validated synchronously — throws on
|
|
1059
|
+
* empty string, non-http(s) protocol, missing path. Warns (does
|
|
1060
|
+
* not throw) when the URL points at loopback.
|
|
1061
|
+
*
|
|
1062
|
+
* @see US-202
|
|
1063
|
+
*/
|
|
1064
|
+
setCrlDistributionUrl(url2) {
|
|
1065
|
+
this._crlDistributionUrl = validateRevocationUrl(url2, "crlDistributionUrl");
|
|
1066
|
+
}
|
|
1067
|
+
/**
|
|
1068
|
+
* Configure the OCSP responder URL embedded as the `OCSP` leg of
|
|
1069
|
+
* the `authorityInfoAccess` extension on every subsequently-issued
|
|
1070
|
+
* certificate. Pass `undefined` to disable.
|
|
1071
|
+
*
|
|
1072
|
+
* @see US-202
|
|
1073
|
+
*/
|
|
1074
|
+
setOcspResponderUrl(url2) {
|
|
1075
|
+
this._ocspResponderUrl = validateRevocationUrl(url2, "ocspResponderUrl");
|
|
1076
|
+
}
|
|
1077
|
+
/**
|
|
1078
|
+
* Configure the caIssuers URL embedded as the `caIssuers` leg of
|
|
1079
|
+
* the `authorityInfoAccess` extension on every subsequently-issued
|
|
1080
|
+
* certificate. Pass `undefined` to disable.
|
|
1081
|
+
*
|
|
1082
|
+
* @see US-202
|
|
1083
|
+
*/
|
|
1084
|
+
setCaIssuersUrl(url2) {
|
|
1085
|
+
this._caIssuersUrl = validateRevocationUrl(url2, "caIssuersUrl");
|
|
1086
|
+
}
|
|
1087
|
+
/**
|
|
1088
|
+
* @internal
|
|
1089
|
+
* Populate the OpenSSL config substitution env vars (`CDP_URL` and
|
|
1090
|
+
* `AIA_VALUE`) from the configured URLs, or unset them so the
|
|
1091
|
+
* matching `{{#KEY}}...{{/KEY}}` blocks in the templates are
|
|
1092
|
+
* stripped. MUST be called before every `generateStaticConfig`
|
|
1093
|
+
* invocation that signs a certificate.
|
|
1094
|
+
*/
|
|
1095
|
+
_wireRevocationEnvVars() {
|
|
1096
|
+
unsetEnv("CDP_URL");
|
|
1097
|
+
unsetEnv("AIA_VALUE");
|
|
1098
|
+
if (this._crlDistributionUrl) {
|
|
1099
|
+
setEnv("CDP_URL", this._crlDistributionUrl);
|
|
1100
|
+
}
|
|
1101
|
+
const aiaLegs = [];
|
|
1102
|
+
if (this._ocspResponderUrl) {
|
|
1103
|
+
aiaLegs.push(`OCSP;URI:${this._ocspResponderUrl}`);
|
|
1104
|
+
}
|
|
1105
|
+
if (this._caIssuersUrl) {
|
|
1106
|
+
aiaLegs.push(`caIssuers;URI:${this._caIssuersUrl}`);
|
|
1107
|
+
}
|
|
1108
|
+
if (aiaLegs.length > 0) {
|
|
1109
|
+
setEnv("AIA_VALUE", aiaLegs.join(","));
|
|
1110
|
+
}
|
|
976
1111
|
}
|
|
977
1112
|
/** Absolute path to the CA root directory (alias for {@link location}). */
|
|
978
1113
|
get rootDir() {
|
|
@@ -1575,6 +1710,7 @@ var CertificateAuthority = class {
|
|
|
1575
1710
|
async signCACertificateRequest(certFile, csrFile, params) {
|
|
1576
1711
|
const caRootDir = path5.resolve(this.rootDir);
|
|
1577
1712
|
const options = { cwd: caRootDir };
|
|
1713
|
+
this._wireRevocationEnvVars();
|
|
1578
1714
|
const configFile = generateStaticConfig("conf/caconfig.cnf", options);
|
|
1579
1715
|
const validity = params.validity ?? 3650;
|
|
1580
1716
|
await execute_openssl(
|
|
@@ -1741,6 +1877,7 @@ var CertificateAuthority = class {
|
|
|
1741
1877
|
ip
|
|
1742
1878
|
};
|
|
1743
1879
|
processAltNames(params);
|
|
1880
|
+
this._wireRevocationEnvVars();
|
|
1744
1881
|
const configFile = generateStaticConfig("conf/caconfig.cnf", options);
|
|
1745
1882
|
displaySubtitle("- then we ask the authority to sign the certificate signing request");
|
|
1746
1883
|
const configOption = ` -config ${configFile}`;
|