node-opcua-pki 6.14.0 → 6.16.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 +195 -20
- package/dist/bin/pki.mjs.map +1 -1
- package/dist/index.d.mts +152 -1
- package/dist/index.d.ts +152 -1
- package/dist/index.js +195 -20
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +195 -20
- package/dist/index.mjs.map +1 -1
- package/package.json +2 -2
package/dist/index.js
CHANGED
|
@@ -77,9 +77,17 @@ function adjustDate(params) {
|
|
|
77
77
|
(0, import_node_assert.default)(params instanceof Object);
|
|
78
78
|
params.startDate = params.startDate || /* @__PURE__ */ new Date();
|
|
79
79
|
(0, import_node_assert.default)(params.startDate instanceof Date);
|
|
80
|
-
params.
|
|
81
|
-
|
|
82
|
-
|
|
80
|
+
if (params.validityMs !== void 0) {
|
|
81
|
+
if (params.validityMs <= 0) {
|
|
82
|
+
throw new RangeError(`validityMs must be > 0 (got ${params.validityMs})`);
|
|
83
|
+
}
|
|
84
|
+
params.endDate = new Date(params.startDate.getTime() + params.validityMs);
|
|
85
|
+
params.validity = Math.ceil(params.validityMs / 864e5);
|
|
86
|
+
} else {
|
|
87
|
+
params.validity = params.validity || 365;
|
|
88
|
+
params.endDate = new Date(params.startDate.getTime());
|
|
89
|
+
params.endDate.setDate(params.startDate.getDate() + params.validity);
|
|
90
|
+
}
|
|
83
91
|
(0, import_node_assert.default)(params.endDate instanceof Date);
|
|
84
92
|
(0, import_node_assert.default)(params.startDate instanceof Date);
|
|
85
93
|
}
|
|
@@ -166,9 +174,15 @@ function setEnv(varName, value) {
|
|
|
166
174
|
process.env[varName] = value;
|
|
167
175
|
}
|
|
168
176
|
}
|
|
177
|
+
function hasEnv(varName) {
|
|
178
|
+
return Object.prototype.hasOwnProperty.call(exportedEnvVars, varName);
|
|
179
|
+
}
|
|
169
180
|
function getEnv(varName) {
|
|
170
181
|
return exportedEnvVars[varName];
|
|
171
182
|
}
|
|
183
|
+
function unsetEnv(varName) {
|
|
184
|
+
delete exportedEnvVars[varName];
|
|
185
|
+
}
|
|
172
186
|
function getEnvironmentVarNames() {
|
|
173
187
|
return Object.keys(exportedEnvVars).map((varName) => {
|
|
174
188
|
return { key: varName, pattern: `\\$ENV\\:\\:${varName}` };
|
|
@@ -685,10 +699,17 @@ function openssl_require2DigitYearInDate() {
|
|
|
685
699
|
}
|
|
686
700
|
g_config.opensslVersion = "";
|
|
687
701
|
var _counter = 0;
|
|
702
|
+
function stripConditionalBlocks(template) {
|
|
703
|
+
return template.replace(/\{\{#([A-Z_][A-Z0-9_]*)\}\}([\s\S]*?)\{\{\/\1\}\}\r?\n?/g, (_match, key, content) => {
|
|
704
|
+
const keep = hasEnv(key) && getEnv(key) !== "";
|
|
705
|
+
return keep ? content : "";
|
|
706
|
+
});
|
|
707
|
+
}
|
|
688
708
|
function generateStaticConfig(configPath, options) {
|
|
689
709
|
const prePath = options?.cwd || "";
|
|
690
710
|
const originalFilename = !import_node_path3.default.isAbsolute(configPath) ? import_node_path3.default.join(prePath, configPath) : configPath;
|
|
691
711
|
let staticConfig = import_node_fs5.default.readFileSync(originalFilename, { encoding: "utf8" });
|
|
712
|
+
staticConfig = stripConditionalBlocks(staticConfig);
|
|
692
713
|
for (const envVar of getEnvironmentVarNames()) {
|
|
693
714
|
staticConfig = staticConfig.replace(new RegExp(envVar.pattern, "gi"), getEnv(envVar.key));
|
|
694
715
|
}
|
|
@@ -843,7 +864,9 @@ nsComment = ''OpenSSL Generated Certificate''
|
|
|
843
864
|
#nsSslServerName =
|
|
844
865
|
keyUsage = critical, digitalSignature, nonRepudiation, keyEncipherment, dataEncipherment, keyAgreement
|
|
845
866
|
extendedKeyUsage = critical,serverAuth ,clientAuth
|
|
846
|
-
|
|
867
|
+
{{#CDP_URL}}crlDistributionPoints = URI:$ENV::CDP_URL
|
|
868
|
+
{{/CDP_URL}}{{#AIA_VALUE}}authorityInfoAccess = $ENV::AIA_VALUE
|
|
869
|
+
{{/AIA_VALUE}}
|
|
847
870
|
[ v3_req ]
|
|
848
871
|
basicConstraints = critical, CA:FALSE
|
|
849
872
|
keyUsage = nonRepudiation, digitalSignature, keyEncipherment, dataEncipherment, keyAgreement
|
|
@@ -866,10 +889,9 @@ nsComment = "CA Certificate generated by Node-OPCUA Certificate
|
|
|
866
889
|
#nsCertType = sslCA, emailCA
|
|
867
890
|
#issuerAltName = issuer:copy
|
|
868
891
|
#obj = DER:02:03
|
|
869
|
-
crlDistributionPoints =
|
|
870
|
-
|
|
871
|
-
|
|
872
|
-
[ v3_selfsigned]
|
|
892
|
+
{{#CDP_URL}}crlDistributionPoints = URI:$ENV::CDP_URL
|
|
893
|
+
{{/CDP_URL}}{{#AIA_VALUE}}authorityInfoAccess = $ENV::AIA_VALUE
|
|
894
|
+
{{/AIA_VALUE}}[ v3_selfsigned]
|
|
873
895
|
basicConstraints = critical, CA:FALSE
|
|
874
896
|
keyUsage = nonRepudiation, digitalSignature, keyEncipherment, dataEncipherment, keyAgreement
|
|
875
897
|
extendedKeyUsage = critical,serverAuth ,clientAuth
|
|
@@ -952,6 +974,7 @@ async function construct_CertificateAuthority(certificateAuthority) {
|
|
|
952
974
|
const subjectOpt = ` -subj "${subject.toString()}" `;
|
|
953
975
|
const caCommonName = subject.commonName || "NodeOPCUA-CA";
|
|
954
976
|
setEnv("ALTNAME", `URI:urn:${caCommonName}`);
|
|
977
|
+
certificateAuthority._wireRevocationEnvVars();
|
|
955
978
|
const options = { cwd: caRootDir };
|
|
956
979
|
const configFile = generateStaticConfig("conf/caconfig.cnf", options);
|
|
957
980
|
const configOption = ` -config ${q3(n4(configFile))}`;
|
|
@@ -1005,6 +1028,33 @@ function parseOpenSSLDate(dateStr) {
|
|
|
1005
1028
|
const sec = raw.substring(10, 12);
|
|
1006
1029
|
return `${year}-${month}-${day}T${hour}:${min}:${sec}Z`;
|
|
1007
1030
|
}
|
|
1031
|
+
function validateRevocationUrl(url2, fieldName) {
|
|
1032
|
+
if (url2 === void 0) {
|
|
1033
|
+
return void 0;
|
|
1034
|
+
}
|
|
1035
|
+
if (url2 === "") {
|
|
1036
|
+
throw new Error(`${fieldName} must not be empty \u2014 pass undefined to disable the extension`);
|
|
1037
|
+
}
|
|
1038
|
+
let parsed;
|
|
1039
|
+
try {
|
|
1040
|
+
parsed = new URL(url2);
|
|
1041
|
+
} catch {
|
|
1042
|
+
throw new Error(`${fieldName} is not a valid URL: ${url2}`);
|
|
1043
|
+
}
|
|
1044
|
+
if (parsed.protocol !== "http:" && parsed.protocol !== "https:") {
|
|
1045
|
+
throw new Error(`${fieldName} must use http: or https: (got ${parsed.protocol} in ${url2})`);
|
|
1046
|
+
}
|
|
1047
|
+
if (!parsed.pathname || parsed.pathname === "/") {
|
|
1048
|
+
throw new Error(`${fieldName} must include a path component (got ${url2})`);
|
|
1049
|
+
}
|
|
1050
|
+
const isLoopback = parsed.hostname === "localhost" || parsed.hostname === "::1" || parsed.hostname.startsWith("127.");
|
|
1051
|
+
if (isLoopback) {
|
|
1052
|
+
console.warn(
|
|
1053
|
+
`[node-opcua-pki] ${fieldName} points at loopback (${url2}) \u2014 certificates issued with this URL will be unreachable from any other host.`
|
|
1054
|
+
);
|
|
1055
|
+
}
|
|
1056
|
+
return url2;
|
|
1057
|
+
}
|
|
1008
1058
|
var CertificateAuthority = class {
|
|
1009
1059
|
/** RSA key size used when generating the CA private key. */
|
|
1010
1060
|
keySize;
|
|
@@ -1014,6 +1064,10 @@ var CertificateAuthority = class {
|
|
|
1014
1064
|
subject;
|
|
1015
1065
|
/** @internal Parent CA (undefined for root CAs). */
|
|
1016
1066
|
_issuerCA;
|
|
1067
|
+
/** @internal Configured CDP / AIA URLs (US-202). */
|
|
1068
|
+
_crlDistributionUrl;
|
|
1069
|
+
_ocspResponderUrl;
|
|
1070
|
+
_caIssuersUrl;
|
|
1017
1071
|
constructor(options) {
|
|
1018
1072
|
(0, import_node_assert7.default)(Object.prototype.hasOwnProperty.call(options, "location"));
|
|
1019
1073
|
(0, import_node_assert7.default)(Object.prototype.hasOwnProperty.call(options, "keySize"));
|
|
@@ -1021,6 +1075,93 @@ var CertificateAuthority = class {
|
|
|
1021
1075
|
this.keySize = options.keySize || 2048;
|
|
1022
1076
|
this.subject = new import_node_opcua_crypto2.Subject(options.subject || defaultSubject);
|
|
1023
1077
|
this._issuerCA = options.issuerCA;
|
|
1078
|
+
if (options.crlDistributionUrl !== void 0) {
|
|
1079
|
+
this.setCrlDistributionUrl(options.crlDistributionUrl);
|
|
1080
|
+
}
|
|
1081
|
+
if (options.ocspResponderUrl !== void 0) {
|
|
1082
|
+
this.setOcspResponderUrl(options.ocspResponderUrl);
|
|
1083
|
+
}
|
|
1084
|
+
if (options.caIssuersUrl !== void 0) {
|
|
1085
|
+
this.setCaIssuersUrl(options.caIssuersUrl);
|
|
1086
|
+
}
|
|
1087
|
+
}
|
|
1088
|
+
/**
|
|
1089
|
+
* Public URL where the CRL produced by this CA is reachable, or
|
|
1090
|
+
* `undefined` if no CDP extension should be emitted on issued certs.
|
|
1091
|
+
*/
|
|
1092
|
+
get crlDistributionUrl() {
|
|
1093
|
+
return this._crlDistributionUrl;
|
|
1094
|
+
}
|
|
1095
|
+
/**
|
|
1096
|
+
* Public URL of the OCSP responder, or `undefined` if no AIA OCSP
|
|
1097
|
+
* leg should be emitted on issued certs.
|
|
1098
|
+
*/
|
|
1099
|
+
get ocspResponderUrl() {
|
|
1100
|
+
return this._ocspResponderUrl;
|
|
1101
|
+
}
|
|
1102
|
+
/**
|
|
1103
|
+
* Public URL where the issuer's certificate can be fetched, or
|
|
1104
|
+
* `undefined` if no AIA caIssuers leg should be emitted.
|
|
1105
|
+
*/
|
|
1106
|
+
get caIssuersUrl() {
|
|
1107
|
+
return this._caIssuersUrl;
|
|
1108
|
+
}
|
|
1109
|
+
/**
|
|
1110
|
+
* Configure the URL embedded as `crlDistributionPoints` in every
|
|
1111
|
+
* subsequently-issued certificate. Pass `undefined` to disable
|
|
1112
|
+
* the extension entirely. Validated synchronously — throws on
|
|
1113
|
+
* empty string, non-http(s) protocol, missing path. Warns (does
|
|
1114
|
+
* not throw) when the URL points at loopback.
|
|
1115
|
+
*
|
|
1116
|
+
* @see US-202
|
|
1117
|
+
*/
|
|
1118
|
+
setCrlDistributionUrl(url2) {
|
|
1119
|
+
this._crlDistributionUrl = validateRevocationUrl(url2, "crlDistributionUrl");
|
|
1120
|
+
}
|
|
1121
|
+
/**
|
|
1122
|
+
* Configure the OCSP responder URL embedded as the `OCSP` leg of
|
|
1123
|
+
* the `authorityInfoAccess` extension on every subsequently-issued
|
|
1124
|
+
* certificate. Pass `undefined` to disable.
|
|
1125
|
+
*
|
|
1126
|
+
* @see US-202
|
|
1127
|
+
*/
|
|
1128
|
+
setOcspResponderUrl(url2) {
|
|
1129
|
+
this._ocspResponderUrl = validateRevocationUrl(url2, "ocspResponderUrl");
|
|
1130
|
+
}
|
|
1131
|
+
/**
|
|
1132
|
+
* Configure the caIssuers URL embedded as the `caIssuers` leg of
|
|
1133
|
+
* the `authorityInfoAccess` extension on every subsequently-issued
|
|
1134
|
+
* certificate. Pass `undefined` to disable.
|
|
1135
|
+
*
|
|
1136
|
+
* @see US-202
|
|
1137
|
+
*/
|
|
1138
|
+
setCaIssuersUrl(url2) {
|
|
1139
|
+
this._caIssuersUrl = validateRevocationUrl(url2, "caIssuersUrl");
|
|
1140
|
+
}
|
|
1141
|
+
/**
|
|
1142
|
+
* @internal
|
|
1143
|
+
* Populate the OpenSSL config substitution env vars (`CDP_URL` and
|
|
1144
|
+
* `AIA_VALUE`) from the configured URLs, or unset them so the
|
|
1145
|
+
* matching `{{#KEY}}...{{/KEY}}` blocks in the templates are
|
|
1146
|
+
* stripped. MUST be called before every `generateStaticConfig`
|
|
1147
|
+
* invocation that signs a certificate.
|
|
1148
|
+
*/
|
|
1149
|
+
_wireRevocationEnvVars() {
|
|
1150
|
+
unsetEnv("CDP_URL");
|
|
1151
|
+
unsetEnv("AIA_VALUE");
|
|
1152
|
+
if (this._crlDistributionUrl) {
|
|
1153
|
+
setEnv("CDP_URL", this._crlDistributionUrl);
|
|
1154
|
+
}
|
|
1155
|
+
const aiaLegs = [];
|
|
1156
|
+
if (this._ocspResponderUrl) {
|
|
1157
|
+
aiaLegs.push(`OCSP;URI:${this._ocspResponderUrl}`);
|
|
1158
|
+
}
|
|
1159
|
+
if (this._caIssuersUrl) {
|
|
1160
|
+
aiaLegs.push(`caIssuers;URI:${this._caIssuersUrl}`);
|
|
1161
|
+
}
|
|
1162
|
+
if (aiaLegs.length > 0) {
|
|
1163
|
+
setEnv("AIA_VALUE", aiaLegs.join(","));
|
|
1164
|
+
}
|
|
1024
1165
|
}
|
|
1025
1166
|
/** Absolute path to the CA root directory (alias for {@link location}). */
|
|
1026
1167
|
get rootDir() {
|
|
@@ -1267,14 +1408,15 @@ var CertificateAuthority = class {
|
|
|
1267
1408
|
* @returns the signed certificate as a DER-encoded buffer
|
|
1268
1409
|
*/
|
|
1269
1410
|
async signCertificateRequestFromDER(csrDer, options) {
|
|
1270
|
-
const validity = options?.validity ?? 365;
|
|
1271
1411
|
const tmpDir = await import_node_fs7.default.promises.mkdtemp(import_node_path5.default.join(import_node_os3.default.tmpdir(), "pki-sign-"));
|
|
1272
1412
|
try {
|
|
1273
1413
|
const csrFile = import_node_path5.default.join(tmpDir, "request.csr");
|
|
1274
1414
|
const certFile = import_node_path5.default.join(tmpDir, "certificate.pem");
|
|
1275
1415
|
const csrPem = (0, import_node_opcua_crypto2.toPem)(csrDer, "CERTIFICATE REQUEST");
|
|
1276
1416
|
await import_node_fs7.default.promises.writeFile(csrFile, csrPem, "utf-8");
|
|
1277
|
-
const signingParams = {
|
|
1417
|
+
const signingParams = {};
|
|
1418
|
+
if (options?.validityMs !== void 0) signingParams.validityMs = options.validityMs;
|
|
1419
|
+
else signingParams.validity = options?.validity ?? 365;
|
|
1278
1420
|
if (options?.startDate) signingParams.startDate = options.startDate;
|
|
1279
1421
|
if (options?.dns) signingParams.dns = options.dns;
|
|
1280
1422
|
if (options?.ip) signingParams.ip = options.ip;
|
|
@@ -1290,6 +1432,35 @@ var CertificateAuthority = class {
|
|
|
1290
1432
|
});
|
|
1291
1433
|
}
|
|
1292
1434
|
}
|
|
1435
|
+
/**
|
|
1436
|
+
* Advertise the validity limits this CA can honor.
|
|
1437
|
+
*
|
|
1438
|
+
* Consumers (notably the GDS server in [`cert_auth.ts`](https://github.com/sterfive/node-opcua-gds))
|
|
1439
|
+
* clamp a requested validity against these bounds before calling
|
|
1440
|
+
* {@link signCertificateRequestFromDER}, so a misconfigured
|
|
1441
|
+
* `defaultCertValidity` cannot ask the CA for something it cannot
|
|
1442
|
+
* produce.
|
|
1443
|
+
*
|
|
1444
|
+
* Defaults match the OpenSSL-backed implementation:
|
|
1445
|
+
* - `minValidityMs = 60_000` (1 minute) — practical floor; the
|
|
1446
|
+
* X.509 spec floor is 1 second but very short certs are rarely
|
|
1447
|
+
* useful and pathological for any real deployment.
|
|
1448
|
+
* - `maxValidityMs = 10 * 365 * 86_400_000` (≈ 10 years) — long
|
|
1449
|
+
* enough for root CAs.
|
|
1450
|
+
* - `validityGranularityMs = 1_000` (1 second) — RFC 5280 §4.1.2.5
|
|
1451
|
+
* floor on `notBefore` / `notAfter`.
|
|
1452
|
+
* - `nativeUnit = "second"` — what `x509Date()` actually encodes.
|
|
1453
|
+
*
|
|
1454
|
+
* @see US-208 — the consumer-side capability story.
|
|
1455
|
+
*/
|
|
1456
|
+
getCapabilities() {
|
|
1457
|
+
return {
|
|
1458
|
+
minValidityMs: 6e4,
|
|
1459
|
+
maxValidityMs: 10 * 365 * 864e5,
|
|
1460
|
+
validityGranularityMs: 1e3,
|
|
1461
|
+
nativeUnit: "second"
|
|
1462
|
+
};
|
|
1463
|
+
}
|
|
1293
1464
|
/**
|
|
1294
1465
|
* Generate a new RSA key pair, create an internal CSR, sign it
|
|
1295
1466
|
* with this CA, and return both the certificate and private key
|
|
@@ -1307,7 +1478,6 @@ var CertificateAuthority = class {
|
|
|
1307
1478
|
*/
|
|
1308
1479
|
async generateKeyPairAndSignDER(options) {
|
|
1309
1480
|
const keySize = options.keySize ?? 2048;
|
|
1310
|
-
const validity = options.validity ?? 365;
|
|
1311
1481
|
const startDate = options.startDate ?? /* @__PURE__ */ new Date();
|
|
1312
1482
|
const tmpDir = await import_node_fs7.default.promises.mkdtemp(import_node_path5.default.join(import_node_os3.default.tmpdir(), "pki-keygen-"));
|
|
1313
1483
|
try {
|
|
@@ -1327,13 +1497,15 @@ var CertificateAuthority = class {
|
|
|
1327
1497
|
purpose: import_node_opcua_crypto2.CertificatePurpose.ForApplication
|
|
1328
1498
|
});
|
|
1329
1499
|
const certFile = import_node_path5.default.join(tmpDir, "certificate.pem");
|
|
1330
|
-
|
|
1500
|
+
const signingParams = {
|
|
1331
1501
|
applicationUri: options.applicationUri,
|
|
1332
1502
|
dns: options.dns,
|
|
1333
1503
|
ip: options.ip,
|
|
1334
|
-
startDate
|
|
1335
|
-
|
|
1336
|
-
|
|
1504
|
+
startDate
|
|
1505
|
+
};
|
|
1506
|
+
if (options.validityMs !== void 0) signingParams.validityMs = options.validityMs;
|
|
1507
|
+
else signingParams.validity = options.validity ?? 365;
|
|
1508
|
+
await this.signCertificateRequest(certFile, csrFile, signingParams);
|
|
1337
1509
|
const certPem = (0, import_node_opcua_crypto2.readCertificatePEM)(certFile);
|
|
1338
1510
|
const certificateDer = (0, import_node_opcua_crypto2.convertPEMtoDER)(certPem);
|
|
1339
1511
|
const privateKey = (0, import_node_opcua_crypto2.readPrivateKey)(privateKeyFile);
|
|
@@ -1358,7 +1530,6 @@ var CertificateAuthority = class {
|
|
|
1358
1530
|
*/
|
|
1359
1531
|
async generateKeyPairAndSignPFX(options) {
|
|
1360
1532
|
const keySize = options.keySize ?? 2048;
|
|
1361
|
-
const validity = options.validity ?? 365;
|
|
1362
1533
|
const startDate = options.startDate ?? /* @__PURE__ */ new Date();
|
|
1363
1534
|
const passphrase = options.passphrase ?? "";
|
|
1364
1535
|
const tmpDir = await import_node_fs7.default.promises.mkdtemp(import_node_path5.default.join(import_node_os3.default.tmpdir(), "pki-keygen-pfx-"));
|
|
@@ -1379,13 +1550,15 @@ var CertificateAuthority = class {
|
|
|
1379
1550
|
purpose: import_node_opcua_crypto2.CertificatePurpose.ForApplication
|
|
1380
1551
|
});
|
|
1381
1552
|
const certFile = import_node_path5.default.join(tmpDir, "certificate.pem");
|
|
1382
|
-
|
|
1553
|
+
const signingParams = {
|
|
1383
1554
|
applicationUri: options.applicationUri,
|
|
1384
1555
|
dns: options.dns,
|
|
1385
1556
|
ip: options.ip,
|
|
1386
|
-
startDate
|
|
1387
|
-
|
|
1388
|
-
|
|
1557
|
+
startDate
|
|
1558
|
+
};
|
|
1559
|
+
if (options.validityMs !== void 0) signingParams.validityMs = options.validityMs;
|
|
1560
|
+
else signingParams.validity = options.validity ?? 365;
|
|
1561
|
+
await this.signCertificateRequest(certFile, csrFile, signingParams);
|
|
1389
1562
|
const pfxFile = import_node_path5.default.join(tmpDir, "bundle.pfx");
|
|
1390
1563
|
await createPFX({
|
|
1391
1564
|
certificateFile: certFile,
|
|
@@ -1623,6 +1796,7 @@ var CertificateAuthority = class {
|
|
|
1623
1796
|
async signCACertificateRequest(certFile, csrFile, params) {
|
|
1624
1797
|
const caRootDir = import_node_path5.default.resolve(this.rootDir);
|
|
1625
1798
|
const options = { cwd: caRootDir };
|
|
1799
|
+
this._wireRevocationEnvVars();
|
|
1626
1800
|
const configFile = generateStaticConfig("conf/caconfig.cnf", options);
|
|
1627
1801
|
const validity = params.validity ?? 3650;
|
|
1628
1802
|
await execute_openssl(
|
|
@@ -1789,6 +1963,7 @@ var CertificateAuthority = class {
|
|
|
1789
1963
|
ip
|
|
1790
1964
|
};
|
|
1791
1965
|
processAltNames(params);
|
|
1966
|
+
this._wireRevocationEnvVars();
|
|
1792
1967
|
const configFile = generateStaticConfig("conf/caconfig.cnf", options);
|
|
1793
1968
|
displaySubtitle("- then we ask the authority to sign the certificate signing request");
|
|
1794
1969
|
const configOption = ` -config ${configFile}`;
|