node-opcua-pki 6.19.0 → 6.20.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 CHANGED
@@ -91,6 +91,45 @@ var init_hostname = __esm({
91
91
  }
92
92
  });
93
93
 
94
+ // packages/node-opcua-pki/lib/toolbox/common.ts
95
+ import assert2 from "assert";
96
+ async function resolvePrivateKeyPassphrase(passphrase) {
97
+ if (passphrase === void 0) {
98
+ return void 0;
99
+ }
100
+ return typeof passphrase === "function" ? await passphrase() : passphrase;
101
+ }
102
+ function adjustDate(params) {
103
+ assert2(params instanceof Object);
104
+ params.startDate = params.startDate || /* @__PURE__ */ new Date();
105
+ assert2(params.startDate instanceof Date);
106
+ if (params.validityMs !== void 0) {
107
+ if (params.validityMs <= 0) {
108
+ throw new RangeError(`validityMs must be > 0 (got ${params.validityMs})`);
109
+ }
110
+ params.endDate = new Date(params.startDate.getTime() + params.validityMs);
111
+ params.validity = Math.ceil(params.validityMs / 864e5);
112
+ } else {
113
+ params.validity = params.validity || 365;
114
+ params.endDate = new Date(params.startDate.getTime());
115
+ params.endDate.setDate(params.startDate.getDate() + params.validity);
116
+ }
117
+ assert2(params.endDate instanceof Date);
118
+ assert2(params.startDate instanceof Date);
119
+ }
120
+ function adjustApplicationUri(params) {
121
+ const applicationUri = params.applicationUri || "";
122
+ if (applicationUri.length > 200) {
123
+ throw new Error(`Openssl doesn't support urn with length greater than 200${applicationUri}`);
124
+ }
125
+ }
126
+ var init_common = __esm({
127
+ "packages/node-opcua-pki/lib/toolbox/common.ts"() {
128
+ "use strict";
129
+ init_esm_shims();
130
+ }
131
+ });
132
+
94
133
  // packages/node-opcua-pki/lib/toolbox/config.ts
95
134
  var g_config;
96
135
  var init_config = __esm({
@@ -126,7 +165,7 @@ var init_debug = __esm({
126
165
  });
127
166
 
128
167
  // packages/node-opcua-pki/lib/toolbox/common2.ts
129
- import assert2 from "assert";
168
+ import assert3 from "assert";
130
169
  import fs from "fs";
131
170
  import path from "path";
132
171
  import chalk from "chalk";
@@ -145,12 +184,31 @@ function mkdirRecursiveSync(folder) {
145
184
  fs.mkdirSync(folder, { recursive: true });
146
185
  }
147
186
  }
187
+ function restrictPrivateFilePermissions(target, mode) {
188
+ if (process.platform === "win32") {
189
+ return;
190
+ }
191
+ try {
192
+ fs.chmodSync(target, mode);
193
+ } catch (err) {
194
+ warningLog(chalk.yellow(" could not restrict permissions on "), target, err.message);
195
+ }
196
+ }
197
+ function ensurePrivateDirectory(dir) {
198
+ if (!fs.existsSync(dir)) {
199
+ fs.mkdirSync(dir, { recursive: true, mode: 448 });
200
+ }
201
+ restrictPrivateFilePermissions(dir, 448);
202
+ }
203
+ function isEncryptedPrivateKeyFile(filename) {
204
+ return fs.readFileSync(filename, "utf-8").includes("-----BEGIN ENCRYPTED PRIVATE KEY-----");
205
+ }
148
206
  function makePath(folderName, filename) {
149
207
  let s;
150
208
  if (filename) {
151
209
  s = path.join(path.normalize(folderName), filename);
152
210
  } else {
153
- assert2(folderName);
211
+ assert3(folderName);
154
212
  s = folderName;
155
213
  }
156
214
  s = s.replace(/\\/g, "/");
@@ -203,21 +261,22 @@ var init_display = __esm({
203
261
  });
204
262
 
205
263
  // packages/node-opcua-pki/lib/toolbox/without_openssl/create_certificate_signing_request.ts
206
- import assert3 from "assert";
264
+ import assert4 from "assert";
207
265
  import fs2 from "fs";
208
- import { createCertificateSigningRequest, pemToPrivateKey, Subject } from "node-opcua-crypto";
266
+ import { coercePrivateKeyPem, createCertificateSigningRequest, pemToPrivateKey, Subject } from "node-opcua-crypto";
209
267
  async function createCertificateSigningRequestAsync(certificateSigningRequestFilename, params) {
210
- assert3(params);
211
- assert3(params.rootDir);
212
- assert3(params.configFile);
213
- assert3(params.privateKey);
214
- assert3(typeof params.privateKey === "string");
215
- assert3(fs2.existsSync(params.privateKey), `Private key must exist${params.privateKey}`);
216
- assert3(fs2.existsSync(params.rootDir), "RootDir key must exist");
217
- assert3(typeof certificateSigningRequestFilename === "string");
268
+ assert4(params);
269
+ assert4(params.rootDir);
270
+ assert4(params.configFile);
271
+ assert4(params.privateKey);
272
+ if (typeof params.privateKey === "string") {
273
+ assert4(fs2.existsSync(params.privateKey), `Private key must exist${params.privateKey}`);
274
+ }
275
+ assert4(fs2.existsSync(params.rootDir), "RootDir key must exist");
276
+ assert4(typeof certificateSigningRequestFilename === "string");
218
277
  const subject = params.subject ? new Subject(params.subject).toString() : void 0;
219
278
  displaySubtitle("- Creating a Certificate Signing Request with subtile");
220
- const privateKeyPem = await fs2.promises.readFile(params.privateKey, "utf-8");
279
+ const privateKeyPem = typeof params.privateKey === "string" ? await fs2.promises.readFile(params.privateKey, "utf-8") : coercePrivateKeyPem(params.privateKey);
221
280
  const privateKey = await pemToPrivateKey(privateKeyPem);
222
281
  const { csr } = await createCertificateSigningRequest({
223
282
  privateKey,
@@ -228,7 +287,7 @@ async function createCertificateSigningRequestAsync(certificateSigningRequestFil
228
287
  purpose: params.purpose
229
288
  });
230
289
  await fs2.promises.writeFile(certificateSigningRequestFilename, csr, "utf-8");
231
- display(`- privateKey ${params.privateKey}`);
290
+ display(`- privateKey ${typeof params.privateKey === "string" ? params.privateKey : "<in-memory>"}`);
232
291
  display(`- certificateSigningRequestFilename ${certificateSigningRequestFilename}`);
233
292
  }
234
293
  var init_create_certificate_signing_request = __esm({
@@ -239,47 +298,12 @@ var init_create_certificate_signing_request = __esm({
239
298
  }
240
299
  });
241
300
 
242
- // packages/node-opcua-pki/lib/toolbox/common.ts
243
- import assert4 from "assert";
244
- function quote(str) {
245
- return `"${str || ""}"`;
246
- }
247
- function adjustDate(params) {
248
- assert4(params instanceof Object);
249
- params.startDate = params.startDate || /* @__PURE__ */ new Date();
250
- assert4(params.startDate instanceof Date);
251
- if (params.validityMs !== void 0) {
252
- if (params.validityMs <= 0) {
253
- throw new RangeError(`validityMs must be > 0 (got ${params.validityMs})`);
254
- }
255
- params.endDate = new Date(params.startDate.getTime() + params.validityMs);
256
- params.validity = Math.ceil(params.validityMs / 864e5);
257
- } else {
258
- params.validity = params.validity || 365;
259
- params.endDate = new Date(params.startDate.getTime());
260
- params.endDate.setDate(params.startDate.getDate() + params.validity);
261
- }
262
- assert4(params.endDate instanceof Date);
263
- assert4(params.startDate instanceof Date);
264
- }
265
- function adjustApplicationUri(params) {
266
- const applicationUri = params.applicationUri || "";
267
- if (applicationUri.length > 200) {
268
- throw new Error(`Openssl doesn't support urn with length greater than 200${applicationUri}`);
269
- }
270
- }
271
- var init_common = __esm({
272
- "packages/node-opcua-pki/lib/toolbox/common.ts"() {
273
- "use strict";
274
- init_esm_shims();
275
- }
276
- });
277
-
278
301
  // packages/node-opcua-pki/lib/toolbox/without_openssl/create_self_signed_certificate.ts
279
302
  import assert5 from "assert";
280
303
  import fs3 from "fs";
281
304
  import {
282
305
  CertificatePurpose,
306
+ coercePrivateKeyPem as coercePrivateKeyPem2,
283
307
  createSelfSignedCertificate as createSelfSignedCertificate1,
284
308
  pemToPrivateKey as pemToPrivateKey2,
285
309
  Subject as Subject2
@@ -289,7 +313,9 @@ async function createSelfSignedCertificateAsync(certificate, params) {
289
313
  assert5(params.purpose, "Please provide a Certificate Purpose");
290
314
  assert5(fs3.existsSync(params.configFile));
291
315
  assert5(fs3.existsSync(params.rootDir));
292
- assert5(fs3.existsSync(params.privateKey));
316
+ if (typeof params.privateKey === "string") {
317
+ assert5(fs3.existsSync(params.privateKey));
318
+ }
293
319
  if (!params.subject) {
294
320
  throw Error("Missing subject");
295
321
  }
@@ -301,7 +327,7 @@ async function createSelfSignedCertificateAsync(certificate, params) {
301
327
  subject = subject.toString();
302
328
  const purpose = params.purpose;
303
329
  displayTitle("Generate a certificate request");
304
- const privateKeyPem = await fs3.promises.readFile(params.privateKey, "utf-8");
330
+ const privateKeyPem = typeof params.privateKey === "string" ? await fs3.promises.readFile(params.privateKey, "utf-8") : coercePrivateKeyPem2(params.privateKey);
305
331
  const privateKey = await pemToPrivateKey2(privateKeyPem);
306
332
  const { cert } = await createSelfSignedCertificate1({
307
333
  privateKey,
@@ -365,9 +391,11 @@ import {
365
391
  readCertificateChain,
366
392
  readCertificateChainAsync,
367
393
  readCertificateRevocationList,
394
+ readPrivateKey,
368
395
  split_der,
369
396
  toPem,
370
- verifyCertificateSignature
397
+ verifyCertificateSignature,
398
+ writePrivateKeyFile
371
399
  } from "node-opcua-crypto";
372
400
  function getOrComputeInfo(entry) {
373
401
  if (!entry.info) {
@@ -468,6 +496,7 @@ var init_certificate_manager = __esm({
468
496
  "packages/node-opcua-pki/lib/pki/certificate_manager.ts"() {
469
497
  "use strict";
470
498
  init_esm_shims();
499
+ init_common();
471
500
  init_common2();
472
501
  init_debug();
473
502
  init_without_openssl();
@@ -576,6 +605,15 @@ var init_certificate_manager = __esm({
576
605
  #initializingPromise;
577
606
  #addCertValidation;
578
607
  #disableFileWatchers;
608
+ #privateKeyPassphrase;
609
+ #privateKeyProvider;
610
+ /**
611
+ * The on-disk key, decrypted once and kept for the instance's lifetime,
612
+ * so the passphrase (or its resolver function) is consulted at most
613
+ * once. Cleared by `dispose()`. Not used when a provider is configured:
614
+ * the provider is the authority on the current key.
615
+ */
616
+ #cachedPrivateKey;
579
617
  #thumbs = {
580
618
  rejected: /* @__PURE__ */ new Map(),
581
619
  trusted: /* @__PURE__ */ new Map(),
@@ -610,6 +648,8 @@ var init_certificate_manager = __esm({
610
648
  maxChainLength: v.maxChainLength ?? 5
611
649
  };
612
650
  this.#disableFileWatchers = options.disableFileWatchers ?? process.env.OPCUA_PKI_DISABLE_FILE_WATCHERS === "true";
651
+ this.#privateKeyPassphrase = options.privateKeyPassphrase;
652
+ this.#privateKeyProvider = options.privateKeyProvider;
613
653
  mkdirRecursiveSync(options.location);
614
654
  if (!fs4.existsSync(this.#location)) {
615
655
  throw new Error(`CertificateManager cannot access location ${this.#location}`);
@@ -623,10 +663,108 @@ var init_certificate_manager = __esm({
623
663
  get rootDir() {
624
664
  return this.#location;
625
665
  }
626
- /** Path to the private key file (`own/private/private_key.pem`). */
666
+ /**
667
+ * Path to the private key file (`own/private/private_key.pem`).
668
+ *
669
+ * Kept for backward compatibility with code that reads the key
670
+ * directly from disk. When a passphrase or a `privateKeyProvider` is
671
+ * configured, prefer {@link getPrivateKey} instead — this getter still
672
+ * returns the on-disk path even if a provider is configured (there may
673
+ * be no meaningful file in that case).
674
+ */
627
675
  get privateKey() {
628
676
  return path2.join(this.rootDir, "own/private/private_key.pem");
629
677
  }
678
+ /**
679
+ * Resolve the private key: from `privateKeyProvider` if configured,
680
+ * otherwise from disk (decrypting with `privateKeyPassphrase` if the
681
+ * key is encrypted). Fails closed — throws
682
+ * `PrivateKeyPassphraseRequiredError` — if the on-disk key is encrypted
683
+ * and no passphrase is configured, or if the wrong passphrase is
684
+ * configured.
685
+ *
686
+ * The on-disk key is read and decrypted once and then cached for the
687
+ * lifetime of this instance, so a `privateKeyPassphrase` function is
688
+ * called at most once (concurrent first calls share the same read). A
689
+ * failed read is not cached, so a caller can fix the passphrase and
690
+ * retry. A `privateKeyProvider` is consulted on every call: it is the
691
+ * authority on what the current key is.
692
+ */
693
+ async getPrivateKey() {
694
+ if (this.#privateKeyProvider) {
695
+ return await this.#privateKeyProvider.getPrivateKey();
696
+ }
697
+ if (this.#cachedPrivateKey) {
698
+ return this.#cachedPrivateKey;
699
+ }
700
+ if (!this.#privateKeyPromise) {
701
+ this.#privateKeyPromise = (async () => {
702
+ const passphrase = await resolvePrivateKeyPassphrase(this.#privateKeyPassphrase);
703
+ return readPrivateKey(this.privateKey, passphrase);
704
+ })().then(
705
+ (key) => {
706
+ this.#cachedPrivateKey = key;
707
+ return key;
708
+ },
709
+ (err) => {
710
+ this.#privateKeyPromise = void 0;
711
+ throw err;
712
+ }
713
+ );
714
+ }
715
+ return await this.#privateKeyPromise;
716
+ }
717
+ /** In-flight first read of the on-disk key, so concurrent callers share one passphrase resolution. */
718
+ #privateKeyPromise;
719
+ /**
720
+ * Enable, disable, or rotate the passphrase protecting the on-disk
721
+ * private key: decrypt with `oldPassphrase` (omit if the key is
722
+ * currently unencrypted), then write back encrypted with
723
+ * `newPassphrase` (omit to leave it unencrypted). The write goes to a
724
+ * temporary file in the same directory and is atomically renamed into
725
+ * place, so a crash mid-rotation cannot leave a partially-written key;
726
+ * the temporary file is removed if anything fails, so a rotation *to*
727
+ * plaintext can never leave a stray cleartext copy behind. Runs under
728
+ * the same lock as `initialize()`.
729
+ *
730
+ * This only rewrites the on-disk file — it does not update this
731
+ * instance's own `privateKeyPassphrase` (set at construction), and it
732
+ * drops this instance's cached key so that disk stays the source of
733
+ * truth. Construct a new `CertificateManager` with the new passphrase to
734
+ * continue using it afterward.
735
+ *
736
+ * Not supported when a `privateKeyProvider` is configured (there is no
737
+ * disk file for this method to rewrite).
738
+ */
739
+ async reencryptPrivateKey(oldPassphrase, newPassphrase) {
740
+ if (this.#privateKeyProvider) {
741
+ throw new Error("reencryptPrivateKey: not supported when a privateKeyProvider is configured");
742
+ }
743
+ const oldPass = await resolvePrivateKeyPassphrase(oldPassphrase);
744
+ const newPass = await resolvePrivateKeyPassphrase(newPassphrase);
745
+ await this.withLock2(async () => {
746
+ const privateKey = readPrivateKey(this.privateKey, oldPass);
747
+ await this.#rewritePrivateKeyFile(privateKey, newPass);
748
+ });
749
+ this.#cachedPrivateKey = void 0;
750
+ this.#privateKeyPromise = void 0;
751
+ }
752
+ /**
753
+ * Atomically replace the on-disk private key with `privateKey`, written
754
+ * as PKCS#8 (encrypted with `passphrase` if given). Temp file next to the
755
+ * target, `0600`, renamed into place; the temp file is unlinked on any
756
+ * failure so no partial or cleartext copy can be left behind.
757
+ * Caller must hold the lock.
758
+ */
759
+ async #rewritePrivateKeyFile(privateKey, passphrase) {
760
+ const tmpFilename = `${this.privateKey}.${process.pid}-${Date.now()}.tmp`;
761
+ try {
762
+ await writePrivateKeyFile(tmpFilename, privateKey, { passphrase });
763
+ await fs4.promises.rename(tmpFilename, this.privateKey);
764
+ } finally {
765
+ await fs4.promises.rm(tmpFilename, { force: true });
766
+ }
767
+ }
630
768
  /** Path to the OpenSSL random seed file. */
631
769
  get randomFile() {
632
770
  return path2.join(this.rootDir, "./random.rnd");
@@ -926,19 +1064,24 @@ var init_certificate_manager = __esm({
926
1064
  }
927
1065
  this.state = 1 /* Initializing */;
928
1066
  this.#initializingPromise = this.#initialize();
929
- await this.#initializingPromise;
1067
+ try {
1068
+ await this.#initializingPromise;
1069
+ } catch (err) {
1070
+ this.#initializingPromise = void 0;
1071
+ this.state = 0 /* Uninitialized */;
1072
+ throw err;
1073
+ }
930
1074
  this.#initializingPromise = void 0;
931
1075
  this.state = 2 /* Initialized */;
932
1076
  _CertificateManager.#activeInstances.add(this);
933
1077
  _CertificateManager.#installProcessCleanup();
934
1078
  }
935
1079
  async #initialize() {
936
- this.state = 1 /* Initializing */;
937
1080
  const pkiDir = this.#location;
938
1081
  mkdirRecursiveSync(pkiDir);
939
1082
  mkdirRecursiveSync(path2.join(pkiDir, "own"));
940
1083
  mkdirRecursiveSync(path2.join(pkiDir, "own/certs"));
941
- mkdirRecursiveSync(path2.join(pkiDir, "own/private"));
1084
+ ensurePrivateDirectory(path2.join(pkiDir, "own/private"));
942
1085
  mkdirRecursiveSync(path2.join(pkiDir, "rejected"));
943
1086
  mkdirRecursiveSync(path2.join(pkiDir, "trusted"));
944
1087
  mkdirRecursiveSync(path2.join(pkiDir, "trusted/certs"));
@@ -946,7 +1089,10 @@ var init_certificate_manager = __esm({
946
1089
  mkdirRecursiveSync(path2.join(pkiDir, "issuers"));
947
1090
  mkdirRecursiveSync(path2.join(pkiDir, "issuers/certs"));
948
1091
  mkdirRecursiveSync(path2.join(pkiDir, "issuers/crl"));
949
- if (!fs4.existsSync(this.configFile) || !fs4.existsSync(this.privateKey)) {
1092
+ const ownsDiskKey = !this.#privateKeyProvider;
1093
+ const needsKeyGeneration = ownsDiskKey && !fs4.existsSync(this.privateKey);
1094
+ const needsKeyEncryption = ownsDiskKey && !needsKeyGeneration && this.#privateKeyPassphrase !== void 0 && !isEncryptedPrivateKeyFile(this.privateKey);
1095
+ if (!fs4.existsSync(this.configFile) || needsKeyGeneration || needsKeyEncryption) {
950
1096
  return await this.withLock2(async () => {
951
1097
  if (this.state === 3 /* Disposing */ || this.state === 4 /* Disposed */) {
952
1098
  return;
@@ -954,15 +1100,29 @@ var init_certificate_manager = __esm({
954
1100
  if (!fs4.existsSync(this.configFile)) {
955
1101
  fs4.writeFileSync(this.configFile, configurationFileSimpleTemplate);
956
1102
  }
957
- if (!fs4.existsSync(this.privateKey)) {
1103
+ if (ownsDiskKey && !fs4.existsSync(this.privateKey)) {
958
1104
  debugLog("generating private key ...");
959
- await generatePrivateKeyFile(this.privateKey, this.keySize);
960
- await this.#readCertificates();
961
- } else {
962
- await this.#readCertificates();
1105
+ const passphrase = await resolvePrivateKeyPassphrase(this.#privateKeyPassphrase);
1106
+ await generatePrivateKeyFile(this.privateKey, this.keySize, { passphrase });
1107
+ this.#cachedPrivateKey = readPrivateKey(this.privateKey, passphrase);
1108
+ } else if (ownsDiskKey && this.#privateKeyPassphrase !== void 0 && !isEncryptedPrivateKeyFile(this.privateKey)) {
1109
+ warningLog("initialize: private key is plaintext but a passphrase is configured; encrypting it in place");
1110
+ const passphrase = await resolvePrivateKeyPassphrase(this.#privateKeyPassphrase);
1111
+ const plaintextKey = readPrivateKey(this.privateKey);
1112
+ await this.#rewritePrivateKeyFile(plaintextKey, passphrase);
1113
+ this.#cachedPrivateKey = plaintextKey;
1114
+ }
1115
+ if (ownsDiskKey) {
1116
+ restrictPrivateFilePermissions(this.privateKey, 384);
963
1117
  }
1118
+ await this.getPrivateKey();
1119
+ await this.#readCertificates();
964
1120
  });
965
1121
  } else {
1122
+ if (ownsDiskKey) {
1123
+ restrictPrivateFilePermissions(this.privateKey, 384);
1124
+ }
1125
+ await this.getPrivateKey();
966
1126
  await this.#readCertificates();
967
1127
  }
968
1128
  }
@@ -998,6 +1158,8 @@ var init_certificate_manager = __esm({
998
1158
  this.#watchers.splice(0);
999
1159
  } finally {
1000
1160
  this.state = 4 /* Disposed */;
1161
+ this.#cachedPrivateKey = void 0;
1162
+ this.#privateKeyPromise = void 0;
1001
1163
  _CertificateManager.#activeInstances.delete(this);
1002
1164
  }
1003
1165
  }
@@ -1044,16 +1206,18 @@ var init_certificate_manager = __esm({
1044
1206
  if (typeof params.applicationUri !== "string") {
1045
1207
  throw new Error("createSelfSignedCertificate: expecting applicationUri to be a string");
1046
1208
  }
1047
- if (!fs4.existsSync(this.privateKey)) {
1209
+ if (!this.#privateKeyProvider && !fs4.existsSync(this.privateKey)) {
1048
1210
  throw new Error(`Cannot find private key ${this.privateKey}`);
1049
1211
  }
1050
1212
  let certificateFilename = path2.join(this.rootDir, "own/certs/self_signed_certificate.pem");
1051
1213
  certificateFilename = params.outputFile || certificateFilename;
1052
- const _params = params;
1053
- _params.rootDir = this.rootDir;
1054
- _params.configFile = this.configFile;
1055
- _params.privateKey = this.privateKey;
1056
- _params.subject = params.subject || "CN=FIXME";
1214
+ const _params = {
1215
+ ...params,
1216
+ rootDir: this.rootDir,
1217
+ configFile: this.configFile,
1218
+ privateKey: await this.getPrivateKey(),
1219
+ subject: params.subject || "CN=FIXME"
1220
+ };
1057
1221
  await this.withLock2(async () => {
1058
1222
  await createSelfSignedCertificate(certificateFilename, _params);
1059
1223
  });
@@ -1072,13 +1236,15 @@ var init_certificate_manager = __esm({
1072
1236
  if (!params) {
1073
1237
  throw new Error("params is required");
1074
1238
  }
1075
- const _params = params;
1076
- if (Object.prototype.hasOwnProperty.call(_params, "rootDir")) {
1239
+ if (Object.prototype.hasOwnProperty.call(params, "rootDir")) {
1077
1240
  throw new Error("rootDir should not be specified ");
1078
1241
  }
1079
- _params.rootDir = path2.resolve(this.rootDir);
1080
- _params.configFile = path2.resolve(this.configFile);
1081
- _params.privateKey = path2.resolve(this.privateKey);
1242
+ const _params = {
1243
+ ...params,
1244
+ rootDir: path2.resolve(this.rootDir),
1245
+ configFile: path2.resolve(this.configFile),
1246
+ privateKey: await this.getPrivateKey()
1247
+ };
1082
1248
  return await this.withLock2(async () => {
1083
1249
  const now = /* @__PURE__ */ new Date();
1084
1250
  const today2 = `${now.toISOString().slice(0, 10)}_${now.getTime()}`;
@@ -1940,6 +2106,19 @@ var init_toolbox = __esm({
1940
2106
  });
1941
2107
 
1942
2108
  // packages/node-opcua-pki/lib/toolbox/with_openssl/_env.ts
2109
+ function buildChildEnv(extra) {
2110
+ const env = {};
2111
+ for (const key of Object.keys(process.env)) {
2112
+ if (SAFE_ENV_PASSTHROUGH.has(key.toLowerCase())) {
2113
+ env[key] = process.env[key];
2114
+ }
2115
+ }
2116
+ return { ...env, ...extra };
2117
+ }
2118
+ function redactEnvForLog(options) {
2119
+ const { env, ...rest } = options;
2120
+ return env ? { ...rest, env: Object.keys(env) } : rest;
2121
+ }
1943
2122
  function setEnv(varName, value) {
1944
2123
  if (!g_config.silent) {
1945
2124
  warningLog(` set ${varName}=${value}`);
@@ -1982,13 +2161,44 @@ function processAltNames(params) {
1982
2161
  const subjectAltNameString = subjectAltName.join(", ");
1983
2162
  setEnv("ALTNAME", subjectAltNameString);
1984
2163
  }
1985
- var exportedEnvVars;
2164
+ var SAFE_ENV_PASSTHROUGH, exportedEnvVars;
1986
2165
  var init_env = __esm({
1987
2166
  "packages/node-opcua-pki/lib/toolbox/with_openssl/_env.ts"() {
1988
2167
  "use strict";
1989
2168
  init_esm_shims();
1990
2169
  init_config();
1991
2170
  init_debug();
2171
+ SAFE_ENV_PASSTHROUGH = /* @__PURE__ */ new Set([
2172
+ // POSIX/Windows shell and process essentials
2173
+ "path",
2174
+ "home",
2175
+ "userprofile",
2176
+ "temp",
2177
+ "tmp",
2178
+ "tmpdir",
2179
+ "systemroot",
2180
+ "windir",
2181
+ "comspec",
2182
+ "pathext",
2183
+ "appdata",
2184
+ "localappdata",
2185
+ // dynamic loader: a custom-built or relocated openssl (e.g. under /opt,
2186
+ // or Homebrew on macOS) may need these to find its own libcrypto/libssl
2187
+ "ld_library_path",
2188
+ "dyld_library_path",
2189
+ "dyld_fallback_library_path",
2190
+ // locale, so openssl's textual output stays parseable
2191
+ "lang",
2192
+ "lc_all",
2193
+ "lc_ctype",
2194
+ // openssl configuration this app, or the host OS/user, may rely on
2195
+ "openssl_conf",
2196
+ "randfile",
2197
+ "openssl_modules",
2198
+ "openssl_engines",
2199
+ "ssl_cert_file",
2200
+ "ssl_cert_dir"
2201
+ ]);
1992
2202
  exportedEnvVars = {};
1993
2203
  }
1994
2204
  });
@@ -2011,24 +2221,17 @@ import { pipeline } from "stream/promises";
2011
2221
  import byline from "byline";
2012
2222
  import chalk4 from "chalk";
2013
2223
  import yauzl from "yauzl";
2014
- async function execute(cmd, cwd) {
2224
+ async function execute(file, args, cwd) {
2015
2225
  let output = "";
2016
- const options = {
2017
- cwd,
2018
- windowsHide: true
2019
- };
2020
2226
  return await new Promise((resolve, reject) => {
2021
- const child = child_process.exec(
2022
- cmd,
2023
- options,
2024
- (err) => {
2025
- const exitCode = err === null ? 0 : typeof err.code === "number" ? err.code : 1;
2026
- if (err) reject(err);
2027
- else {
2028
- resolve({ exitCode, output });
2029
- }
2030
- }
2031
- );
2227
+ const child = child_process.spawn(file, args, {
2228
+ cwd,
2229
+ windowsHide: true,
2230
+ env: buildChildEnv(),
2231
+ stdio: ["ignore", "pipe", "pipe"]
2232
+ });
2233
+ child.on("error", (err) => reject(err));
2234
+ child.on("close", (code) => resolve({ exitCode: code ?? 1, output }));
2032
2235
  const stream1 = byline(child.stdout);
2033
2236
  stream1.on("data", (line) => {
2034
2237
  output += `${line}
@@ -2040,16 +2243,13 @@ async function execute(cmd, cwd) {
2040
2243
  });
2041
2244
  });
2042
2245
  }
2043
- function quote2(str) {
2044
- return `"${str.replace(/\\/g, "/")}"`;
2045
- }
2046
2246
  function is_expected_openssl_version(strVersion) {
2047
2247
  return !!strVersion.match(/OpenSSL \d/);
2048
2248
  }
2049
2249
  async function getopensslExecPath() {
2050
2250
  let result1;
2051
2251
  try {
2052
- result1 = await execute("which openssl");
2252
+ result1 = await execute("which", ["openssl"]);
2053
2253
  } catch (err) {
2054
2254
  warningLog("warning: ", err.message);
2055
2255
  throw new Error("Cannot find openssl");
@@ -2066,11 +2266,10 @@ async function getopensslExecPath() {
2066
2266
  }
2067
2267
  async function check_system_openssl_version() {
2068
2268
  const opensslExecPath = await getopensslExecPath();
2069
- const q_opensslExecPath = quote2(opensslExecPath);
2070
2269
  if (doDebug2) {
2071
2270
  warningLog(` OpenSSL found in : ${chalk4.yellow(opensslExecPath)}`);
2072
2271
  }
2073
- const result = await execute(`${q_opensslExecPath} version`);
2272
+ const result = await execute(opensslExecPath, ["version"]);
2074
2273
  const exitCode = result?.exitCode;
2075
2274
  const output = result?.output;
2076
2275
  const version = output.trim();
@@ -2110,9 +2309,8 @@ async function install_and_check_win32_openssl_version() {
2110
2309
  version: `cannot find file ${opensslExecPath2}`
2111
2310
  };
2112
2311
  } else {
2113
- const q_openssl_exe_path = quote2(opensslExecPath2);
2114
2312
  const cwd = ".";
2115
- const { exitCode, output } = await execute(`${q_openssl_exe_path} version`, cwd);
2313
+ const { exitCode, output } = await execute(opensslExecPath2, ["version"], cwd);
2116
2314
  const version = output.trim();
2117
2315
  if (doDebug2) {
2118
2316
  warningLog(" Version = ", version);
@@ -2125,7 +2323,7 @@ async function install_and_check_win32_openssl_version() {
2125
2323
  }
2126
2324
  async function find_system_openssl_win32() {
2127
2325
  try {
2128
- const result = await execute("where openssl");
2326
+ const result = await execute("where", ["openssl"]);
2129
2327
  if (result.exitCode !== 0) {
2130
2328
  return void 0;
2131
2329
  }
@@ -2133,8 +2331,7 @@ async function install_and_check_win32_openssl_version() {
2133
2331
  if (!opensslPath2 || !fs5.existsSync(opensslPath2)) {
2134
2332
  return void 0;
2135
2333
  }
2136
- const q5 = quote2(opensslPath2);
2137
- const versionResult = await execute(`${q5} version`);
2334
+ const versionResult = await execute(opensslPath2, ["version"]);
2138
2335
  const version = versionResult.output.trim();
2139
2336
  if (versionResult.exitCode === 0 && is_expected_openssl_version(version)) {
2140
2337
  warningLog(
@@ -2299,6 +2496,7 @@ var init_install_prerequisite = __esm({
2299
2496
  "use strict";
2300
2497
  init_esm_shims();
2301
2498
  init_debug();
2499
+ init_env();
2302
2500
  doDebug2 = process.env.NODEOPCUAPKIDEBUG || false;
2303
2501
  }
2304
2502
  });
@@ -2310,36 +2508,53 @@ import fs6 from "fs";
2310
2508
  import os3 from "os";
2311
2509
  import byline2 from "byline";
2312
2510
  import chalk5 from "chalk";
2313
- async function execute2(cmd, options) {
2511
+ function passinArg(passphrase = "") {
2512
+ return { args: ["-passin", `env:${PASSIN_ENV_VAR}`], env: { [PASSIN_ENV_VAR]: passphrase } };
2513
+ }
2514
+ function passoutArg(passphrase = "") {
2515
+ return { args: ["-passout", `env:${PASSOUT_ENV_VAR}`], env: { [PASSOUT_ENV_VAR]: passphrase } };
2516
+ }
2517
+ function renderForDisplay(file, args) {
2518
+ return [file, ...args].map((a) => a === "" || /[\s"'`$\\]/.test(a) ? JSON.stringify(a) : a).join(" ");
2519
+ }
2520
+ async function execute2(file, args, options) {
2314
2521
  const from = new Error();
2315
2522
  options.cwd = options.cwd || process.cwd();
2316
2523
  if (!g_config.silent) {
2317
2524
  warningLog(chalk5.cyan(" CWD "), options.cwd);
2318
2525
  }
2319
2526
  const outputs = [];
2527
+ const errorOutputs = [];
2528
+ const display2 = renderForDisplay(file, args);
2320
2529
  return await new Promise((resolve, reject) => {
2321
- const child = child_process2.exec(
2322
- cmd,
2323
- {
2324
- cwd: options.cwd,
2325
- windowsHide: true
2326
- },
2327
- (err) => {
2328
- if (err) {
2329
- if (!options.hideErrorMessage) {
2330
- const fence = "###########################################";
2331
- console.error(chalk5.bgWhiteBright.redBright(`${fence} OPENSSL ERROR ${fence}`));
2332
- console.error(chalk5.bgWhiteBright.redBright(`CWD = ${options.cwd}`));
2333
- console.error(chalk5.bgWhiteBright.redBright(err.message));
2334
- console.error(chalk5.bgWhiteBright.redBright(`${fence} OPENSSL ERROR ${fence}`));
2335
- console.error(from.stack);
2336
- }
2337
- reject(new Error(err.message));
2338
- return;
2339
- }
2530
+ const child = child_process2.spawn(file, [...args], {
2531
+ cwd: options.cwd,
2532
+ windowsHide: true,
2533
+ env: buildChildEnv(options.env),
2534
+ stdio: ["ignore", "pipe", "pipe"]
2535
+ });
2536
+ const fail = (message) => {
2537
+ if (!options.hideErrorMessage) {
2538
+ const fence = "###########################################";
2539
+ console.error(chalk5.bgWhiteBright.redBright(`${fence} OPENSSL ERROR ${fence}`));
2540
+ console.error(chalk5.bgWhiteBright.redBright(`CWD = ${options.cwd}`));
2541
+ console.error(chalk5.bgWhiteBright.redBright(message));
2542
+ console.error(chalk5.bgWhiteBright.redBright(`${fence} OPENSSL ERROR ${fence}`));
2543
+ console.error(from.stack);
2544
+ }
2545
+ reject(new Error(message));
2546
+ };
2547
+ child.on("error", (err) => fail(`Command failed: ${display2}
2548
+ ${err.message}`));
2549
+ child.on("close", (code, signal) => {
2550
+ if (code === 0) {
2340
2551
  resolve(outputs.join(""));
2552
+ return;
2341
2553
  }
2342
- );
2554
+ const why = signal ? `signal ${signal}` : `exit code ${code}`;
2555
+ fail(`Command failed: ${display2}
2556
+ ${errorOutputs.join("")}(${why})`);
2557
+ });
2343
2558
  if (child.stdout) {
2344
2559
  const stream2 = byline2(child.stdout);
2345
2560
  stream2.on("data", (line) => {
@@ -2356,17 +2571,17 @@ async function execute2(cmd, options) {
2356
2571
  });
2357
2572
  }
2358
2573
  }
2359
- if (!g_config.silent) {
2360
- if (child.stderr) {
2361
- const stream1 = byline2(child.stderr);
2362
- stream1.on("data", (line) => {
2363
- line = line.toString();
2364
- if (displayError) {
2365
- process.stdout.write(`${chalk5.white(" stderr ") + chalk5.red(line)}
2574
+ if (child.stderr) {
2575
+ const stream1 = byline2(child.stderr);
2576
+ stream1.on("data", (line) => {
2577
+ line = line.toString();
2578
+ errorOutputs.push(`${line}
2366
2579
  `);
2367
- }
2368
- });
2369
- }
2580
+ if (!g_config.silent && displayError) {
2581
+ process.stdout.write(`${chalk5.white(" stderr ") + chalk5.red(line)}
2582
+ `);
2583
+ }
2584
+ });
2370
2585
  }
2371
2586
  });
2372
2587
  }
@@ -2376,18 +2591,18 @@ async function find_openssl() {
2376
2591
  async function ensure_openssl_installed() {
2377
2592
  if (!opensslPath) {
2378
2593
  opensslPath = await find_openssl();
2379
- const outputs = await execute_openssl("version", { cwd: "." });
2594
+ const outputs = await execute_openssl(["version"], { cwd: "." });
2380
2595
  g_config.opensslVersion = outputs.trim();
2381
2596
  if (doDebug) {
2382
2597
  warningLog("OpenSSL version : ", g_config.opensslVersion);
2383
2598
  }
2384
2599
  }
2385
2600
  }
2386
- async function execute_openssl_no_failure(cmd, options) {
2601
+ async function execute_openssl_no_failure(args, options) {
2387
2602
  options = options || {};
2388
2603
  options.hideErrorMessage = true;
2389
2604
  try {
2390
- return await execute_openssl(cmd, options);
2605
+ return await execute_openssl(args, options);
2391
2606
  } catch (err) {
2392
2607
  debugLog(" (ignored error = ERROR : )", err.message);
2393
2608
  }
@@ -2395,8 +2610,8 @@ async function execute_openssl_no_failure(cmd, options) {
2395
2610
  function getTempFolder() {
2396
2611
  return os3.tmpdir();
2397
2612
  }
2398
- async function execute_openssl(cmd, options) {
2399
- debugLog("execute_openssl", cmd, options);
2613
+ async function execute_openssl(args, options) {
2614
+ debugLog("execute_openssl", args, redactEnvForLog(options));
2400
2615
  const empty_config_file = n(getTempFolder(), "empty_config.cnf");
2401
2616
  if (!fs6.existsSync(empty_config_file)) {
2402
2617
  await fs6.promises.writeFile(empty_config_file, "# empty config file");
@@ -2408,23 +2623,24 @@ async function execute_openssl(cmd, options) {
2408
2623
  if (!g_config.silent) {
2409
2624
  warningLog(chalk5.cyan(" OPENSSL_CONF"), process.env.OPENSSL_CONF);
2410
2625
  warningLog(chalk5.cyan(" RANDFILE "), process.env.RANDFILE);
2411
- warningLog(chalk5.cyan(" CMD openssl "), chalk5.cyanBright(cmd));
2626
+ warningLog(chalk5.cyan(" CMD "), chalk5.cyanBright(renderForDisplay("openssl", args)));
2412
2627
  }
2413
2628
  await ensure_openssl_installed();
2414
- return await execute2(`${quote(opensslPath)} ${cmd}`, options);
2629
+ return await execute2(opensslPath, args, options);
2415
2630
  }
2416
- var opensslPath, n;
2631
+ var opensslPath, n, PASSIN_ENV_VAR, PASSOUT_ENV_VAR;
2417
2632
  var init_execute_openssl = __esm({
2418
2633
  "packages/node-opcua-pki/lib/toolbox/with_openssl/execute_openssl.ts"() {
2419
2634
  "use strict";
2420
2635
  init_esm_shims();
2421
- init_common();
2422
2636
  init_common2();
2423
2637
  init_config();
2424
2638
  init_debug();
2425
2639
  init_env();
2426
2640
  init_install_prerequisite();
2427
2641
  n = makePath;
2642
+ PASSIN_ENV_VAR = "NODE_OPCUA_PKI_OPENSSL_PASSIN";
2643
+ PASSOUT_ENV_VAR = "NODE_OPCUA_PKI_OPENSSL_PASSOUT";
2428
2644
  }
2429
2645
  });
2430
2646
 
@@ -2463,9 +2679,12 @@ function generateStaticConfig(configPath, options) {
2463
2679
  return temporaryConfigPath;
2464
2680
  }
2465
2681
  }
2466
- async function getPublicKeyFromPrivateKey(privateKeyFilename, publicKeyFilename) {
2682
+ async function getPublicKeyFromPrivateKey(privateKeyFilename, publicKeyFilename, passphrase) {
2467
2683
  assert7(fs7.existsSync(privateKeyFilename));
2468
- await execute_openssl(`rsa -pubout -in ${q(n2(privateKeyFilename))} -out ${q(n2(publicKeyFilename))}`, {});
2684
+ const passin = passinArg(passphrase);
2685
+ await execute_openssl(["rsa", "-pubout", "-in", n2(privateKeyFilename), "-out", n2(publicKeyFilename), ...passin.args], {
2686
+ env: passin.env
2687
+ });
2469
2688
  }
2470
2689
  function x509Date(date) {
2471
2690
  date = date || /* @__PURE__ */ new Date();
@@ -2486,30 +2705,28 @@ function x509Date(date) {
2486
2705
  }
2487
2706
  async function dumpCertificate(certificate) {
2488
2707
  assert7(fs7.existsSync(certificate));
2489
- return await execute_openssl(`x509 -in ${q(n2(certificate))} -text -noout`, {});
2708
+ return await execute_openssl(["x509", "-in", n2(certificate), "-text", "-noout"], {});
2490
2709
  }
2491
2710
  async function toDer(certificatePem) {
2492
2711
  assert7(fs7.existsSync(certificatePem));
2493
2712
  const certificateDer = certificatePem.replace(".pem", ".der");
2494
- return await execute_openssl(`x509 -outform der -in ${certificatePem} -out ${certificateDer}`, {});
2713
+ return await execute_openssl(["x509", "-outform", "der", "-in", certificatePem, "-out", certificateDer], {});
2495
2714
  }
2496
2715
  async function fingerprint(certificatePem) {
2497
2716
  assert7(fs7.existsSync(certificatePem));
2498
- return await execute_openssl(`x509 -fingerprint -noout -in ${certificatePem}`, {});
2717
+ return await execute_openssl(["x509", "-fingerprint", "-noout", "-in", certificatePem], {});
2499
2718
  }
2500
- var _counter, q, n2;
2719
+ var _counter, n2;
2501
2720
  var init_toolbox2 = __esm({
2502
2721
  "packages/node-opcua-pki/lib/toolbox/with_openssl/toolbox.ts"() {
2503
2722
  "use strict";
2504
2723
  init_esm_shims();
2505
- init_common();
2506
2724
  init_common2();
2507
2725
  init_config();
2508
2726
  init_env();
2509
2727
  init_execute_openssl();
2510
2728
  g_config.opensslVersion = "";
2511
2729
  _counter = 0;
2512
- q = quote;
2513
2730
  n2 = makePath;
2514
2731
  }
2515
2732
  });
@@ -2531,28 +2748,37 @@ async function createCertificateSigningRequestWithOpenSSL(certificateSigningRequ
2531
2748
  processAltNames(params);
2532
2749
  const configFile = generateStaticConfig(params.configFile, { cwd: params.rootDir });
2533
2750
  const options = { cwd: params.rootDir, openssl_conf: path5.relative(params.rootDir, configFile) };
2534
- const configOption = ` -config ${q2(n3(configFile))}`;
2535
2751
  const subject = params.subject ? new Subject3(params.subject).toString() : void 0;
2536
- const subjectOptions = subject ? ` -subj "${subject}"` : "";
2537
2752
  displaySubtitle("- Creating a Certificate Signing Request with openssl");
2538
2753
  await execute_openssl(
2539
- "req -new -sha256 -batch -text " + configOption + " -key " + q2(n3(params.privateKey)) + subjectOptions + " -out " + q2(n3(certificateSigningRequestFilename)),
2754
+ [
2755
+ "req",
2756
+ "-new",
2757
+ "-sha256",
2758
+ "-batch",
2759
+ "-text",
2760
+ "-config",
2761
+ n3(configFile),
2762
+ "-key",
2763
+ n3(params.privateKey),
2764
+ ...subject ? ["-subj", subject] : [],
2765
+ "-out",
2766
+ n3(certificateSigningRequestFilename)
2767
+ ],
2540
2768
  options
2541
2769
  );
2542
2770
  }
2543
- var q2, n3;
2771
+ var n3;
2544
2772
  var init_create_certificate_signing_request2 = __esm({
2545
2773
  "packages/node-opcua-pki/lib/toolbox/with_openssl/create_certificate_signing_request.ts"() {
2546
2774
  "use strict";
2547
2775
  init_esm_shims();
2548
2776
  init_subject();
2549
- init_common();
2550
2777
  init_common2();
2551
2778
  init_display();
2552
2779
  init_env();
2553
2780
  init_execute_openssl();
2554
2781
  init_toolbox2();
2555
- q2 = quote;
2556
2782
  n3 = makePath;
2557
2783
  }
2558
2784
  });
@@ -2575,31 +2801,28 @@ var init_with_openssl = __esm({
2575
2801
  import assert9 from "assert";
2576
2802
  import fs9 from "fs";
2577
2803
  async function createPFX(options) {
2578
- const { certificateFile, privateKeyFile, outputFile, passphrase = "", caCertificateFiles } = options;
2804
+ const { certificateFile, privateKeyFile, privateKeyPassphrase, outputFile, passphrase = "", caCertificateFiles } = options;
2579
2805
  assert9(fs9.existsSync(certificateFile), `Certificate file does not exist: ${certificateFile}`);
2580
2806
  assert9(fs9.existsSync(privateKeyFile), `Private key file does not exist: ${privateKeyFile}`);
2581
- let cmd = `pkcs12 -export`;
2582
- cmd += ` -in ${q3(n4(certificateFile))}`;
2583
- cmd += ` -inkey ${q3(n4(privateKeyFile))}`;
2807
+ const args = ["pkcs12", "-export", "-in", n4(certificateFile), "-inkey", n4(privateKeyFile)];
2584
2808
  if (caCertificateFiles) {
2585
2809
  for (const caFile of caCertificateFiles) {
2586
2810
  assert9(fs9.existsSync(caFile), `CA certificate file does not exist: ${caFile}`);
2587
- cmd += ` -certfile ${q3(n4(caFile))}`;
2811
+ args.push("-certfile", n4(caFile));
2588
2812
  }
2589
2813
  }
2590
- cmd += ` -out ${q3(n4(outputFile))}`;
2591
- cmd += ` -passout pass:${passphrase}`;
2592
- await execute_openssl(cmd, {});
2814
+ const passin = passinArg(privateKeyPassphrase);
2815
+ const passout = passoutArg(passphrase);
2816
+ args.push("-out", n4(outputFile), ...passin.args, ...passout.args);
2817
+ await execute_openssl(args, { env: { ...passin.env, ...passout.env } });
2593
2818
  }
2594
- var q3, n4;
2819
+ var n4;
2595
2820
  var init_toolbox_pfx = __esm({
2596
2821
  "packages/node-opcua-pki/lib/pki/toolbox_pfx.ts"() {
2597
2822
  "use strict";
2598
2823
  init_esm_shims();
2599
- init_common();
2600
2824
  init_common2();
2601
2825
  init_execute_openssl();
2602
- q3 = quote;
2603
2826
  n4 = makePath;
2604
2827
  }
2605
2828
  });
@@ -2614,7 +2837,7 @@ var init_ca_config_template_cnf = __esm({
2614
2837
  [ ca ]
2615
2838
  default_ca = CA_default
2616
2839
  [ CA_default ]
2617
- dir = %%ROOT_FOLDER%% # the main CA folder
2840
+ dir = "%%ROOT_FOLDER%%" # the main CA folder (quoted: see renderCaConfig)
2618
2841
  certs = $dir/certs # where to store certificates
2619
2842
  new_certs_dir = $dir/certs #
2620
2843
  database = $dir/index.txt # the certificate database
@@ -2760,10 +2983,17 @@ import {
2760
2983
  generatePrivateKeyFile as generatePrivateKeyFile2,
2761
2984
  readCertificatePEM,
2762
2985
  readCertificateSigningRequest,
2763
- readPrivateKey,
2986
+ readPrivateKey as readPrivateKey2,
2764
2987
  Subject as Subject4,
2765
- toPem as toPem2
2988
+ toPem as toPem2,
2989
+ writePrivateKeyFile as writePrivateKeyFile2
2766
2990
  } from "node-opcua-crypto";
2991
+ function escapeOpensslConfDoubleQuoted(value) {
2992
+ return value.replace(/\\/g, "\\\\").replace(/"/g, '\\"');
2993
+ }
2994
+ function renderCaConfig(caRootDir) {
2995
+ return configurationFileTemplate.replace(/%%ROOT_FOLDER%%/, escapeOpensslConfDoubleQuoted(makePath(caRootDir)));
2996
+ }
2767
2997
  function octetStringToIpAddress(a) {
2768
2998
  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();
2769
2999
  }
@@ -2772,7 +3002,7 @@ async function construct_CertificateAuthority(certificateAuthority) {
2772
3002
  const caRootDir = path6.resolve(certificateAuthority.rootDir);
2773
3003
  async function make_folders() {
2774
3004
  mkdirRecursiveSync(caRootDir);
2775
- mkdirRecursiveSync(path6.join(caRootDir, "private"));
3005
+ ensurePrivateDirectory(path6.join(caRootDir, "private"));
2776
3006
  mkdirRecursiveSync(path6.join(caRootDir, "public"));
2777
3007
  mkdirRecursiveSync(path6.join(caRootDir, "certs"));
2778
3008
  mkdirRecursiveSync(path6.join(caRootDir, "crl"));
@@ -2797,6 +3027,8 @@ async function construct_CertificateAuthority(certificateAuthority) {
2797
3027
  const caKeyExists = fs10.existsSync(path6.join(caRootDir, "private/cakey.pem"));
2798
3028
  const caCertExists = fs10.existsSync(path6.join(caRootDir, "public/cacert.pem"));
2799
3029
  if (caKeyExists && caCertExists && !config3.forceCA) {
3030
+ restrictPrivateFilePermissions(path6.join(caRootDir, "private/cakey.pem"), 384);
3031
+ await certificateAuthority._ensurePrivateKeyProtection();
2800
3032
  debugLog("CA private key and certificate already exist ... skipping");
2801
3033
  return;
2802
3034
  }
@@ -2815,26 +3047,40 @@ async function construct_CertificateAuthority(certificateAuthority) {
2815
3047
  }
2816
3048
  const caConfigFile = certificateAuthority.configFile;
2817
3049
  if (1) {
2818
- let data = configurationFileTemplate;
2819
- data = makePath(data.replace(/%%ROOT_FOLDER%%/, caRootDir));
2820
- await fs10.promises.writeFile(caConfigFile, data);
3050
+ await fs10.promises.writeFile(caConfigFile, renderCaConfig(caRootDir));
2821
3051
  }
2822
- const subjectOpt = ` -subj "${subject.toString()}" `;
3052
+ const subjectOpt = ["-subj", subject.toString()];
2823
3053
  const caCommonName = subject.commonName || "NodeOPCUA-CA";
2824
3054
  setEnv("ALTNAME", `URI:urn:${caCommonName}`);
2825
3055
  certificateAuthority._wireRevocationEnvVars();
2826
3056
  const options = { cwd: caRootDir };
2827
3057
  const configFile = generateStaticConfig("conf/caconfig.cnf", options);
2828
- const configOption = ` -config ${q4(n5(configFile))}`;
3058
+ const configOption = ["-config", n5(configFile)];
2829
3059
  const keySize = certificateAuthority.keySize;
2830
3060
  const privateKeyFilename = path6.join(caRootDir, "private/cakey.pem");
2831
3061
  const csrFilename = path6.join(caRootDir, "private/cakey.csr");
2832
3062
  displayTitle(`Generate the CA private Key - ${keySize}`);
2833
- await generatePrivateKeyFile2(privateKeyFilename, keySize);
3063
+ const passin = await certificateAuthority._opensslPassin();
3064
+ await generatePrivateKeyFile2(privateKeyFilename, keySize, { passphrase: await certificateAuthority._privateKeyPassphrase() });
3065
+ restrictPrivateFilePermissions(privateKeyFilename, 384);
2834
3066
  displayTitle("Generate a certificate request for the CA key");
2835
3067
  await execute_openssl(
2836
- "req -new -sha256 -text -extensions v3_ca_req" + configOption + " -key " + q4(n5(privateKeyFilename)) + " -out " + q4(n5(csrFilename)) + " " + subjectOpt,
2837
- options
3068
+ [
3069
+ "req",
3070
+ "-new",
3071
+ "-sha256",
3072
+ "-text",
3073
+ "-extensions",
3074
+ "v3_ca_req",
3075
+ ...configOption,
3076
+ "-key",
3077
+ n5(privateKeyFilename),
3078
+ "-out",
3079
+ n5(csrFilename),
3080
+ ...subjectOpt,
3081
+ ...passin.args
3082
+ ],
3083
+ { ...options, env: passin.env }
2838
3084
  );
2839
3085
  const issuerCA = certificateAuthority._issuerCA;
2840
3086
  if (issuerCA) {
@@ -2842,27 +3088,71 @@ async function construct_CertificateAuthority(certificateAuthority) {
2842
3088
  const issuerCert = path6.resolve(issuerCA.caCertificate);
2843
3089
  const issuerKey = path6.resolve(issuerCA.rootDir, "private/cakey.pem");
2844
3090
  const issuerSerial = path6.resolve(issuerCA.rootDir, "serial");
3091
+ const issuerPassin = await issuerCA._opensslPassin();
2845
3092
  await execute_openssl(
2846
- " x509 -sha256 -req -days 3650 -text -extensions v3_ca -extfile " + q4(n5(configFile)) + " -in private/cakey.csr -CA " + q4(n5(issuerCert)) + " -CAkey " + q4(n5(issuerKey)) + " -CAserial " + q4(n5(issuerSerial)) + " -out public/cacert.pem",
2847
- options
3093
+ [
3094
+ "x509",
3095
+ "-sha256",
3096
+ "-req",
3097
+ "-days",
3098
+ "3650",
3099
+ "-text",
3100
+ "-extensions",
3101
+ "v3_ca",
3102
+ "-extfile",
3103
+ n5(configFile),
3104
+ "-in",
3105
+ "private/cakey.csr",
3106
+ "-CA",
3107
+ n5(issuerCert),
3108
+ "-CAkey",
3109
+ n5(issuerKey),
3110
+ "-CAserial",
3111
+ n5(issuerSerial),
3112
+ "-out",
3113
+ "public/cacert.pem",
3114
+ ...issuerPassin.args
3115
+ ],
3116
+ { ...options, env: issuerPassin.env }
2848
3117
  );
2849
3118
  } else {
2850
3119
  displayTitle("Generate CA Certificate (self-signed)");
2851
3120
  await execute_openssl(
2852
- " x509 -sha256 -req -days 3650 -text -extensions v3_ca -extfile " + q4(n5(configFile)) + " -in private/cakey.csr -signkey " + q4(n5(privateKeyFilename)) + " -out public/cacert.pem",
2853
- options
3121
+ [
3122
+ "x509",
3123
+ "-sha256",
3124
+ "-req",
3125
+ "-days",
3126
+ "3650",
3127
+ "-text",
3128
+ "-extensions",
3129
+ "v3_ca",
3130
+ "-extfile",
3131
+ n5(configFile),
3132
+ "-in",
3133
+ "private/cakey.csr",
3134
+ "-signkey",
3135
+ n5(privateKeyFilename),
3136
+ "-out",
3137
+ "public/cacert.pem",
3138
+ ...passin.args
3139
+ ],
3140
+ { ...options, env: passin.env }
2854
3141
  );
2855
3142
  }
2856
3143
  displaySubtitle("generate initial CRL (Certificate Revocation List)");
2857
- await regenerateCrl(certificateAuthority.revocationList, configOption, options);
3144
+ await regenerateCrl(certificateAuthority.revocationList, configOption, options, passin);
2858
3145
  displayTitle("Create Certificate Authority (CA) ---> DONE");
2859
3146
  }
2860
- async function regenerateCrl(revocationList, configOption, options) {
3147
+ async function regenerateCrl(revocationList, configOption, options, passin) {
2861
3148
  displaySubtitle("regenerate CRL (Certificate Revocation List)");
2862
- await execute_openssl(`ca -gencrl ${configOption} -out crl/revocation_list.crl`, options);
2863
- await execute_openssl("crl -in crl/revocation_list.crl -out crl/revocation_list.der -outform der", options);
3149
+ await execute_openssl(["ca", "-gencrl", ...configOption, "-out", "crl/revocation_list.crl", ...passin.args], {
3150
+ ...options,
3151
+ env: passin.env
3152
+ });
3153
+ await execute_openssl(["crl", "-in", "crl/revocation_list.crl", "-out", "crl/revocation_list.der", "-outform", "der"], options);
2864
3154
  displaySubtitle("Display (Certificate Revocation List)");
2865
- await execute_openssl(`crl -in ${q4(n5(revocationList))} -text -noout`, options);
3155
+ await execute_openssl(["crl", "-in", n5(revocationList), "-text", "-noout"], options);
2866
3156
  }
2867
3157
  function parseOpenSSLDate(dateStr) {
2868
3158
  const raw = dateStr?.split(",")[0] ?? "";
@@ -2903,7 +3193,7 @@ function validateRevocationUrl(url, fieldName) {
2903
3193
  }
2904
3194
  return url;
2905
3195
  }
2906
- var defaultSubject, configurationFileTemplate, configurationFileSimpleTemplate2, config3, n5, q4, CertificateAuthority;
3196
+ var defaultSubject, configurationFileTemplate, configurationFileSimpleTemplate2, config3, n5, CertificateAuthority;
2907
3197
  var init_certificate_authority = __esm({
2908
3198
  "packages/node-opcua-pki/lib/ca/certificate_authority.ts"() {
2909
3199
  "use strict";
@@ -2922,7 +3212,6 @@ var init_certificate_authority = __esm({
2922
3212
  pkiDir: "INVALID"
2923
3213
  };
2924
3214
  n5 = makePath;
2925
- q4 = quote;
2926
3215
  assert10(octetStringToIpAddress("c07b9179") === "192.123.145.121");
2927
3216
  CertificateAuthority = class {
2928
3217
  /** RSA key size used when generating the CA private key. */
@@ -2937,6 +3226,10 @@ var init_certificate_authority = __esm({
2937
3226
  _crlDistributionUrl;
2938
3227
  _ocspResponderUrl;
2939
3228
  _caIssuersUrl;
3229
+ #privateKeyPassphrase;
3230
+ /** resolved once (see `privateKeyPassphrase`); `#passphraseResolved` distinguishes "none" from "not yet" */
3231
+ #resolvedPassphrase;
3232
+ #passphraseResolved = false;
2940
3233
  constructor(options) {
2941
3234
  assert10(Object.prototype.hasOwnProperty.call(options, "location"));
2942
3235
  assert10(Object.prototype.hasOwnProperty.call(options, "keySize"));
@@ -2944,6 +3237,7 @@ var init_certificate_authority = __esm({
2944
3237
  this.keySize = options.keySize || 2048;
2945
3238
  this.subject = new Subject4(options.subject || defaultSubject);
2946
3239
  this._issuerCA = options.issuerCA;
3240
+ this.#privateKeyPassphrase = options.privateKeyPassphrase;
2947
3241
  if (options.crlDistributionUrl !== void 0) {
2948
3242
  this.setCrlDistributionUrl(options.crlDistributionUrl);
2949
3243
  }
@@ -3040,6 +3334,69 @@ var init_certificate_authority = __esm({
3040
3334
  get configFile() {
3041
3335
  return path6.normalize(path6.join(this.rootDir, "./conf/caconfig.cnf"));
3042
3336
  }
3337
+ /** Path to the CA private key (`private/cakey.pem`); may be passphrase-encrypted, see {@link getPrivateKey}. */
3338
+ get privateKey() {
3339
+ return path6.join(path6.resolve(this.rootDir), "private/cakey.pem");
3340
+ }
3341
+ /**
3342
+ * The CA private key, decrypted with the configured `privateKeyPassphrase`
3343
+ * if it is encrypted. Fails closed (`PrivateKeyPassphraseRequiredError`)
3344
+ * on an encrypted key with no or the wrong passphrase.
3345
+ */
3346
+ async getPrivateKey() {
3347
+ return readPrivateKey2(this.privateKey, await this._privateKeyPassphrase());
3348
+ }
3349
+ /**
3350
+ * Enable, disable, or rotate the passphrase protecting `private/cakey.pem`
3351
+ * (temp file + atomic rename, temp file removed on failure). Only
3352
+ * rewrites the file: construct a new `CertificateAuthority` with the new
3353
+ * passphrase to continue using it.
3354
+ */
3355
+ async reencryptPrivateKey(oldPassphrase, newPassphrase) {
3356
+ const oldPass = await resolvePrivateKeyPassphrase(oldPassphrase);
3357
+ const newPass = await resolvePrivateKeyPassphrase(newPassphrase);
3358
+ const key = readPrivateKey2(this.privateKey, oldPass);
3359
+ await this.#rewritePrivateKeyFile(key, newPass);
3360
+ }
3361
+ async #rewritePrivateKeyFile(privateKey, passphrase) {
3362
+ const tmpFilename = `${this.privateKey}.${process.pid}-${Date.now()}.tmp`;
3363
+ try {
3364
+ await writePrivateKeyFile2(tmpFilename, privateKey, { passphrase });
3365
+ await fs10.promises.rename(tmpFilename, this.privateKey);
3366
+ } finally {
3367
+ await fs10.promises.rm(tmpFilename, { force: true });
3368
+ }
3369
+ }
3370
+ /** @internal resolve the configured passphrase, at most once per instance */
3371
+ async _privateKeyPassphrase() {
3372
+ if (!this.#passphraseResolved) {
3373
+ this.#resolvedPassphrase = await resolvePrivateKeyPassphrase(this.#privateKeyPassphrase);
3374
+ this.#passphraseResolved = true;
3375
+ }
3376
+ return this.#resolvedPassphrase;
3377
+ }
3378
+ /** @internal `-passin env:` argv + env for every openssl call that loads this CA's key (always emitted, empty when none) */
3379
+ async _opensslPassin() {
3380
+ return passinArg(await this._privateKeyPassphrase());
3381
+ }
3382
+ /**
3383
+ * @internal On an existing key: encrypt it in place if a passphrase is
3384
+ * configured and it is still plaintext (secure by default: the option
3385
+ * means "protect this key", not "ignore me"), then read it back so a
3386
+ * wrong or missing passphrase fails initialize() closed rather than the
3387
+ * first signing operation.
3388
+ */
3389
+ async _ensurePrivateKeyProtection() {
3390
+ if (!fs10.existsSync(this.privateKey)) {
3391
+ return;
3392
+ }
3393
+ if (this.#privateKeyPassphrase !== void 0 && !isEncryptedPrivateKeyFile(this.privateKey)) {
3394
+ warningLog("CertificateAuthority: private key is plaintext but a passphrase is configured; encrypting it in place");
3395
+ const plaintextKey = readPrivateKey2(this.privateKey);
3396
+ await this.#rewritePrivateKeyFile(plaintextKey, await this._privateKeyPassphrase());
3397
+ }
3398
+ await this.getPrivateKey();
3399
+ }
3043
3400
  /** Path to the CA certificate in PEM format (`public/cacert.pem`). */
3044
3401
  get caCertificate() {
3045
3402
  return makePath(this.rootDir, "./public/cacert.pem");
@@ -3377,7 +3734,7 @@ var init_certificate_authority = __esm({
3377
3734
  await this.signCertificateRequest(certFile, csrFile, signingParams);
3378
3735
  const certPem = readCertificatePEM(certFile);
3379
3736
  const certificateDer = convertPEMtoDER(certPem);
3380
- const privateKey = readPrivateKey(privateKeyFile);
3737
+ const privateKey = readPrivateKey2(privateKeyFile);
3381
3738
  return { certificateDer, privateKey };
3382
3739
  } finally {
3383
3740
  await fs10.promises.rm(tmpDir, {
@@ -3497,12 +3854,17 @@ var init_certificate_authority = __esm({
3497
3854
  async initializeCSR() {
3498
3855
  const caRootDir = path6.resolve(this.rootDir);
3499
3856
  mkdirRecursiveSync(caRootDir);
3500
- for (const dir of ["private", "public", "certs", "crl", "conf"]) {
3857
+ for (const dir of ["public", "certs", "crl", "conf"]) {
3501
3858
  mkdirRecursiveSync(path6.join(caRootDir, dir));
3502
3859
  }
3860
+ ensurePrivateDirectory(path6.join(caRootDir, "private"));
3503
3861
  const caCertFile = this.caCertificate;
3504
3862
  const privateKeyFile = path6.join(caRootDir, "private/cakey.pem");
3505
3863
  const csrFile = path6.join(caRootDir, "private/cakey.csr");
3864
+ if (fs10.existsSync(privateKeyFile)) {
3865
+ restrictPrivateFilePermissions(privateKeyFile, 384);
3866
+ await this._ensurePrivateKeyProtection();
3867
+ }
3506
3868
  if (fs10.existsSync(caCertFile)) {
3507
3869
  const certDer = convertPEMtoDER(readCertificatePEM(caCertFile));
3508
3870
  const certInfo = exploreCertificate2(certDer);
@@ -3536,11 +3898,10 @@ var init_certificate_authority = __esm({
3536
3898
  await fs10.promises.writeFile(indexFileAttr, "unique_subject = no");
3537
3899
  }
3538
3900
  const caConfigFile = this.configFile;
3539
- let data = configurationFileTemplate;
3540
- data = makePath(data.replace(/%%ROOT_FOLDER%%/, caRootDir));
3541
- await fs10.promises.writeFile(caConfigFile, data);
3901
+ await fs10.promises.writeFile(caConfigFile, renderCaConfig(caRootDir));
3542
3902
  if (!fs10.existsSync(privateKeyFile)) {
3543
- await generatePrivateKeyFile2(privateKeyFile, this.keySize);
3903
+ await generatePrivateKeyFile2(privateKeyFile, this.keySize, { passphrase: await this._privateKeyPassphrase() });
3904
+ restrictPrivateFilePermissions(privateKeyFile, 384);
3544
3905
  }
3545
3906
  await this._generateCSR(caRootDir, privateKeyFile, csrFile);
3546
3907
  return { status: "created", csrPath: csrFile };
@@ -3582,14 +3943,29 @@ var init_certificate_authority = __esm({
3582
3943
  * @internal
3583
3944
  */
3584
3945
  async _generateCSR(caRootDir, privateKeyFile, csrFile) {
3585
- const subjectOpt = ` -subj "${this.subject.toString()}" `;
3586
3946
  processAltNames({});
3587
3947
  const options = { cwd: caRootDir };
3588
3948
  const configFile = generateStaticConfig("conf/caconfig.cnf", options);
3589
- const configOption = ` -config ${q4(n5(configFile))}`;
3949
+ const passin = await this._opensslPassin();
3590
3950
  await execute_openssl(
3591
- "req -new -sha256 -text -extensions v3_ca_req" + configOption + " -key " + q4(n5(privateKeyFile)) + " -out " + q4(n5(csrFile)) + " " + subjectOpt,
3592
- options
3951
+ [
3952
+ "req",
3953
+ "-new",
3954
+ "-sha256",
3955
+ "-text",
3956
+ "-extensions",
3957
+ "v3_ca_req",
3958
+ "-config",
3959
+ n5(configFile),
3960
+ "-key",
3961
+ n5(privateKeyFile),
3962
+ "-out",
3963
+ n5(csrFile),
3964
+ "-subj",
3965
+ this.subject.toString(),
3966
+ ...passin.args
3967
+ ],
3968
+ { ...options, env: passin.env }
3593
3969
  );
3594
3970
  }
3595
3971
  /**
@@ -3611,7 +3987,6 @@ var init_certificate_authority = __esm({
3611
3987
  async installCACertificate(signedCertFile) {
3612
3988
  const caRootDir = path6.resolve(this.rootDir);
3613
3989
  const caCertFile = this.caCertificate;
3614
- const privateKeyFile = path6.join(caRootDir, "private/cakey.pem");
3615
3990
  const fullPem = await fs10.promises.readFile(signedCertFile, "utf8");
3616
3991
  const pemBlocks = fullPem.match(/-----BEGIN CERTIFICATE-----[\s\S]*?-----END CERTIFICATE-----/g);
3617
3992
  if (!pemBlocks || pemBlocks.length === 0) {
@@ -3622,7 +3997,7 @@ var init_certificate_authority = __esm({
3622
3997
  };
3623
3998
  }
3624
3999
  const certDer = convertPEMtoDER(pemBlocks[0]);
3625
- const privateKey = readPrivateKey(privateKeyFile);
4000
+ const privateKey = await this.getPrivateKey();
3626
4001
  if (!certificateMatchesPrivateKey(certDer, privateKey)) {
3627
4002
  return {
3628
4003
  status: "error",
@@ -3645,8 +4020,7 @@ var init_certificate_authority = __esm({
3645
4020
  }
3646
4021
  const options = { cwd: caRootDir };
3647
4022
  const configFile = generateStaticConfig("conf/caconfig.cnf", options);
3648
- const configOption = ` -config ${q4(n5(configFile))}`;
3649
- await regenerateCrl(this.revocationList, configOption, options);
4023
+ await regenerateCrl(this.revocationList, ["-config", n5(configFile)], options, await this._opensslPassin());
3650
4024
  return { status: "success" };
3651
4025
  }
3652
4026
  /**
@@ -3668,9 +4042,32 @@ var init_certificate_authority = __esm({
3668
4042
  this._wireRevocationEnvVars();
3669
4043
  const configFile = generateStaticConfig("conf/caconfig.cnf", options);
3670
4044
  const validity = params.validity ?? 3650;
4045
+ const passin = await this._opensslPassin();
3671
4046
  await execute_openssl(
3672
- ` x509 -sha256 -req -days ${validity} -text -extensions v3_ca -extfile ` + q4(n5(configFile)) + " -in " + q4(n5(csrFile)) + " -CA " + q4(n5(this.caCertificate)) + " -CAkey " + q4(n5(path6.join(caRootDir, "private/cakey.pem"))) + " -CAserial " + q4(n5(path6.join(caRootDir, "serial"))) + " -out " + q4(n5(certFile)),
3673
- options
4047
+ [
4048
+ "x509",
4049
+ "-sha256",
4050
+ "-req",
4051
+ "-days",
4052
+ String(validity),
4053
+ "-text",
4054
+ "-extensions",
4055
+ "v3_ca",
4056
+ "-extfile",
4057
+ n5(configFile),
4058
+ "-in",
4059
+ n5(csrFile),
4060
+ "-CA",
4061
+ n5(this.caCertificate),
4062
+ "-CAkey",
4063
+ n5(path6.join(caRootDir, "private/cakey.pem")),
4064
+ "-CAserial",
4065
+ n5(path6.join(caRootDir, "serial")),
4066
+ "-out",
4067
+ n5(certFile),
4068
+ ...passin.args
4069
+ ],
4070
+ { ...options, env: passin.env }
3674
4071
  );
3675
4072
  await this.constructCertificateChain(certFile);
3676
4073
  }
@@ -3732,23 +4129,36 @@ var init_certificate_authority = __esm({
3732
4129
  cwd: this.rootDir,
3733
4130
  openssl_conf: makePath(configFile)
3734
4131
  };
3735
- const configOption = "";
3736
4132
  const subject = params.subject ? new Subject4(params.subject).toString() : "";
3737
- const subjectOptions = subject && subject.length > 1 ? ` -subj ${subject} ` : "";
4133
+ const subjectOptions = subject && subject.length > 1 ? ["-subj", subject] : [];
3738
4134
  displaySubtitle("- the certificate signing request");
3739
4135
  await execute_openssl(
3740
- "req -new -sha256 -text " + configOption + subjectOptions + " -batch -key " + q4(n5(privateKey)) + " -out " + q4(n5(csrFile)),
4136
+ ["req", "-new", "-sha256", "-text", ...subjectOptions, "-batch", "-key", n5(privateKey), "-out", n5(csrFile)],
3741
4137
  options
3742
4138
  );
3743
4139
  displaySubtitle("- creating the self-signed certificate");
3744
4140
  await execute_openssl(
3745
- "ca -selfsign -keyfile " + q4(n5(privateKey)) + " -startdate " + x509Date(params.startDate) + " -enddate " + x509Date(params.endDate) + " -batch -out " + q4(n5(certificateFile)) + " -in " + q4(n5(csrFile)),
4141
+ [
4142
+ "ca",
4143
+ "-selfsign",
4144
+ "-keyfile",
4145
+ n5(privateKey),
4146
+ "-startdate",
4147
+ x509Date(params.startDate),
4148
+ "-enddate",
4149
+ x509Date(params.endDate),
4150
+ "-batch",
4151
+ "-out",
4152
+ n5(certificateFile),
4153
+ "-in",
4154
+ n5(csrFile)
4155
+ ],
3746
4156
  options
3747
4157
  );
3748
4158
  displaySubtitle("- dump the certificate for a check");
3749
- await execute_openssl(`x509 -in ${q4(n5(certificateFile))} -dates -fingerprint -purpose -noout`, {});
4159
+ await execute_openssl(["x509", "-in", n5(certificateFile), "-dates", "-fingerprint", "-purpose", "-noout"], {});
3750
4160
  displaySubtitle("- verify self-signed certificate");
3751
- await execute_openssl_no_failure(`verify -verbose -CAfile ${q4(n5(certificateFile))} ${q4(n5(certificateFile))}`, options);
4161
+ await execute_openssl_no_failure(["verify", "-verbose", "-CAfile", n5(certificateFile), n5(certificateFile)], options);
3752
4162
  await fs10.promises.unlink(csrFile);
3753
4163
  }
3754
4164
  /**
@@ -3778,22 +4188,41 @@ var init_certificate_authority = __esm({
3778
4188
  setEnv("ALTNAME", "");
3779
4189
  const randomFile = path6.join(this.rootDir, "random.rnd");
3780
4190
  setEnv("RANDFILE", randomFile);
3781
- const configOption = ` -config ${q4(n5(configFile))}`;
4191
+ const configOption = ["-config", n5(configFile)];
3782
4192
  const reason = params.reason || "keyCompromise";
3783
4193
  assert10(crlReasons.indexOf(reason) >= 0);
3784
4194
  displayTitle(`Revoking certificate ${certificate}`);
3785
4195
  displaySubtitle("Revoke certificate");
3786
- await execute_openssl_no_failure(`ca -verbose ${configOption} -revoke ${q4(certificate)} -crl_reason ${reason}`, options);
3787
- await regenerateCrl(this.revocationList, configOption, options);
4196
+ const passin = await this._opensslPassin();
4197
+ await execute_openssl_no_failure(
4198
+ ["ca", "-verbose", ...configOption, "-revoke", certificate, "-crl_reason", reason, ...passin.args],
4199
+ { ...options, env: passin.env }
4200
+ );
4201
+ await regenerateCrl(this.revocationList, configOption, options, passin);
3788
4202
  displaySubtitle("Verify that certificate is revoked");
3789
4203
  await execute_openssl_no_failure(
3790
- "verify -verbose -CRLfile " + q4(n5(this.revocationList)) + " -CAfile " + q4(n5(this.caCertificate)) + " -crl_check " + q4(n5(certificate)),
4204
+ [
4205
+ "verify",
4206
+ "-verbose",
4207
+ "-CRLfile",
4208
+ n5(this.revocationList),
4209
+ "-CAfile",
4210
+ n5(this.caCertificate),
4211
+ "-crl_check",
4212
+ n5(certificate)
4213
+ ],
3791
4214
  options
3792
4215
  );
3793
4216
  displaySubtitle("Produce CRL in DER form ");
3794
- await execute_openssl(`crl -in ${q4(n5(this.revocationList))} -out crl/revocation_list.der -outform der`, options);
4217
+ await execute_openssl(
4218
+ ["crl", "-in", n5(this.revocationList), "-out", "crl/revocation_list.der", "-outform", "der"],
4219
+ options
4220
+ );
3795
4221
  displaySubtitle("Produce CRL in PEM form ");
3796
- await execute_openssl(`crl -in ${q4(n5(this.revocationList))} -out crl/revocation_list.pem -outform pem -text `, options);
4222
+ await execute_openssl(
4223
+ ["crl", "-in", n5(this.revocationList), "-out", "crl/revocation_list.pem", "-outform", "pem", "-text"],
4224
+ options
4225
+ );
3797
4226
  }
3798
4227
  /**
3799
4228
  * Sign a Certificate Signing Request (CSR) with this CA.
@@ -3835,13 +4264,27 @@ var init_certificate_authority = __esm({
3835
4264
  this._wireRevocationEnvVars();
3836
4265
  const configFile = generateStaticConfig("conf/caconfig.cnf", options);
3837
4266
  displaySubtitle("- then we ask the authority to sign the certificate signing request");
3838
- const configOption = ` -config ${configFile}`;
4267
+ const passin = await this._opensslPassin();
3839
4268
  await execute_openssl(
3840
- "ca " + configOption + " -startdate " + x509Date(params1.startDate) + " -enddate " + x509Date(params1.endDate) + " -batch -out " + q4(n5(certificate)) + " -in " + q4(n5(certificateSigningRequestFilename)),
3841
- options
4269
+ [
4270
+ "ca",
4271
+ "-config",
4272
+ configFile,
4273
+ "-startdate",
4274
+ x509Date(params1.startDate),
4275
+ "-enddate",
4276
+ x509Date(params1.endDate),
4277
+ "-batch",
4278
+ "-out",
4279
+ n5(certificate),
4280
+ "-in",
4281
+ n5(certificateSigningRequestFilename),
4282
+ ...passin.args
4283
+ ],
4284
+ { ...options, env: passin.env }
3842
4285
  );
3843
4286
  displaySubtitle("- dump the certificate for a check");
3844
- await execute_openssl(`x509 -in ${q4(n5(certificate))} -dates -fingerprint -purpose -noout`, options);
4287
+ await execute_openssl(["x509", "-in", n5(certificate), "-dates", "-fingerprint", "-purpose", "-noout"], options);
3845
4288
  displaySubtitle("- construct CA certificate with CRL");
3846
4289
  await this.constructCACertificateWithCRL();
3847
4290
  displaySubtitle("- construct certificate chain");
@@ -3861,10 +4304,8 @@ var init_certificate_authority = __esm({
3861
4304
  const options = { cwd: this.rootDir };
3862
4305
  const configFile = generateStaticConfig("conf/caconfig.cnf", options);
3863
4306
  setEnv("OPENSSL_CONF", makePath(configFile));
3864
- const _configOption = ` -config ${configFile}`;
3865
- _configOption;
3866
4307
  await execute_openssl_no_failure(
3867
- `verify -verbose -CAfile ${q4(n5(this.caCertificateWithCrl))} ${q4(n5(certificate))}`,
4308
+ ["verify", "-verbose", "-CAfile", n5(this.caCertificateWithCrl), n5(certificate)],
3868
4309
  options
3869
4310
  );
3870
4311
  }
@@ -4131,7 +4572,7 @@ async function createDefaultCertificate(base_name, prefix, key_length, applicati
4131
4572
  365
4132
4573
  );
4133
4574
  warningLog(" certificate to revoke => ", certificate);
4134
- revoke_certificate(certificate_revoked);
4575
+ await revoke_certificate(certificate_revoked);
4135
4576
  }
4136
4577
  }
4137
4578
  }
@@ -4149,7 +4590,7 @@ async function create_default_certificates(dev) {
4149
4590
  let clientURN;
4150
4591
  let serverURN;
4151
4592
  let discoveryServerURN;
4152
- wrap(async () => {
4593
+ await wrap(async () => {
4153
4594
  await extractFullyQualifiedDomainName();
4154
4595
  const hostname = os5.hostname();
4155
4596
  const fqdn2 = getFullyQualifiedDomainName();