node-opcua-pki 6.20.0 → 6.22.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/index.js CHANGED
@@ -31,11 +31,15 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
31
31
  var lib_exports = {};
32
32
  __export(lib_exports, {
33
33
  CertificateAuthority: () => CertificateAuthority,
34
+ CertificateAuthorityCore: () => CertificateAuthorityCore,
34
35
  CertificateManager: () => CertificateManager,
35
36
  CertificateManagerState: () => CertificateManagerState,
36
37
  ChainCompletionStatus: () => ChainCompletionStatus,
37
- PrivateKeyPassphraseRequiredError: () => import_node_opcua_crypto6.PrivateKeyPassphraseRequiredError,
38
- Subject: () => import_node_opcua_crypto.Subject,
38
+ NativeCaBackend: () => NativeCaBackend,
39
+ OpenSslCaBackend: () => OpenSslCaBackend,
40
+ PrivateKeyPassphraseRequiredError: () => import_node_opcua_crypto10.PrivateKeyPassphraseRequiredError,
41
+ PrivateKeyUnavailableError: () => import_node_opcua_crypto10.PrivateKeyUnavailableError,
42
+ Subject: () => import_node_opcua_crypto3.Subject,
39
43
  VerificationStatus: () => VerificationStatus,
40
44
  adjustApplicationUri: () => adjustApplicationUri,
41
45
  adjustDate: () => adjustDate,
@@ -51,28 +55,61 @@ __export(lib_exports, {
51
55
  install_prerequisite: () => install_prerequisite,
52
56
  isIntermediateIssuer: () => isIntermediateIssuer,
53
57
  isIssuer: () => isIssuer,
58
+ isOpaqueSigner: () => isOpaqueSigner,
54
59
  isRootIssuer: () => isRootIssuer,
55
60
  makeFingerprint: () => makeFingerprint,
56
61
  quote: () => quote,
57
62
  resolvePrivateKeyPassphrase: () => resolvePrivateKeyPassphrase
58
63
  });
59
64
  module.exports = __toCommonJS(lib_exports);
60
- var import_node_opcua_crypto6 = require("node-opcua-crypto");
65
+ var import_node_opcua_crypto10 = require("node-opcua-crypto");
61
66
 
62
- // packages/node-opcua-pki/lib/ca/certificate_authority.ts
63
- var import_node_assert7 = __toESM(require("assert"));
64
- var import_node_fs7 = __toESM(require("fs"));
65
- var import_node_os3 = __toESM(require("os"));
66
- var import_node_path5 = __toESM(require("path"));
67
- var import_chalk5 = __toESM(require("chalk"));
67
+ // packages/node-opcua-pki/lib/ca/backends/native_ca_backend.ts
68
+ var import_node_fs3 = __toESM(require("fs"));
69
+ var import_node_path3 = __toESM(require("path"));
68
70
  var import_node_opcua_crypto2 = require("node-opcua-crypto");
69
71
 
70
- // packages/node-opcua-pki/lib/pki/toolbox_pfx.ts
71
- var import_node_assert3 = __toESM(require("assert"));
72
- var import_node_fs4 = __toESM(require("fs"));
72
+ // packages/node-opcua-pki/lib/toolbox/common.ts
73
+ var import_node_assert = __toESM(require("assert"));
74
+ function quote(str) {
75
+ return `"${str || ""}"`;
76
+ }
77
+ function isOpaqueSigner(privateKey) {
78
+ return typeof privateKey !== "string" && typeof privateKey.sign === "function";
79
+ }
80
+ async function resolvePrivateKeyPassphrase(passphrase) {
81
+ if (passphrase === void 0) {
82
+ return void 0;
83
+ }
84
+ return typeof passphrase === "function" ? await passphrase() : passphrase;
85
+ }
86
+ function adjustDate(params) {
87
+ (0, import_node_assert.default)(params instanceof Object);
88
+ params.startDate = params.startDate || /* @__PURE__ */ new Date();
89
+ (0, import_node_assert.default)(params.startDate instanceof Date);
90
+ if (params.validityMs !== void 0) {
91
+ if (params.validityMs <= 0) {
92
+ throw new RangeError(`validityMs must be > 0 (got ${params.validityMs})`);
93
+ }
94
+ params.endDate = new Date(params.startDate.getTime() + params.validityMs);
95
+ params.validity = Math.ceil(params.validityMs / 864e5);
96
+ } else {
97
+ params.validity = params.validity || 365;
98
+ params.endDate = new Date(params.startDate.getTime());
99
+ params.endDate.setDate(params.startDate.getDate() + params.validity);
100
+ }
101
+ (0, import_node_assert.default)(params.endDate instanceof Date);
102
+ (0, import_node_assert.default)(params.startDate instanceof Date);
103
+ }
104
+ function adjustApplicationUri(params) {
105
+ const applicationUri = params.applicationUri || "";
106
+ if (applicationUri.length > 200) {
107
+ throw new Error(`Openssl doesn't support urn with length greater than 200${applicationUri}`);
108
+ }
109
+ }
73
110
 
74
111
  // packages/node-opcua-pki/lib/toolbox/common2.ts
75
- var import_node_assert = __toESM(require("assert"));
112
+ var import_node_assert2 = __toESM(require("assert"));
76
113
  var import_node_fs = __toESM(require("fs"));
77
114
  var import_node_path = __toESM(require("path"));
78
115
  var import_chalk = __toESM(require("chalk"));
@@ -137,20 +174,515 @@ function makePath(folderName, filename) {
137
174
  if (filename) {
138
175
  s = import_node_path.default.join(import_node_path.default.normalize(folderName), filename);
139
176
  } else {
140
- (0, import_node_assert.default)(folderName);
177
+ (0, import_node_assert2.default)(folderName);
141
178
  s = folderName;
142
179
  }
143
180
  s = s.replace(/\\/g, "/");
144
181
  return s;
145
182
  }
146
183
 
147
- // packages/node-opcua-pki/lib/toolbox/with_openssl/execute_openssl.ts
148
- var import_node_assert2 = __toESM(require("assert"));
149
- var import_node_child_process2 = __toESM(require("child_process"));
150
- var import_node_fs3 = __toESM(require("fs"));
151
- var import_node_os2 = __toESM(require("os"));
152
- var import_byline2 = __toESM(require("byline"));
153
- var import_chalk3 = __toESM(require("chalk"));
184
+ // packages/node-opcua-pki/lib/toolbox/display.ts
185
+ var import_chalk2 = __toESM(require("chalk"));
186
+ function displayTitle(str) {
187
+ if (!g_config.silent) {
188
+ warningLog("");
189
+ warningLog(import_chalk2.default.yellowBright(str));
190
+ warningLog(import_chalk2.default.yellow(new Array(str.length + 1).join("=")), "\n");
191
+ }
192
+ }
193
+ function displaySubtitle(str) {
194
+ if (!g_config.silent) {
195
+ warningLog("");
196
+ warningLog(` ${import_chalk2.default.yellowBright(str)}`);
197
+ warningLog(` ${import_chalk2.default.white(new Array(str.length + 1).join("-"))}`, "\n");
198
+ }
199
+ }
200
+ function display(str) {
201
+ if (!g_config.silent) {
202
+ warningLog(` ${str}`);
203
+ }
204
+ }
205
+
206
+ // packages/node-opcua-pki/lib/ca/core/ca_database.ts
207
+ var import_node_fs2 = __toESM(require("fs"));
208
+ var import_node_path2 = __toESM(require("path"));
209
+ var import_node_opcua_crypto = require("node-opcua-crypto");
210
+ function parseOpenSSLDate(dateStr) {
211
+ const raw = dateStr?.split(",")[0] ?? "";
212
+ if (raw.length < 12) return "";
213
+ const yy = parseInt(raw.substring(0, 2), 10);
214
+ const year = yy >= 70 ? 1900 + yy : 2e3 + yy;
215
+ const month = raw.substring(2, 4);
216
+ const day = raw.substring(4, 6);
217
+ const hour = raw.substring(6, 8);
218
+ const min = raw.substring(8, 10);
219
+ const sec = raw.substring(10, 12);
220
+ return `${year}-${month}-${day}T${hour}:${min}:${sec}Z`;
221
+ }
222
+ function formatOpenSSLDate(date) {
223
+ const yy = String(date.getUTCFullYear() % 100).padStart(2, "0");
224
+ const mm = String(date.getUTCMonth() + 1).padStart(2, "0");
225
+ const dd = String(date.getUTCDate()).padStart(2, "0");
226
+ const hh = String(date.getUTCHours()).padStart(2, "0");
227
+ const mi = String(date.getUTCMinutes()).padStart(2, "0");
228
+ const ss = String(date.getUTCSeconds()).padStart(2, "0");
229
+ return `${yy}${mm}${dd}${hh}${mi}${ss}Z`;
230
+ }
231
+ function parseRevocationReason(dateStr) {
232
+ const parts = dateStr?.split(",");
233
+ return parts && parts.length > 1 ? parts[1] : void 0;
234
+ }
235
+ function evenLengthHex(value) {
236
+ const hex = value.toString(16).toUpperCase();
237
+ return hex.length % 2 === 0 ? hex : `0${hex}`;
238
+ }
239
+ function escapeIndexField(value) {
240
+ return value.replace(/[\x00-\x1f\x7f\\]/g, (c) => `\\x${c.charCodeAt(0).toString(16).toUpperCase().padStart(2, "0")}`);
241
+ }
242
+ function assertHexSerial(serial) {
243
+ if (!/^[0-9A-Fa-f]+$/.test(serial)) {
244
+ throw new Error(`Invalid certificate serial number: ${JSON.stringify(serial)}`);
245
+ }
246
+ return serial.toUpperCase();
247
+ }
248
+ var CaDatabase = class {
249
+ #rootDir;
250
+ constructor(rootDir) {
251
+ this.#rootDir = rootDir;
252
+ }
253
+ /** Path to the OpenSSL certificate database file (`index.txt`). */
254
+ get indexFile() {
255
+ return import_node_path2.default.join(this.#rootDir, "index.txt");
256
+ }
257
+ /**
258
+ * Parse the OpenSSL `index.txt` certificate database.
259
+ *
260
+ * Each line has tab-separated fields:
261
+ * ```
262
+ * status expiry [revocationDate] serial unknown subject
263
+ * ```
264
+ *
265
+ * - status: `V` (valid), `R` (revoked), `E` (expired)
266
+ * - expiry: `YYMMDDHHmmssZ`
267
+ * - revocationDate: present only for revoked certs
268
+ * - serial: hex string
269
+ * - unknown: always `"unknown"`
270
+ * - subject: X.500 slash-delimited string
271
+ */
272
+ readIndex() {
273
+ const indexPath = this.indexFile;
274
+ if (!import_node_fs2.default.existsSync(indexPath)) {
275
+ return [];
276
+ }
277
+ const content = import_node_fs2.default.readFileSync(indexPath, "utf-8");
278
+ const lines = content.split("\n").filter((l) => l.trim().length > 0);
279
+ const records = [];
280
+ for (const line of lines) {
281
+ const fields = line.split(" ");
282
+ if (fields.length < 4) continue;
283
+ const statusChar = fields[0];
284
+ const expiryStr = fields[1];
285
+ let serial;
286
+ let subject;
287
+ let revocationDate;
288
+ if (statusChar === "R") {
289
+ revocationDate = fields[2];
290
+ serial = fields[3];
291
+ subject = fields.length >= 6 ? fields[5] : "";
292
+ } else {
293
+ serial = fields[3];
294
+ subject = fields.length >= 6 ? fields[5] : "";
295
+ }
296
+ let status;
297
+ switch (statusChar) {
298
+ case "V":
299
+ status = "valid";
300
+ break;
301
+ case "R":
302
+ status = "revoked";
303
+ break;
304
+ case "E":
305
+ status = "expired";
306
+ break;
307
+ default:
308
+ continue;
309
+ }
310
+ records.push({
311
+ serial,
312
+ status,
313
+ subject,
314
+ expiryDate: parseOpenSSLDate(expiryStr),
315
+ revocationDate: revocationDate ? parseOpenSSLDate(revocationDate) : void 0,
316
+ reason: revocationDate ? parseRevocationReason(revocationDate) : void 0
317
+ });
318
+ }
319
+ return records;
320
+ }
321
+ /** Look up one record by serial number (case-insensitive). */
322
+ findBySerial(serial) {
323
+ const upper = serial.toUpperCase();
324
+ return this.readIndex().find((r) => r.serial.toUpperCase() === upper);
325
+ }
326
+ /**
327
+ * Read a specific issued certificate by serial number.
328
+ *
329
+ * OpenSSL stores signed certificates in the `certs/` directory using
330
+ * the naming convention `<SERIAL>.pem`.
331
+ *
332
+ * @param serial - hex-encoded serial number (e.g. `"1000"`)
333
+ * @returns the DER buffer, or `undefined` if not found
334
+ */
335
+ getCertificateBySerial(serial) {
336
+ const upper = serial.toUpperCase();
337
+ const certFile = import_node_path2.default.join(this.#rootDir, "certs", `${upper}.pem`);
338
+ if (!import_node_fs2.default.existsSync(certFile)) {
339
+ return void 0;
340
+ }
341
+ const pem = (0, import_node_opcua_crypto.readCertificatePEM)(certFile);
342
+ return (0, import_node_opcua_crypto.convertPEMtoDER)(pem);
343
+ }
344
+ /**
345
+ * Read-increment-write a hex counter file (`serial`/`crlnumber`),
346
+ * returning the value to hand out — matching `openssl ca`'s own
347
+ * behavior: the file holds the *next* value to assign, and is bumped
348
+ * to the following one immediately after being read. A `.old` backup
349
+ * of the pre-bump content is kept, as `openssl` itself does.
350
+ */
351
+ #nextHexCounter(fileName) {
352
+ const counterFile = import_node_path2.default.join(this.#rootDir, fileName);
353
+ const current = BigInt(`0x${import_node_fs2.default.readFileSync(counterFile, "utf-8").trim()}`);
354
+ import_node_fs2.default.copyFileSync(counterFile, `${counterFile}.old`);
355
+ import_node_fs2.default.writeFileSync(counterFile, evenLengthHex(current + 1n));
356
+ return evenLengthHex(current);
357
+ }
358
+ /** Hand out the next certificate serial number, bumping the `serial` file. */
359
+ nextSerial() {
360
+ return this.#nextHexCounter("serial");
361
+ }
362
+ /** Hand out the next CRL number, bumping the `crlnumber` file. */
363
+ nextCrlNumber() {
364
+ return this.#nextHexCounter("crlnumber");
365
+ }
366
+ /** Append a newly-issued certificate's `V` (valid) row to `index.txt`. */
367
+ appendIssued(record) {
368
+ const serial = assertHexSerial(record.serial);
369
+ const subject = escapeIndexField(record.subject);
370
+ const line = `V ${formatOpenSSLDate(record.expiryDate)} ${serial} unknown ${subject}
371
+ `;
372
+ import_node_fs2.default.appendFileSync(this.indexFile, line);
373
+ }
374
+ /**
375
+ * Rewrite a certificate's `index.txt` row from `V` to `R` (revoked).
376
+ * Throws if the serial is not found, or is already revoked — the same
377
+ * "already revoked" case `openssl ca -revoke` itself rejects.
378
+ */
379
+ markRevoked(serial, revocationDate, reason) {
380
+ const upper = assertHexSerial(serial);
381
+ if (!/^[A-Za-z]+$/.test(reason)) {
382
+ throw new Error(`Invalid CRL reason: ${JSON.stringify(reason)}`);
383
+ }
384
+ const lines = import_node_fs2.default.readFileSync(this.indexFile, "utf-8").split("\n").filter((l) => l.trim().length > 0);
385
+ let found = false;
386
+ const rewritten = lines.map((line) => {
387
+ const fields = line.split(" ");
388
+ if (fields.length < 4 || fields[3]?.toUpperCase() !== upper) {
389
+ return line;
390
+ }
391
+ if (fields[0] === "R") {
392
+ throw new Error(`Certificate ${upper} is already revoked`);
393
+ }
394
+ found = true;
395
+ const subject = fields.length >= 6 ? fields[5] : "";
396
+ return `R ${fields[1]} ${formatOpenSSLDate(revocationDate)},${reason} ${upper} unknown ${subject}`;
397
+ });
398
+ if (!found) {
399
+ throw new Error(`Certificate ${upper} not found in the certificate database`);
400
+ }
401
+ import_node_fs2.default.writeFileSync(this.indexFile, `${rewritten.join("\n")}
402
+ `);
403
+ }
404
+ /** Store a signed certificate's PEM under `certs/<SERIAL>.pem`, as `openssl ca` does. */
405
+ storeCertificate(serial, pem) {
406
+ const certFile = import_node_path2.default.join(this.#rootDir, "certs", `${assertHexSerial(serial)}.pem`);
407
+ import_node_fs2.default.writeFileSync(certFile, pem);
408
+ }
409
+ };
410
+
411
+ // packages/node-opcua-pki/lib/ca/backends/native_ca_backend.ts
412
+ var CA_CERT_VALIDITY_DAYS = 3650;
413
+ var RSA_SHA256 = { name: "RSASSA-PKCS1-v1_5", hash: { name: "SHA-256" } };
414
+ function signingAlgorithmOf(key) {
415
+ return (0, import_node_opcua_crypto2.isCaSigner)(key) ? key.algorithm : RSA_SHA256;
416
+ }
417
+ function daysFromNow(notBefore, days) {
418
+ return new Date(notBefore.getTime() + days * 24 * 60 * 60 * 1e3);
419
+ }
420
+ function caSanUri(ca) {
421
+ return `urn:${ca.subject.commonName || "NodeOPCUA-CA"}`;
422
+ }
423
+ function sanFromCsr(csrPem) {
424
+ const request = new import_node_opcua_crypto2.x509.Pkcs10CertificateRequest(csrPem);
425
+ const extension = request.getExtension("2.5.29.17");
426
+ const dns = [];
427
+ const ip = [];
428
+ let applicationUri;
429
+ for (const name of extension?.names.toJSON() ?? []) {
430
+ if (name.type === "dns") {
431
+ dns.push(name.value);
432
+ } else if (name.type === "ip") {
433
+ ip.push(name.value);
434
+ } else if (name.type === "url" && applicationUri === void 0) {
435
+ applicationUri = name.value;
436
+ }
437
+ }
438
+ return { dns, ip, applicationUri };
439
+ }
440
+ var REASON_TO_X509_CRL_REASON = {
441
+ unspecified: import_node_opcua_crypto2.x509.X509CrlReason.unspecified,
442
+ keyCompromise: import_node_opcua_crypto2.x509.X509CrlReason.keyCompromise,
443
+ CACompromise: import_node_opcua_crypto2.x509.X509CrlReason.cACompromise,
444
+ affiliationChanged: import_node_opcua_crypto2.x509.X509CrlReason.affiliationChanged,
445
+ superseded: import_node_opcua_crypto2.x509.X509CrlReason.superseded,
446
+ cessationOfOperation: import_node_opcua_crypto2.x509.X509CrlReason.cessationOfOperation,
447
+ certificateHold: import_node_opcua_crypto2.x509.X509CrlReason.certificateHold,
448
+ removeFromCRL: import_node_opcua_crypto2.x509.X509CrlReason.removeFromCRL
449
+ };
450
+ function toOpenSslSubjectString(subjectName) {
451
+ const parts = [];
452
+ for (const rdn of subjectName.toJSON()) {
453
+ for (const [type, values] of Object.entries(rdn)) {
454
+ for (const value of values) {
455
+ parts.push(`${type}=${value}`);
456
+ }
457
+ }
458
+ }
459
+ return `/${parts.join("/")}`;
460
+ }
461
+ var NativeCaBackend = class {
462
+ /** Nothing to check: this backend spawns no process and needs no tool on PATH. */
463
+ /** Signing goes through `ca._getSigningKey()`, which may be an external signer. */
464
+ supportsExternalSigner = true;
465
+ async preflight() {
466
+ }
467
+ /**
468
+ * Write a CSR for the CA's own key, the `[v3_ca_req]` equivalent. The
469
+ * key comes from the CA rather than from `privateKeyFile`: on a
470
+ * signer-backed CA that file does not exist.
471
+ */
472
+ async generateCaCsr(ca, _caRootDir, _privateKeyFile, csrFile) {
473
+ displayTitle("Generate a certificate request for the CA key");
474
+ const signingKey = await ca._getSigningKey();
475
+ const { csr } = await (0, import_node_opcua_crypto2.createCertificateSigningRequest)({
476
+ privateKey: signingKey,
477
+ subject: ca.subject.toString(),
478
+ applicationUri: caSanUri(ca),
479
+ purpose: import_node_opcua_crypto2.CertificatePurpose.ForCertificateAuthority
480
+ });
481
+ await import_node_fs3.default.promises.writeFile(csrFile, csr);
482
+ }
483
+ async bootstrap(ca) {
484
+ const caRootDir = import_node_path3.default.resolve(ca.rootDir);
485
+ const csrFile = import_node_path3.default.join(caRootDir, "private/cakey.csr");
486
+ await this.generateCaCsr(ca, caRootDir, import_node_path3.default.join(caRootDir, "private/cakey.pem"), csrFile);
487
+ const issuerCA = ca._issuerCA;
488
+ if (issuerCA) {
489
+ displayTitle("Generate CA Certificate (signed by issuer CA)");
490
+ const signWithIssuer = () => this.signSubordinateCsr(issuerCA, csrFile, ca.caCertificate, CA_CERT_VALIDITY_DAYS);
491
+ if (import_node_path3.default.resolve(issuerCA.rootDir) === caRootDir) {
492
+ await signWithIssuer();
493
+ } else {
494
+ await issuerCA._withCaDirectoryLock(signWithIssuer);
495
+ }
496
+ } else {
497
+ displayTitle("Generate CA Certificate (self-signed)");
498
+ const csrPem = await import_node_fs3.default.promises.readFile(csrFile, "utf-8");
499
+ const signingKey = await ca._getSigningKey();
500
+ const request = new import_node_opcua_crypto2.x509.Pkcs10CertificateRequest(csrPem);
501
+ const notBefore = /* @__PURE__ */ new Date();
502
+ const { cert } = await (0, import_node_opcua_crypto2.createCertificateFromCsr)({
503
+ csr: csrPem,
504
+ // the CSR's own parsed subject, so issuer and subject are
505
+ // byte-identical on a self-signed root: re-encoding a string
506
+ // form could differ and break chain building
507
+ issuerName: request.subjectName,
508
+ issuerPublicKey: request.publicKey,
509
+ signingKey,
510
+ signingAlgorithm: signingAlgorithmOf(signingKey),
511
+ notBefore,
512
+ notAfter: daysFromNow(notBefore, CA_CERT_VALIDITY_DAYS),
513
+ purpose: import_node_opcua_crypto2.CertificatePurpose.ForCertificateAuthority,
514
+ applicationUri: caSanUri(ca),
515
+ revocation: {
516
+ crlDistributionUrl: ca.crlDistributionUrl,
517
+ ocspResponderUrl: ca.ocspResponderUrl,
518
+ caIssuersUrl: ca.caIssuersUrl
519
+ }
520
+ });
521
+ await import_node_fs3.default.promises.writeFile(ca.caCertificate, cert);
522
+ }
523
+ displaySubtitle("generate initial CRL (Certificate Revocation List)");
524
+ await this.#regenerateCrlLocked(ca, new CaDatabase(ca.rootDir));
525
+ displayTitle("Create Certificate Authority (CA) ---> DONE");
526
+ }
527
+ /** Sign a subordinate CA's request with this CA's key, the `[v3_ca]` equivalent. */
528
+ async signSubordinateCsr(ca, csrFile, certFile, validityDays) {
529
+ const { signingKey, signingAlgorithm, issuerPublicKey, issuerName } = await this.#issuerContext(ca);
530
+ const csrPem = await import_node_fs3.default.promises.readFile(csrFile, "utf-8");
531
+ const db = new CaDatabase(ca.rootDir);
532
+ const serialNumber = db.nextSerial();
533
+ const notBefore = /* @__PURE__ */ new Date();
534
+ const notAfter = daysFromNow(notBefore, validityDays);
535
+ const { cert } = await (0, import_node_opcua_crypto2.createCertificateFromCsr)({
536
+ csr: csrPem,
537
+ issuerName,
538
+ issuerPublicKey,
539
+ signingKey,
540
+ signingAlgorithm,
541
+ serialNumber,
542
+ notBefore,
543
+ notAfter,
544
+ purpose: import_node_opcua_crypto2.CertificatePurpose.ForCertificateAuthority,
545
+ // the subordinate's own SAN, not the issuer's
546
+ ...sanFromCsr(csrPem),
547
+ revocation: {
548
+ crlDistributionUrl: ca.crlDistributionUrl,
549
+ ocspResponderUrl: ca.ocspResponderUrl,
550
+ caIssuersUrl: ca.caIssuersUrl
551
+ }
552
+ });
553
+ await import_node_fs3.default.promises.writeFile(certFile, cert);
554
+ this.#record(db, serialNumber, cert, notAfter);
555
+ }
556
+ /**
557
+ * Self-sign a certificate with a caller-supplied key file and record it
558
+ * in this CA's database: the native equivalent of `openssl req -new`
559
+ * followed by `openssl ca -selfsign`, which likewise applies the
560
+ * end-entity profile and updates `index.txt`.
561
+ */
562
+ async createSelfSignedCertificate(ca, certificateFile, privateKeyFile, params) {
563
+ displaySubtitle("- the certificate signing request");
564
+ const key = await (0, import_node_opcua_crypto2.privateKeyToCryptoKey)((0, import_node_opcua_crypto2.readPrivateKey)(privateKeyFile, await ca._privateKeyPassphrase()));
565
+ const { csr } = await (0, import_node_opcua_crypto2.createCertificateSigningRequest)({
566
+ privateKey: key,
567
+ subject: params.subject ? new import_node_opcua_crypto2.Subject(params.subject).toString() : ca.subject.toString(),
568
+ dns: params.dns,
569
+ ip: params.ip,
570
+ applicationUri: params.applicationUri,
571
+ purpose: import_node_opcua_crypto2.CertificatePurpose.ForApplication
572
+ });
573
+ displaySubtitle("- creating the self-signed certificate");
574
+ const request = new import_node_opcua_crypto2.x509.Pkcs10CertificateRequest(csr);
575
+ const db = new CaDatabase(ca.rootDir);
576
+ const serialNumber = db.nextSerial();
577
+ const notBefore = params.startDate ?? /* @__PURE__ */ new Date();
578
+ const notAfter = params.endDate ?? daysFromNow(notBefore, params.validity ?? 365);
579
+ const { cert } = await (0, import_node_opcua_crypto2.createCertificateFromCsr)({
580
+ csr,
581
+ issuerName: request.subjectName,
582
+ issuerPublicKey: request.publicKey,
583
+ signingKey: key,
584
+ signingAlgorithm: RSA_SHA256,
585
+ serialNumber,
586
+ notBefore,
587
+ notAfter,
588
+ purpose: import_node_opcua_crypto2.CertificatePurpose.ForApplication,
589
+ ...sanFromCsr(csr)
590
+ });
591
+ await import_node_fs3.default.promises.writeFile(certificateFile, cert);
592
+ this.#record(db, serialNumber, cert, notAfter);
593
+ }
594
+ /** `certs/<SERIAL>.pem` plus the `V` row, exactly as `openssl ca` writes them. */
595
+ #record(db, serialNumber, certPem, notAfter) {
596
+ db.storeCertificate(serialNumber, certPem);
597
+ db.appendIssued({
598
+ serial: serialNumber,
599
+ expiryDate: notAfter,
600
+ subject: toOpenSslSubjectString(new import_node_opcua_crypto2.x509.X509Certificate(certPem).subjectName)
601
+ });
602
+ }
603
+ /**
604
+ * The CA's signing key and issuer identity, resolved fresh on every
605
+ * call and never cached across operations. The identity is read back
606
+ * from the CA certificate on disk so that the issuer field of anything
607
+ * this CA signs is byte-identical to that certificate's subject.
608
+ */
609
+ async #issuerContext(ca) {
610
+ const signingKey = await ca._getSigningKey();
611
+ const caCertPem = await import_node_fs3.default.promises.readFile(ca.caCertificate, "utf-8");
612
+ const caCert = new import_node_opcua_crypto2.x509.X509Certificate(caCertPem);
613
+ return {
614
+ signingKey,
615
+ signingAlgorithm: signingAlgorithmOf(signingKey),
616
+ issuerPublicKey: caCert.publicKey,
617
+ issuerName: caCert.subjectName
618
+ };
619
+ }
620
+ async signEndEntityCsr(ca, certificate, csr, params, sanOverride) {
621
+ const { signingKey, signingAlgorithm, issuerPublicKey, issuerName } = await this.#issuerContext(ca);
622
+ const db = new CaDatabase(ca.rootDir);
623
+ const serialNumber = db.nextSerial();
624
+ const csrPem = await import_node_fs3.default.promises.readFile(csr, "utf-8");
625
+ const notBefore = params.startDate ?? /* @__PURE__ */ new Date();
626
+ const notAfter = params.endDate ?? new Date(notBefore.getTime() + (params.validity ?? 365) * 24 * 60 * 60 * 1e3);
627
+ const { cert } = await (0, import_node_opcua_crypto2.createCertificateFromCsr)({
628
+ csr: csrPem,
629
+ issuerName,
630
+ issuerPublicKey,
631
+ signingKey,
632
+ signingAlgorithm,
633
+ serialNumber,
634
+ notBefore,
635
+ notAfter,
636
+ purpose: import_node_opcua_crypto2.CertificatePurpose.ForApplication,
637
+ dns: sanOverride.dns,
638
+ ip: sanOverride.ip,
639
+ applicationUri: sanOverride.applicationUri,
640
+ revocation: {
641
+ crlDistributionUrl: ca.crlDistributionUrl,
642
+ ocspResponderUrl: ca.ocspResponderUrl,
643
+ caIssuersUrl: ca.caIssuersUrl
644
+ }
645
+ });
646
+ await import_node_fs3.default.promises.writeFile(certificate, cert);
647
+ this.#record(db, serialNumber, cert, notAfter);
648
+ }
649
+ async #regenerateCrlLocked(ca, db) {
650
+ const { signingKey, signingAlgorithm, issuerPublicKey, issuerName } = await this.#issuerContext(ca);
651
+ const crlNumber = db.nextCrlNumber();
652
+ const entries = db.readIndex().filter((r) => r.status === "revoked").map((r) => ({
653
+ serialNumber: r.serial,
654
+ revocationDate: r.revocationDate ? new Date(r.revocationDate) : /* @__PURE__ */ new Date(),
655
+ reason: r.reason ? REASON_TO_X509_CRL_REASON[r.reason] : void 0
656
+ }));
657
+ const { crl } = await (0, import_node_opcua_crypto2.createCrl)({
658
+ issuerName,
659
+ issuerPublicKey,
660
+ signingKey,
661
+ signingAlgorithm,
662
+ crlNumber: BigInt(`0x${crlNumber}`),
663
+ entries
664
+ });
665
+ await import_node_fs3.default.promises.writeFile(ca.revocationList, crl);
666
+ const der = new import_node_opcua_crypto2.x509.X509Crl(crl).rawData;
667
+ await import_node_fs3.default.promises.writeFile(ca.revocationListDER, Buffer.from(der));
668
+ }
669
+ async regenerateCrl(ca) {
670
+ const db = new CaDatabase(ca.rootDir);
671
+ await this.#regenerateCrlLocked(ca, db);
672
+ }
673
+ async revoke(ca, certificate, reason) {
674
+ const certPem = await import_node_fs3.default.promises.readFile(certificate, "utf-8");
675
+ const cert = new import_node_opcua_crypto2.x509.X509Certificate(certPem);
676
+ const db = new CaDatabase(ca.rootDir);
677
+ db.markRevoked(cert.serialNumber, /* @__PURE__ */ new Date(), reason);
678
+ await this.#regenerateCrlLocked(ca, db);
679
+ }
680
+ };
681
+
682
+ // packages/node-opcua-pki/lib/ca/backends/openssl_ca_backend.ts
683
+ var import_node_fs7 = __toESM(require("fs"));
684
+ var import_node_path6 = __toESM(require("path"));
685
+ var import_node_opcua_crypto4 = require("node-opcua-crypto");
154
686
 
155
687
  // packages/node-opcua-pki/lib/toolbox/with_openssl/_env.ts
156
688
  var SAFE_ENV_PASSTHROUGH = /* @__PURE__ */ new Set([
@@ -224,31 +756,33 @@ function getEnvironmentVarNames() {
224
756
  return { key: varName, pattern: `\\$ENV\\:\\:${varName}` };
225
757
  });
226
758
  }
227
- function processAltNames(params) {
228
- params.dns = params.dns || [];
229
- params.ip = params.ip || [];
230
- let subjectAltName = [];
231
- subjectAltName.push(`URI:${params.applicationUri}`);
232
- subjectAltName = [].concat(
233
- subjectAltName,
234
- params.dns.map((d) => `DNS:${d}`)
235
- );
236
- subjectAltName = [].concat(
237
- subjectAltName,
238
- params.ip.map((d) => `IP:${d}`)
239
- );
240
- const subjectAltNameString = subjectAltName.join(", ");
241
- setEnv("ALTNAME", subjectAltNameString);
759
+ function buildSubjectAltNameString(params) {
760
+ return [
761
+ `URI:${params.applicationUri}`,
762
+ ...(params.dns ?? []).map((d) => `DNS:${d}`),
763
+ ...(params.ip ?? []).map((d) => `IP:${d}`)
764
+ ].join(", ");
242
765
  }
243
766
 
767
+ // packages/node-opcua-pki/lib/misc/subject.ts
768
+ var import_node_opcua_crypto3 = require("node-opcua-crypto");
769
+
770
+ // packages/node-opcua-pki/lib/toolbox/with_openssl/execute_openssl.ts
771
+ var import_node_assert3 = __toESM(require("assert"));
772
+ var import_node_child_process2 = __toESM(require("child_process"));
773
+ var import_node_fs5 = __toESM(require("fs"));
774
+ var import_node_os2 = __toESM(require("os"));
775
+ var import_byline2 = __toESM(require("byline"));
776
+ var import_chalk4 = __toESM(require("chalk"));
777
+
244
778
  // packages/node-opcua-pki/lib/toolbox/with_openssl/install_prerequisite.ts
245
779
  var import_node_child_process = __toESM(require("child_process"));
246
- var import_node_fs2 = __toESM(require("fs"));
780
+ var import_node_fs4 = __toESM(require("fs"));
247
781
  var import_node_os = __toESM(require("os"));
248
- var import_node_path2 = __toESM(require("path"));
782
+ var import_node_path4 = __toESM(require("path"));
249
783
  var import_promises = require("stream/promises");
250
784
  var import_byline = __toESM(require("byline"));
251
- var import_chalk2 = __toESM(require("chalk"));
785
+ var import_chalk3 = __toESM(require("chalk"));
252
786
  var import_yauzl = __toESM(require("yauzl"));
253
787
  var doDebug2 = process.env.NODEOPCUAPKIDEBUG || false;
254
788
  async function execute(file, args, cwd) {
@@ -267,7 +801,7 @@ async function execute(file, args, cwd) {
267
801
  output += `${line}
268
802
  `;
269
803
  if (doDebug2) {
270
- process.stdout.write(` stdout ${import_chalk2.default.yellow(line)}
804
+ process.stdout.write(` stdout ${import_chalk3.default.yellow(line)}
271
805
  `);
272
806
  }
273
807
  });
@@ -287,8 +821,8 @@ async function getopensslExecPath() {
287
821
  const exitCode = result1?.exitCode;
288
822
  const output = result1?.output;
289
823
  if (exitCode !== 0) {
290
- warningLog(import_chalk2.default.yellow(" it seems that ") + import_chalk2.default.cyan("openssl") + import_chalk2.default.yellow(" is not installed on your computer "));
291
- warningLog(import_chalk2.default.yellow("Please install it before running this programs"));
824
+ warningLog(import_chalk3.default.yellow(" it seems that ") + import_chalk3.default.cyan("openssl") + import_chalk3.default.yellow(" is not installed on your computer "));
825
+ warningLog(import_chalk3.default.yellow("Please install it before running this programs"));
292
826
  throw new Error("Cannot find openssl");
293
827
  }
294
828
  const opensslExecPath = output.replace(/\n\r/g, "").trim();
@@ -297,7 +831,7 @@ async function getopensslExecPath() {
297
831
  async function check_system_openssl_version() {
298
832
  const opensslExecPath = await getopensslExecPath();
299
833
  if (doDebug2) {
300
- warningLog(` OpenSSL found in : ${import_chalk2.default.yellow(opensslExecPath)}`);
834
+ warningLog(` OpenSSL found in : ${import_chalk3.default.yellow(opensslExecPath)}`);
301
835
  }
302
836
  const result = await execute(opensslExecPath, ["version"]);
303
837
  const exitCode = result?.exitCode;
@@ -305,35 +839,35 @@ async function check_system_openssl_version() {
305
839
  const version = output.trim();
306
840
  const versionOK = exitCode === 0 && is_expected_openssl_version(version);
307
841
  if (!versionOK) {
308
- let message = import_chalk2.default.whiteBright("Warning !!!!!!!!!!!! ") + "\nyour version of openssl is " + version + ". It doesn't match the expected version";
842
+ let message = import_chalk3.default.whiteBright("Warning !!!!!!!!!!!! ") + "\nyour version of openssl is " + version + ". It doesn't match the expected version";
309
843
  if (process.platform === "darwin") {
310
- message += import_chalk2.default.cyan("\nplease refer to :") + import_chalk2.default.yellow(" https://github.com/node-opcua/node-opcua/wiki/installing-node-opcua-or-node-red-on-MacOS");
844
+ message += import_chalk3.default.cyan("\nplease refer to :") + import_chalk3.default.yellow(" https://github.com/node-opcua/node-opcua/wiki/installing-node-opcua-or-node-red-on-MacOS");
311
845
  }
312
846
  console.log(message);
313
847
  }
314
848
  return output;
315
849
  }
316
850
  async function install_and_check_win32_openssl_version() {
317
- const downloadFolder = import_node_path2.default.join(import_node_os.default.tmpdir(), ".");
851
+ const downloadFolder = import_node_path4.default.join(import_node_os.default.tmpdir(), ".");
318
852
  function get_openssl_folder_win32() {
319
853
  if (process.env.LOCALAPPDATA) {
320
- const userProgramFolder = import_node_path2.default.join(process.env.LOCALAPPDATA, "Programs");
321
- if (import_node_fs2.default.existsSync(userProgramFolder)) {
322
- return import_node_path2.default.join(userProgramFolder, "openssl");
854
+ const userProgramFolder = import_node_path4.default.join(process.env.LOCALAPPDATA, "Programs");
855
+ if (import_node_fs4.default.existsSync(userProgramFolder)) {
856
+ return import_node_path4.default.join(userProgramFolder, "openssl");
323
857
  }
324
858
  }
325
- return import_node_path2.default.join(process.cwd(), "openssl");
859
+ return import_node_path4.default.join(process.cwd(), "openssl");
326
860
  }
327
861
  function get_openssl_exec_path_win32() {
328
862
  const opensslFolder2 = get_openssl_folder_win32();
329
- return import_node_path2.default.join(opensslFolder2, "openssl.exe");
863
+ return import_node_path4.default.join(opensslFolder2, "openssl.exe");
330
864
  }
331
865
  async function check_openssl_win32() {
332
866
  const opensslExecPath2 = get_openssl_exec_path_win32();
333
- const exists = import_node_fs2.default.existsSync(opensslExecPath2);
867
+ const exists = import_node_fs4.default.existsSync(opensslExecPath2);
334
868
  if (!exists) {
335
869
  warningLog("checking presence of ", opensslExecPath2);
336
- warningLog(import_chalk2.default.red(" cannot find file ") + opensslExecPath2);
870
+ warningLog(import_chalk3.default.red(" cannot find file ") + opensslExecPath2);
337
871
  return {
338
872
  opensslOk: false,
339
873
  version: `cannot find file ${opensslExecPath2}`
@@ -358,18 +892,18 @@ async function install_and_check_win32_openssl_version() {
358
892
  return void 0;
359
893
  }
360
894
  const opensslPath2 = result.output.split(/\r?\n/)[0].trim();
361
- if (!opensslPath2 || !import_node_fs2.default.existsSync(opensslPath2)) {
895
+ if (!opensslPath2 || !import_node_fs4.default.existsSync(opensslPath2)) {
362
896
  return void 0;
363
897
  }
364
898
  const versionResult = await execute(opensslPath2, ["version"]);
365
899
  const version = versionResult.output.trim();
366
900
  if (versionResult.exitCode === 0 && is_expected_openssl_version(version)) {
367
901
  warningLog(
368
- import_chalk2.default.green("Using system OpenSSL: ") + import_chalk2.default.cyan(version) + import_chalk2.default.green(" at ") + import_chalk2.default.cyan(opensslPath2)
902
+ import_chalk3.default.green("Using system OpenSSL: ") + import_chalk3.default.cyan(version) + import_chalk3.default.green(" at ") + import_chalk3.default.cyan(opensslPath2)
369
903
  );
370
904
  return opensslPath2;
371
905
  }
372
- warningLog(import_chalk2.default.yellow("System OpenSSL found but version not accepted: ") + version);
906
+ warningLog(import_chalk3.default.yellow("System OpenSSL found but version not accepted: ") + version);
373
907
  return void 0;
374
908
  } catch (_err) {
375
909
  return void 0;
@@ -389,9 +923,9 @@ async function install_and_check_win32_openssl_version() {
389
923
  }
390
924
  async function download_openssl() {
391
925
  const url = win32or64() === 64 ? "https://github.com/node-opcua/node-opcua-pki/releases/download/2.14.2/openssl-1.0.2u-x64_86-win64.zip" : "https://github.com/node-opcua/node-opcua-pki/releases/download/2.14.2/openssl-1.0.2u-i386-win32.zip";
392
- const outputFilename = import_node_path2.default.join(downloadFolder, import_node_path2.default.basename(url));
393
- warningLog(`downloading ${import_chalk2.default.yellow(url)} to ${outputFilename}`);
394
- if (import_node_fs2.default.existsSync(outputFilename)) {
926
+ const outputFilename = import_node_path4.default.join(downloadFolder, import_node_path4.default.basename(url));
927
+ warningLog(`downloading ${import_chalk3.default.yellow(url)} to ${outputFilename}`);
928
+ if (import_node_fs4.default.existsSync(outputFilename)) {
395
929
  return { downloadedFile: outputFilename };
396
930
  }
397
931
  const response = await fetch(url, { redirect: "follow" });
@@ -401,7 +935,7 @@ async function install_and_check_win32_openssl_version() {
401
935
  const contentLength = parseInt(response.headers.get("content-length") || "0", 10);
402
936
  let downloaded = 0;
403
937
  let lastPercent = -1;
404
- const fileStream = import_node_fs2.default.createWriteStream(outputFilename);
938
+ const fileStream = import_node_fs4.default.createWriteStream(outputFilename);
405
939
  const body = response.body;
406
940
  body.on("data", (chunk) => {
407
941
  downloaded += chunk.length;
@@ -414,12 +948,12 @@ async function install_and_check_win32_openssl_version() {
414
948
  }
415
949
  });
416
950
  await (0, import_promises.pipeline)(body, fileStream);
417
- const stat = import_node_fs2.default.statSync(outputFilename);
951
+ const stat = import_node_fs4.default.statSync(outputFilename);
418
952
  if (stat.size === 0) {
419
- import_node_fs2.default.unlinkSync(outputFilename);
953
+ import_node_fs4.default.unlinkSync(outputFilename);
420
954
  throw new Error(`Downloaded file is empty: ${outputFilename}`);
421
955
  }
422
- warningLog(import_chalk2.default.green("Download complete: ") + `${stat.size} bytes`);
956
+ warningLog(import_chalk3.default.green("Download complete: ") + `${stat.size} bytes`);
423
957
  return { downloadedFile: outputFilename };
424
958
  }
425
959
  async function unzip_openssl(zipFilename) {
@@ -456,11 +990,11 @@ async function install_and_check_win32_openssl_version() {
456
990
  if (err) {
457
991
  return reject(err);
458
992
  }
459
- const file = import_node_path2.default.join(opensslFolder2, entry.fileName);
993
+ const file = import_node_path4.default.join(opensslFolder2, entry.fileName);
460
994
  if (doDebug2) {
461
995
  warningLog(" unzipping :", file);
462
996
  }
463
- const writeStream = import_node_fs2.default.createWriteStream(file, "binary");
997
+ const writeStream = import_node_fs4.default.createWriteStream(file, "binary");
464
998
  readStream?.pipe(writeStream);
465
999
  writeStream.on("close", () => {
466
1000
  zipFile.readEntry();
@@ -475,29 +1009,29 @@ async function install_and_check_win32_openssl_version() {
475
1009
  }
476
1010
  const opensslFolder = get_openssl_folder_win32();
477
1011
  const opensslExecPath = get_openssl_exec_path_win32();
478
- if (!import_node_fs2.default.existsSync(opensslFolder)) {
1012
+ if (!import_node_fs4.default.existsSync(opensslFolder)) {
479
1013
  if (doDebug2) {
480
1014
  warningLog("creating openssl_folder", opensslFolder);
481
1015
  }
482
- import_node_fs2.default.mkdirSync(opensslFolder);
1016
+ import_node_fs4.default.mkdirSync(opensslFolder);
483
1017
  }
484
1018
  const { opensslOk, version: _version } = await check_openssl_win32();
485
1019
  if (!opensslOk) {
486
- warningLog(import_chalk2.default.yellow("openssl seems to be missing and need to be installed"));
1020
+ warningLog(import_chalk3.default.yellow("openssl seems to be missing and need to be installed"));
487
1021
  const { downloadedFile } = await download_openssl();
488
1022
  if (doDebug2) {
489
- warningLog("deflating ", import_chalk2.default.yellow(downloadedFile));
1023
+ warningLog("deflating ", import_chalk3.default.yellow(downloadedFile));
490
1024
  }
491
1025
  await unzip_openssl(downloadedFile);
492
- const opensslExists = !!import_node_fs2.default.existsSync(opensslExecPath);
1026
+ const opensslExists = !!import_node_fs4.default.existsSync(opensslExecPath);
493
1027
  if (doDebug2) {
494
- warningLog("verifying ", opensslExists, opensslExists ? import_chalk2.default.green("OK ") : import_chalk2.default.red(" Error"), opensslExecPath);
1028
+ warningLog("verifying ", opensslExists, opensslExists ? import_chalk3.default.green("OK ") : import_chalk3.default.red(" Error"), opensslExecPath);
495
1029
  }
496
1030
  const _opensslExecPath2 = await check_openssl_win32();
497
1031
  return opensslExecPath;
498
1032
  } else {
499
1033
  if (doDebug2) {
500
- warningLog(import_chalk2.default.green("openssl is already installed and have the expected version."));
1034
+ warningLog(import_chalk3.default.green("openssl is already installed and have the expected version."));
501
1035
  }
502
1036
  return opensslExecPath;
503
1037
  }
@@ -512,7 +1046,7 @@ async function install_prerequisite() {
512
1046
  async function get_openssl_exec_path() {
513
1047
  if (process.platform === "win32") {
514
1048
  const opensslExecPath = await install_prerequisite();
515
- if (!import_node_fs2.default.existsSync(opensslExecPath)) {
1049
+ if (!import_node_fs4.default.existsSync(opensslExecPath)) {
516
1050
  throw new Error(`internal error cannot find ${opensslExecPath}`);
517
1051
  }
518
1052
  return opensslExecPath;
@@ -525,13 +1059,9 @@ async function get_openssl_exec_path() {
525
1059
  var opensslPath;
526
1060
  var n = makePath;
527
1061
  var PASSIN_ENV_VAR = "NODE_OPCUA_PKI_OPENSSL_PASSIN";
528
- var PASSOUT_ENV_VAR = "NODE_OPCUA_PKI_OPENSSL_PASSOUT";
529
1062
  function passinArg(passphrase = "") {
530
1063
  return { args: ["-passin", `env:${PASSIN_ENV_VAR}`], env: { [PASSIN_ENV_VAR]: passphrase } };
531
1064
  }
532
- function passoutArg(passphrase = "") {
533
- return { args: ["-passout", `env:${PASSOUT_ENV_VAR}`], env: { [PASSOUT_ENV_VAR]: passphrase } };
534
- }
535
1065
  function renderForDisplay(file, args) {
536
1066
  return [file, ...args].map((a) => a === "" || /[\s"'`$\\]/.test(a) ? JSON.stringify(a) : a).join(" ");
537
1067
  }
@@ -539,7 +1069,7 @@ async function execute2(file, args, options) {
539
1069
  const from = new Error();
540
1070
  options.cwd = options.cwd || process.cwd();
541
1071
  if (!g_config.silent) {
542
- warningLog(import_chalk3.default.cyan(" CWD "), options.cwd);
1072
+ warningLog(import_chalk4.default.cyan(" CWD "), options.cwd);
543
1073
  }
544
1074
  const outputs = [];
545
1075
  const errorOutputs = [];
@@ -552,12 +1082,12 @@ async function execute2(file, args, options) {
552
1082
  stdio: ["ignore", "pipe", "pipe"]
553
1083
  });
554
1084
  const fail = (message) => {
555
- if (!options.hideErrorMessage) {
1085
+ if (!options.hideErrorMessage && !g_config.silent) {
556
1086
  const fence = "###########################################";
557
- console.error(import_chalk3.default.bgWhiteBright.redBright(`${fence} OPENSSL ERROR ${fence}`));
558
- console.error(import_chalk3.default.bgWhiteBright.redBright(`CWD = ${options.cwd}`));
559
- console.error(import_chalk3.default.bgWhiteBright.redBright(message));
560
- console.error(import_chalk3.default.bgWhiteBright.redBright(`${fence} OPENSSL ERROR ${fence}`));
1087
+ console.error(import_chalk4.default.bgWhiteBright.redBright(`${fence} OPENSSL ERROR ${fence}`));
1088
+ console.error(import_chalk4.default.bgWhiteBright.redBright(`CWD = ${options.cwd}`));
1089
+ console.error(import_chalk4.default.bgWhiteBright.redBright(message));
1090
+ console.error(import_chalk4.default.bgWhiteBright.redBright(`${fence} OPENSSL ERROR ${fence}`));
561
1091
  console.error(from.stack);
562
1092
  }
563
1093
  reject(new Error(message));
@@ -583,7 +1113,7 @@ ${errorOutputs.join("")}(${why})`);
583
1113
  stream2.on("data", (line) => {
584
1114
  line = line.toString();
585
1115
  if (doDebug) {
586
- process.stdout.write(`${import_chalk3.default.white(" stdout ") + import_chalk3.default.whiteBright(line)}
1116
+ process.stdout.write(`${import_chalk4.default.white(" stdout ") + import_chalk4.default.whiteBright(line)}
587
1117
  `);
588
1118
  }
589
1119
  });
@@ -596,7 +1126,7 @@ ${errorOutputs.join("")}(${why})`);
596
1126
  errorOutputs.push(`${line}
597
1127
  `);
598
1128
  if (!g_config.silent && displayError) {
599
- process.stdout.write(`${import_chalk3.default.white(" stderr ") + import_chalk3.default.red(line)}
1129
+ process.stdout.write(`${import_chalk4.default.white(" stderr ") + import_chalk4.default.red(line)}
600
1130
  `);
601
1131
  }
602
1132
  });
@@ -631,154 +1161,26 @@ function getTempFolder() {
631
1161
  async function execute_openssl(args, options) {
632
1162
  debugLog("execute_openssl", args, redactEnvForLog(options));
633
1163
  const empty_config_file = n(getTempFolder(), "empty_config.cnf");
634
- if (!import_node_fs3.default.existsSync(empty_config_file)) {
635
- await import_node_fs3.default.promises.writeFile(empty_config_file, "# empty config file");
1164
+ if (!import_node_fs5.default.existsSync(empty_config_file)) {
1165
+ await import_node_fs5.default.promises.writeFile(empty_config_file, "# empty config file");
636
1166
  }
637
1167
  options = options || {};
638
1168
  options.openssl_conf = options.openssl_conf || empty_config_file;
639
- (0, import_node_assert2.default)(options.openssl_conf);
1169
+ (0, import_node_assert3.default)(options.openssl_conf);
640
1170
  setEnv("OPENSSL_CONF", options.openssl_conf);
641
1171
  if (!g_config.silent) {
642
- warningLog(import_chalk3.default.cyan(" OPENSSL_CONF"), process.env.OPENSSL_CONF);
643
- warningLog(import_chalk3.default.cyan(" RANDFILE "), process.env.RANDFILE);
644
- warningLog(import_chalk3.default.cyan(" CMD "), import_chalk3.default.cyanBright(renderForDisplay("openssl", args)));
1172
+ warningLog(import_chalk4.default.cyan(" OPENSSL_CONF"), process.env.OPENSSL_CONF);
1173
+ warningLog(import_chalk4.default.cyan(" RANDFILE "), process.env.RANDFILE);
1174
+ warningLog(import_chalk4.default.cyan(" CMD "), import_chalk4.default.cyanBright(renderForDisplay("openssl", args)));
645
1175
  }
646
1176
  await ensure_openssl_installed();
647
1177
  return await execute2(opensslPath, args, options);
648
1178
  }
649
1179
 
650
- // packages/node-opcua-pki/lib/pki/toolbox_pfx.ts
651
- var n2 = makePath;
652
- async function createPFX(options) {
653
- const { certificateFile, privateKeyFile, privateKeyPassphrase, outputFile, passphrase = "", caCertificateFiles } = options;
654
- (0, import_node_assert3.default)(import_node_fs4.default.existsSync(certificateFile), `Certificate file does not exist: ${certificateFile}`);
655
- (0, import_node_assert3.default)(import_node_fs4.default.existsSync(privateKeyFile), `Private key file does not exist: ${privateKeyFile}`);
656
- const args = ["pkcs12", "-export", "-in", n2(certificateFile), "-inkey", n2(privateKeyFile)];
657
- if (caCertificateFiles) {
658
- for (const caFile of caCertificateFiles) {
659
- (0, import_node_assert3.default)(import_node_fs4.default.existsSync(caFile), `CA certificate file does not exist: ${caFile}`);
660
- args.push("-certfile", n2(caFile));
661
- }
662
- }
663
- const passin = passinArg(privateKeyPassphrase);
664
- const passout = passoutArg(passphrase);
665
- args.push("-out", n2(outputFile), ...passin.args, ...passout.args);
666
- await execute_openssl(args, { env: { ...passin.env, ...passout.env } });
667
- }
668
- async function extractCertificateFromPFX(options) {
669
- const { pfxFile, passphrase = "" } = options;
670
- (0, import_node_assert3.default)(import_node_fs4.default.existsSync(pfxFile), `PFX file does not exist: ${pfxFile}`);
671
- const passin = passinArg(passphrase);
672
- return await execute_openssl(["pkcs12", "-in", n2(pfxFile), "-clcerts", "-nokeys", "-nodes", ...passin.args], {
673
- env: passin.env
674
- });
675
- }
676
- async function extractPrivateKeyFromPFX(options) {
677
- const { pfxFile, passphrase = "" } = options;
678
- (0, import_node_assert3.default)(import_node_fs4.default.existsSync(pfxFile), `PFX file does not exist: ${pfxFile}`);
679
- const passin = passinArg(passphrase);
680
- return await execute_openssl(["pkcs12", "-in", n2(pfxFile), "-nocerts", "-nodes", ...passin.args], { env: passin.env });
681
- }
682
- async function extractCACertificatesFromPFX(options) {
683
- const { pfxFile, passphrase = "" } = options;
684
- (0, import_node_assert3.default)(import_node_fs4.default.existsSync(pfxFile), `PFX file does not exist: ${pfxFile}`);
685
- const passin = passinArg(passphrase);
686
- return await execute_openssl(["pkcs12", "-in", n2(pfxFile), "-cacerts", "-nokeys", "-nodes", ...passin.args], {
687
- env: passin.env
688
- });
689
- }
690
- async function extractAllFromPFX(options) {
691
- const [certificate, privateKey, caCertificates] = await Promise.all([
692
- extractCertificateFromPFX(options),
693
- extractPrivateKeyFromPFX(options),
694
- extractCACertificatesFromPFX(options)
695
- ]);
696
- return { certificate, privateKey, caCertificates };
697
- }
698
- async function convertPFXtoPEM(pfxFile, pemFile, passphrase = "") {
699
- (0, import_node_assert3.default)(import_node_fs4.default.existsSync(pfxFile), `PFX file does not exist: ${pfxFile}`);
700
- const passin = passinArg(passphrase);
701
- await execute_openssl(["pkcs12", "-in", n2(pfxFile), "-out", n2(pemFile), "-nodes", ...passin.args], { env: passin.env });
702
- }
703
- async function dumpPFX(pfxFile, passphrase = "") {
704
- (0, import_node_assert3.default)(import_node_fs4.default.existsSync(pfxFile), `PFX file does not exist: ${pfxFile}`);
705
- const passin = passinArg(passphrase);
706
- return await execute_openssl(["pkcs12", "-in", n2(pfxFile), "-info", "-nodes", ...passin.args], { env: passin.env });
707
- }
708
-
709
- // packages/node-opcua-pki/lib/toolbox/common.ts
1180
+ // packages/node-opcua-pki/lib/toolbox/with_openssl/toolbox.ts
710
1181
  var import_node_assert4 = __toESM(require("assert"));
711
- function quote(str) {
712
- return `"${str || ""}"`;
713
- }
714
- async function resolvePrivateKeyPassphrase(passphrase) {
715
- if (passphrase === void 0) {
716
- return void 0;
717
- }
718
- return typeof passphrase === "function" ? await passphrase() : passphrase;
719
- }
720
- function adjustDate(params) {
721
- (0, import_node_assert4.default)(params instanceof Object);
722
- params.startDate = params.startDate || /* @__PURE__ */ new Date();
723
- (0, import_node_assert4.default)(params.startDate instanceof Date);
724
- if (params.validityMs !== void 0) {
725
- if (params.validityMs <= 0) {
726
- throw new RangeError(`validityMs must be > 0 (got ${params.validityMs})`);
727
- }
728
- params.endDate = new Date(params.startDate.getTime() + params.validityMs);
729
- params.validity = Math.ceil(params.validityMs / 864e5);
730
- } else {
731
- params.validity = params.validity || 365;
732
- params.endDate = new Date(params.startDate.getTime());
733
- params.endDate.setDate(params.startDate.getDate() + params.validity);
734
- }
735
- (0, import_node_assert4.default)(params.endDate instanceof Date);
736
- (0, import_node_assert4.default)(params.startDate instanceof Date);
737
- }
738
- function adjustApplicationUri(params) {
739
- const applicationUri = params.applicationUri || "";
740
- if (applicationUri.length > 200) {
741
- throw new Error(`Openssl doesn't support urn with length greater than 200${applicationUri}`);
742
- }
743
- }
744
-
745
- // packages/node-opcua-pki/lib/toolbox/display.ts
746
- var import_chalk4 = __toESM(require("chalk"));
747
- function displayTitle(str) {
748
- if (!g_config.silent) {
749
- warningLog("");
750
- warningLog(import_chalk4.default.yellowBright(str));
751
- warningLog(import_chalk4.default.yellow(new Array(str.length + 1).join("=")), "\n");
752
- }
753
- }
754
- function displaySubtitle(str) {
755
- if (!g_config.silent) {
756
- warningLog("");
757
- warningLog(` ${import_chalk4.default.yellowBright(str)}`);
758
- warningLog(` ${import_chalk4.default.white(new Array(str.length + 1).join("-"))}`, "\n");
759
- }
760
- }
761
- function display(str) {
762
- if (!g_config.silent) {
763
- warningLog(` ${str}`);
764
- }
765
- }
766
-
767
- // packages/node-opcua-pki/lib/toolbox/with_openssl/index.ts
768
- var import_node_constants = __toESM(require("constants"));
769
-
770
- // packages/node-opcua-pki/lib/toolbox/with_openssl/create_certificate_signing_request.ts
771
- var import_node_assert6 = __toESM(require("assert"));
772
1182
  var import_node_fs6 = __toESM(require("fs"));
773
- var import_node_path4 = __toESM(require("path"));
774
-
775
- // packages/node-opcua-pki/lib/misc/subject.ts
776
- var import_node_opcua_crypto = require("node-opcua-crypto");
777
-
778
- // packages/node-opcua-pki/lib/toolbox/with_openssl/toolbox.ts
779
- var import_node_assert5 = __toESM(require("assert"));
780
- var import_node_fs5 = __toESM(require("fs"));
781
- var import_node_path3 = __toESM(require("path"));
1183
+ var import_node_path5 = __toESM(require("path"));
782
1184
  function openssl_require2DigitYearInDate() {
783
1185
  if (!g_config.opensslVersion) {
784
1186
  throw new Error(
@@ -789,29 +1191,40 @@ function openssl_require2DigitYearInDate() {
789
1191
  }
790
1192
  g_config.opensslVersion = "";
791
1193
  var _counter = 0;
792
- function stripConditionalBlocks(template) {
1194
+ function stripConditionalBlocks(template, envOverrides) {
793
1195
  return template.replace(/\{\{#([A-Z_][A-Z0-9_]*)\}\}([\s\S]*?)\{\{\/\1\}\}\r?\n?/g, (_match, key, content) => {
794
- const keep = hasEnv(key) && getEnv(key) !== "";
1196
+ const keep = envOverrides && Object.prototype.hasOwnProperty.call(envOverrides, key) ? envOverrides[key] !== "" : hasEnv(key) && getEnv(key) !== "";
795
1197
  return keep ? content : "";
796
1198
  });
797
1199
  }
798
- function generateStaticConfig(configPath, options) {
1200
+ function generateStaticConfig(configPath, options, envOverrides) {
799
1201
  const prePath = options?.cwd || "";
800
- const originalFilename = !import_node_path3.default.isAbsolute(configPath) ? import_node_path3.default.join(prePath, configPath) : configPath;
801
- let staticConfig = import_node_fs5.default.readFileSync(originalFilename, { encoding: "utf8" });
802
- staticConfig = stripConditionalBlocks(staticConfig);
1202
+ const originalFilename = !import_node_path5.default.isAbsolute(configPath) ? import_node_path5.default.join(prePath, configPath) : configPath;
1203
+ let staticConfig = import_node_fs6.default.readFileSync(originalFilename, { encoding: "utf8" });
1204
+ staticConfig = stripConditionalBlocks(staticConfig, envOverrides);
803
1205
  for (const envVar of getEnvironmentVarNames()) {
804
- staticConfig = staticConfig.replace(new RegExp(envVar.pattern, "gi"), getEnv(envVar.key));
1206
+ if (envOverrides && Object.prototype.hasOwnProperty.call(envOverrides, envVar.key)) {
1207
+ continue;
1208
+ }
1209
+ staticConfig = staticConfig.replace(new RegExp(envVar.pattern, "gi"), () => getEnv(envVar.key));
1210
+ }
1211
+ if (envOverrides) {
1212
+ for (const [key, value] of Object.entries(envOverrides)) {
1213
+ staticConfig = staticConfig.replace(new RegExp(`\\$ENV\\:\\:${key}`, "gi"), () => value);
1214
+ }
805
1215
  }
806
1216
  const staticConfigPath = `${configPath}.${process.pid}-${_counter++}.tmp`;
807
- const temporaryConfigPath = !import_node_path3.default.isAbsolute(configPath) ? import_node_path3.default.join(prePath, staticConfigPath) : staticConfigPath;
808
- import_node_fs5.default.writeFileSync(temporaryConfigPath, staticConfig);
1217
+ const temporaryConfigPath = !import_node_path5.default.isAbsolute(configPath) ? import_node_path5.default.join(prePath, staticConfigPath) : staticConfigPath;
1218
+ import_node_fs6.default.writeFileSync(temporaryConfigPath, staticConfig);
809
1219
  if (options?.cwd) {
810
- return import_node_path3.default.relative(options.cwd, temporaryConfigPath);
1220
+ return import_node_path5.default.relative(options.cwd, temporaryConfigPath);
811
1221
  } else {
812
1222
  return temporaryConfigPath;
813
1223
  }
814
1224
  }
1225
+ async function cleanupStaticConfig(configFile, options) {
1226
+ await import_node_fs6.default.promises.rm(import_node_path5.default.resolve(options?.cwd ?? "", configFile), { force: true });
1227
+ }
815
1228
  function x509Date(date) {
816
1229
  date = date || /* @__PURE__ */ new Date();
817
1230
  const Y = date.getUTCFullYear();
@@ -830,48 +1243,348 @@ function x509Date(date) {
830
1243
  }
831
1244
  }
832
1245
 
833
- // packages/node-opcua-pki/lib/toolbox/with_openssl/create_certificate_signing_request.ts
834
- var n3 = makePath;
835
- async function createCertificateSigningRequestWithOpenSSL(certificateSigningRequestFilename, params) {
836
- (0, import_node_assert6.default)(params);
837
- (0, import_node_assert6.default)(params.rootDir);
838
- (0, import_node_assert6.default)(params.configFile);
839
- (0, import_node_assert6.default)(params.privateKey);
840
- (0, import_node_assert6.default)(typeof params.privateKey === "string");
841
- (0, import_node_assert6.default)(import_node_fs6.default.existsSync(params.configFile), `config file must exist ${params.configFile}`);
842
- (0, import_node_assert6.default)(import_node_fs6.default.existsSync(params.privateKey), `Private key must exist${params.privateKey}`);
843
- (0, import_node_assert6.default)(import_node_fs6.default.existsSync(params.rootDir), "RootDir key must exist");
844
- (0, import_node_assert6.default)(typeof certificateSigningRequestFilename === "string");
845
- processAltNames(params);
846
- const configFile = generateStaticConfig(params.configFile, { cwd: params.rootDir });
847
- const options = { cwd: params.rootDir, openssl_conf: import_node_path4.default.relative(params.rootDir, configFile) };
848
- const subject = params.subject ? new import_node_opcua_crypto.Subject(params.subject).toString() : void 0;
849
- displaySubtitle("- Creating a Certificate Signing Request with openssl");
850
- await execute_openssl(
851
- [
852
- "req",
853
- "-new",
854
- "-sha256",
855
- "-batch",
856
- "-text",
857
- "-config",
858
- n3(configFile),
859
- "-key",
860
- n3(params.privateKey),
861
- ...subject ? ["-subj", subject] : [],
862
- "-out",
863
- n3(certificateSigningRequestFilename)
864
- ],
865
- options
866
- );
1246
+ // packages/node-opcua-pki/lib/ca/backends/openssl_ca_backend.ts
1247
+ var n2 = makePath;
1248
+ function caAltName(ca) {
1249
+ return `URI:urn:${ca.subject.commonName || "NodeOPCUA-CA"}`;
1250
+ }
1251
+ function caConfigEnvOverrides(ca, altName = caAltName(ca)) {
1252
+ const aiaLegs = [];
1253
+ if (ca.ocspResponderUrl) {
1254
+ aiaLegs.push(`OCSP;URI:${ca.ocspResponderUrl}`);
1255
+ }
1256
+ if (ca.caIssuersUrl) {
1257
+ aiaLegs.push(`caIssuers;URI:${ca.caIssuersUrl}`);
1258
+ }
1259
+ return {
1260
+ ALTNAME: altName,
1261
+ CDP_URL: ca.crlDistributionUrl ?? "",
1262
+ AIA_VALUE: aiaLegs.join(",")
1263
+ };
867
1264
  }
1265
+ var OpenSslCaBackend = class {
1266
+ /** This backend is the `openssl` executable, so it has to be there. */
1267
+ /**
1268
+ * `-passin env:` argv and env for an openssl call that has to load this
1269
+ * CA's key. Built here rather than on the CA: the passphrase is the
1270
+ * CA's business, but expressing it as an openssl argument is this
1271
+ * backend's, and the core has no reason to know the flag exists.
1272
+ */
1273
+ async #passin(ca) {
1274
+ return passinArg(await ca._privateKeyPassphrase());
1275
+ }
1276
+ /** The openssl CLI loads its key from a file, so it cannot call out to an HSM. */
1277
+ supportsExternalSigner = false;
1278
+ async preflight() {
1279
+ await ensure_openssl_installed();
1280
+ }
1281
+ /**
1282
+ * Render `conf/caconfig.cnf` once with explicit overrides, run `fn`
1283
+ * with the rendered path, and always remove the temp file. Structural
1284
+ * guarantee that (a) every render carries the three required env
1285
+ * values and (b) no rendered `<name>.<pid>-<n>.tmp` file is ever left
1286
+ * behind — each was previously a per-method discipline.
1287
+ */
1288
+ async #withConfig(ca, altName, fn) {
1289
+ const caRootDir = import_node_path6.default.resolve(ca.rootDir);
1290
+ const options = { cwd: caRootDir };
1291
+ const configFile = generateStaticConfig("conf/caconfig.cnf", options, caConfigEnvOverrides(ca, altName));
1292
+ try {
1293
+ return await fn(configFile, options);
1294
+ } finally {
1295
+ await cleanupStaticConfig(configFile, options);
1296
+ }
1297
+ }
1298
+ async #generateCaCsrWith(ca, configFile, options, privateKeyFile, csrFile) {
1299
+ const passin = await this.#passin(ca);
1300
+ displayTitle("Generate a certificate request for the CA key");
1301
+ await execute_openssl(
1302
+ [
1303
+ "req",
1304
+ "-new",
1305
+ "-sha256",
1306
+ "-extensions",
1307
+ "v3_ca_req",
1308
+ "-config",
1309
+ n2(configFile),
1310
+ "-key",
1311
+ n2(privateKeyFile),
1312
+ "-out",
1313
+ n2(csrFile),
1314
+ "-subj",
1315
+ ca.subject.toString(),
1316
+ ...passin.args
1317
+ ],
1318
+ { ...options, env: passin.env }
1319
+ );
1320
+ }
1321
+ /**
1322
+ * `openssl ca -gencrl` signs the CRL with the CA key it finds through the
1323
+ * config file (`private_key = $dir/private/cakey.pem`), so it needs the
1324
+ * CA passphrase like every other `openssl ca` invocation.
1325
+ */
1326
+ async #regenerateCrlWith(ca, configFile, options) {
1327
+ const passin = await this.#passin(ca);
1328
+ displaySubtitle("regenerate CRL (Certificate Revocation List)");
1329
+ await execute_openssl(["ca", "-gencrl", "-config", n2(configFile), "-out", "crl/revocation_list.crl", ...passin.args], {
1330
+ ...options,
1331
+ env: passin.env
1332
+ });
1333
+ await execute_openssl(
1334
+ ["crl", "-in", "crl/revocation_list.crl", "-out", "crl/revocation_list.der", "-outform", "der"],
1335
+ options
1336
+ );
1337
+ displaySubtitle("Display (Certificate Revocation List)");
1338
+ await execute_openssl(["crl", "-in", n2(ca.revocationList), "-text", "-noout"], options);
1339
+ }
1340
+ async generateCaCsr(ca, _caRootDir, privateKeyFile, csrFile) {
1341
+ await this.#withConfig(
1342
+ ca,
1343
+ caAltName(ca),
1344
+ (configFile, options) => this.#generateCaCsrWith(ca, configFile, options, privateKeyFile, csrFile)
1345
+ );
1346
+ }
1347
+ async bootstrap(ca) {
1348
+ const caRootDir = import_node_path6.default.resolve(ca.rootDir);
1349
+ const privateKeyFilename = import_node_path6.default.join(caRootDir, "private/cakey.pem");
1350
+ const csrFilename = import_node_path6.default.join(caRootDir, "private/cakey.csr");
1351
+ await this.#withConfig(ca, caAltName(ca), async (configFile, options) => {
1352
+ await this.#generateCaCsrWith(ca, configFile, options, privateKeyFilename, csrFilename);
1353
+ const issuerCA = ca._issuerCA;
1354
+ if (issuerCA) {
1355
+ displayTitle("Generate CA Certificate (signed by issuer CA)");
1356
+ const issuerCert = import_node_path6.default.resolve(issuerCA.caCertificate);
1357
+ const issuerKey = import_node_path6.default.resolve(issuerCA.rootDir, "private/cakey.pem");
1358
+ const issuerSerial = import_node_path6.default.resolve(issuerCA.rootDir, "serial");
1359
+ const issuerPassin = await this.#passin(issuerCA);
1360
+ const signWithIssuer = async () => {
1361
+ await execute_openssl(
1362
+ [
1363
+ "x509",
1364
+ "-sha256",
1365
+ "-req",
1366
+ "-days",
1367
+ "3650",
1368
+ "-extensions",
1369
+ "v3_ca",
1370
+ "-extfile",
1371
+ n2(configFile),
1372
+ "-in",
1373
+ "private/cakey.csr",
1374
+ "-CA",
1375
+ n2(issuerCert),
1376
+ "-CAkey",
1377
+ n2(issuerKey),
1378
+ "-CAserial",
1379
+ n2(issuerSerial),
1380
+ "-out",
1381
+ "public/cacert.pem",
1382
+ ...issuerPassin.args
1383
+ ],
1384
+ { ...options, env: issuerPassin.env }
1385
+ );
1386
+ };
1387
+ if (import_node_path6.default.resolve(issuerCA.rootDir) === caRootDir) {
1388
+ await signWithIssuer();
1389
+ } else {
1390
+ await issuerCA._withCaDirectoryLock(signWithIssuer);
1391
+ }
1392
+ } else {
1393
+ displayTitle("Generate CA Certificate (self-signed)");
1394
+ const passin = await this.#passin(ca);
1395
+ await execute_openssl(
1396
+ [
1397
+ "x509",
1398
+ "-sha256",
1399
+ "-req",
1400
+ "-days",
1401
+ "3650",
1402
+ "-extensions",
1403
+ "v3_ca",
1404
+ "-extfile",
1405
+ n2(configFile),
1406
+ "-in",
1407
+ "private/cakey.csr",
1408
+ "-signkey",
1409
+ n2(privateKeyFilename),
1410
+ "-out",
1411
+ "public/cacert.pem",
1412
+ ...passin.args
1413
+ ],
1414
+ { ...options, env: passin.env }
1415
+ );
1416
+ }
1417
+ displaySubtitle("generate initial CRL (Certificate Revocation List)");
1418
+ await this.#regenerateCrlWith(ca, configFile, options);
1419
+ });
1420
+ displayTitle("Create Certificate Authority (CA) ---> DONE");
1421
+ }
1422
+ async regenerateCrl(ca) {
1423
+ await this.#withConfig(ca, caAltName(ca), (configFile, options) => this.#regenerateCrlWith(ca, configFile, options));
1424
+ }
1425
+ async signSubordinateCsr(ca, csrFile, certFile, validityDays) {
1426
+ await this.#withConfig(ca, caAltName(ca), async (configFile, options) => {
1427
+ const caRootDir = options.cwd;
1428
+ const passin = await this.#passin(ca);
1429
+ await execute_openssl(
1430
+ [
1431
+ "x509",
1432
+ "-sha256",
1433
+ "-req",
1434
+ "-days",
1435
+ String(validityDays),
1436
+ "-extensions",
1437
+ "v3_ca",
1438
+ "-extfile",
1439
+ n2(configFile),
1440
+ "-in",
1441
+ n2(csrFile),
1442
+ "-CA",
1443
+ n2(ca.caCertificate),
1444
+ "-CAkey",
1445
+ n2(import_node_path6.default.join(caRootDir, "private/cakey.pem")),
1446
+ "-CAserial",
1447
+ n2(import_node_path6.default.join(caRootDir, "serial")),
1448
+ "-out",
1449
+ n2(certFile),
1450
+ ...passin.args
1451
+ ],
1452
+ { ...options, env: passin.env }
1453
+ );
1454
+ });
1455
+ }
1456
+ async signEndEntityCsr(ca, certificate, csr, params1, sanOverride) {
1457
+ await this.#withConfig(ca, buildSubjectAltNameString(sanOverride), async (configFile, options) => {
1458
+ displaySubtitle("- then we ask the authority to sign the certificate signing request");
1459
+ const passin = await this.#passin(ca);
1460
+ await execute_openssl(
1461
+ [
1462
+ "ca",
1463
+ "-config",
1464
+ configFile,
1465
+ "-startdate",
1466
+ x509Date(params1.startDate),
1467
+ "-enddate",
1468
+ x509Date(params1.endDate),
1469
+ "-batch",
1470
+ "-out",
1471
+ n2(certificate),
1472
+ "-in",
1473
+ n2(csr),
1474
+ ...passin.args
1475
+ ],
1476
+ { ...options, env: passin.env }
1477
+ );
1478
+ displaySubtitle("- dump the certificate for a check");
1479
+ await execute_openssl(["x509", "-in", n2(certificate), "-dates", "-fingerprint", "-purpose", "-noout"], options);
1480
+ });
1481
+ }
1482
+ async revoke(ca, certificate, reason) {
1483
+ setEnv("RANDFILE", import_node_path6.default.join(ca.rootDir, "random.rnd"));
1484
+ await this.#withConfig(ca, caAltName(ca), async (configFile, options) => {
1485
+ displaySubtitle("Revoke certificate");
1486
+ const passin = await this.#passin(ca);
1487
+ await execute_openssl_no_failure(
1488
+ ["ca", "-verbose", "-config", n2(configFile), "-revoke", certificate, "-crl_reason", reason, ...passin.args],
1489
+ { ...options, env: passin.env }
1490
+ );
1491
+ await this.#regenerateCrlWith(ca, configFile, options);
1492
+ displaySubtitle("Verify that certificate is revoked");
1493
+ await execute_openssl_no_failure(
1494
+ [
1495
+ "verify",
1496
+ "-verbose",
1497
+ "-CRLfile",
1498
+ n2(ca.revocationList),
1499
+ "-CAfile",
1500
+ n2(ca.caCertificate),
1501
+ "-crl_check",
1502
+ n2(certificate)
1503
+ ],
1504
+ options
1505
+ );
1506
+ displaySubtitle("Produce CRL in DER form ");
1507
+ await execute_openssl(
1508
+ ["crl", "-in", n2(ca.revocationList), "-out", "crl/revocation_list.der", "-outform", "der"],
1509
+ options
1510
+ );
1511
+ displaySubtitle("Produce CRL in PEM form ");
1512
+ await execute_openssl(
1513
+ ["crl", "-in", n2(ca.revocationList), "-out", "crl/revocation_list.pem", "-outform", "pem", "-text"],
1514
+ options
1515
+ );
1516
+ });
1517
+ }
1518
+ async createSelfSignedCertificate(ca, certificateFile, privateKeyFile, params) {
1519
+ const envOverrides = caConfigEnvOverrides(ca, buildSubjectAltNameString(params));
1520
+ const configFile = generateStaticConfig(ca.configFile, { cwd: ca.rootDir }, envOverrides);
1521
+ const options = {
1522
+ cwd: ca.rootDir,
1523
+ openssl_conf: makePath(configFile)
1524
+ };
1525
+ try {
1526
+ const subject = params.subject ? new import_node_opcua_crypto4.Subject(params.subject).toString() : "";
1527
+ const subjectOptions = subject && subject.length > 1 ? ["-subj", subject] : [];
1528
+ const csrFile = `${certificateFile}_csr`;
1529
+ const passin = await this.#passin(ca);
1530
+ displaySubtitle("- the certificate signing request");
1531
+ await execute_openssl(
1532
+ [
1533
+ "req",
1534
+ "-new",
1535
+ "-sha256",
1536
+ ...subjectOptions,
1537
+ "-batch",
1538
+ "-key",
1539
+ n2(privateKeyFile),
1540
+ "-out",
1541
+ n2(csrFile),
1542
+ ...passin.args
1543
+ ],
1544
+ { ...options, env: passin.env }
1545
+ );
1546
+ displaySubtitle("- creating the self-signed certificate");
1547
+ await execute_openssl(
1548
+ [
1549
+ "ca",
1550
+ "-selfsign",
1551
+ "-keyfile",
1552
+ n2(privateKeyFile),
1553
+ "-startdate",
1554
+ x509Date(params.startDate),
1555
+ "-enddate",
1556
+ x509Date(params.endDate),
1557
+ "-batch",
1558
+ "-out",
1559
+ n2(certificateFile),
1560
+ "-in",
1561
+ n2(csrFile),
1562
+ ...passin.args
1563
+ ],
1564
+ { ...options, env: passin.env }
1565
+ );
1566
+ displaySubtitle("- dump the certificate for a check");
1567
+ await execute_openssl(["x509", "-in", n2(certificateFile), "-dates", "-fingerprint", "-purpose", "-noout"], {});
1568
+ displaySubtitle("- verify self-signed certificate");
1569
+ await execute_openssl_no_failure(["verify", "-verbose", "-CAfile", n2(certificateFile), n2(certificateFile)], options);
1570
+ await import_node_fs7.default.promises.unlink(csrFile);
1571
+ } finally {
1572
+ await cleanupStaticConfig(configFile, { cwd: ca.rootDir });
1573
+ }
1574
+ }
1575
+ };
868
1576
 
869
- // packages/node-opcua-pki/lib/pki/templates/simple_config_template.cnf.ts
870
- var config = '##################################################################################################\n## SIMPLE OPENSSL CONFIG FILE FOR SELF-SIGNED CERTIFICATE GENERATION\n################################################################################################################\n\ndistinguished_name = req_distinguished_name\ndefault_md = sha1\n\ndefault_md = sha256 # The default digest algorithm\n\n[ v3_ca ]\nsubjectKeyIdentifier = hash\nauthorityKeyIdentifier = keyid:always,issuer:always\n\n# authorityKeyIdentifier = keyid\nbasicConstraints = CA:TRUE\nkeyUsage = critical, cRLSign, keyCertSign\nnsComment = "Self-signed Certificate for CA generated by Node-OPCUA Certificate utility"\n#nsCertType = sslCA, emailCA\n#subjectAltName = email:copy\n#issuerAltName = issuer:copy\n#obj = DER:02:03\n# crlDistributionPoints = @crl_info\n# [ crl_info ]\n# URI.0 = http://localhost:8900/crl.pem\nsubjectAltName = $ENV::ALTNAME\n\n[ req ]\ndays = 390\nreq_extensions = v3_req\nx509_extensions = v3_ca\n\n[v3_req]\nbasicConstraints = CA:false\nkeyUsage = critical, nonRepudiation, digitalSignature, keyEncipherment, dataEncipherment\nsubjectAltName = $ENV::ALTNAME\n\n[ v3_ca_signed]\nsubjectKeyIdentifier = hash\nauthorityKeyIdentifier = keyid,issuer\nbasicConstraints = critical, CA:FALSE\nkeyUsage = nonRepudiation, digitalSignature, keyEncipherment, dataEncipherment\nextendedKeyUsage = clientAuth,serverAuth \nnsComment = "certificate generated by Node-OPCUA Certificate utility and signed by a CA"\nsubjectAltName = $ENV::ALTNAME\n[ v3_selfsigned]\nsubjectKeyIdentifier = hash\nauthorityKeyIdentifier = keyid,issuer\nbasicConstraints = critical, CA:FALSE\nkeyUsage = nonRepudiation, digitalSignature, keyEncipherment, dataEncipherment\nextendedKeyUsage = clientAuth,serverAuth \nnsComment = "Self-signed certificate generated by Node-OPCUA Certificate utility"\nsubjectAltName = $ENV::ALTNAME\n[ req_distinguished_name ]\ncountryName = Country Name (2 letter code)\ncountryName_default = FR\ncountryName_min = 2\ncountryName_max = 2\n# stateOrProvinceName = State or Province Name (full name)\n# stateOrProvinceName_default = Ile de France\n# localityName = Locality Name (city, district)\n# localityName_default = Paris\norganizationName = Organization Name (company)\norganizationName_default = NodeOPCUA\n# organizationalUnitName = Organizational Unit Name (department, division)\n# organizationalUnitName_default = R&D\ncommonName = Common Name (hostname, FQDN, IP, or your name)\ncommonName_max = 256\ncommonName_default = NodeOPCUA\n# emailAddress = Email Address\n# emailAddress_max = 40\n# emailAddress_default = node-opcua (at) node-opcua (dot) com\nsubjectAltName = $ENV::ALTNAME';
871
- var simple_config_template_cnf_default = config;
1577
+ // packages/node-opcua-pki/lib/ca/core/certificate_authority_core.ts
1578
+ var import_node_assert5 = __toESM(require("assert"));
1579
+ var import_node_fs8 = __toESM(require("fs"));
1580
+ var import_node_os3 = __toESM(require("os"));
1581
+ var import_node_path7 = __toESM(require("path"));
1582
+ var import_global_mutex = require("@ster5/global-mutex");
1583
+ var import_chalk5 = __toESM(require("chalk"));
1584
+ var import_node_opcua_crypto5 = require("node-opcua-crypto");
872
1585
 
873
1586
  // packages/node-opcua-pki/lib/ca/templates/ca_config_template.cnf.ts
874
- var config2 = `#.........DO NOT MODIFY BY HAND .........................
1587
+ var config = `#.........DO NOT MODIFY BY HAND .........................
875
1588
  [ ca ]
876
1589
  default_ca = CA_default
877
1590
  [ CA_default ]
@@ -896,7 +1609,7 @@ policy = policy_match
896
1609
  # default_enddate = YYMMDDHHMMSSZ
897
1610
  crl_dir = $dir/crl
898
1611
  crl_extensions = crl_ext
899
- crl = $dir/revocation_list.crl # the Revocation list
1612
+ crl = $dir/crl/revocation_list.crl # the Revocation list
900
1613
  crlnumber = $dir/crlnumber # CRL number file
901
1614
  default_crl_days = 30
902
1615
  default_crl_hours = 24
@@ -1002,197 +1715,28 @@ subjectAltName = $ENV::ALTNAME
1002
1715
  #issuerAltName = issuer:copy
1003
1716
  authorityKeyIdentifier = keyid:always,issuer:always
1004
1717
  #authorityInfoAccess = @issuer_info`;
1005
- var ca_config_template_cnf_default = config2;
1718
+ var ca_config_template_cnf_default = config;
1006
1719
 
1007
- // packages/node-opcua-pki/lib/ca/certificate_authority.ts
1720
+ // packages/node-opcua-pki/lib/ca/core/certificate_authority_core.ts
1008
1721
  var defaultSubject = "/C=FR/ST=IDF/L=Paris/O=Local NODE-OPCUA Certificate Authority/CN=NodeOPCUA-CA";
1009
1722
  var configurationFileTemplate = ca_config_template_cnf_default;
1010
- var configurationFileSimpleTemplate = simple_config_template_cnf_default;
1011
1723
  function escapeOpensslConfDoubleQuoted(value) {
1012
1724
  return value.replace(/\\/g, "\\\\").replace(/"/g, '\\"');
1013
1725
  }
1014
1726
  function renderCaConfig(caRootDir) {
1015
1727
  return configurationFileTemplate.replace(/%%ROOT_FOLDER%%/, escapeOpensslConfDoubleQuoted(makePath(caRootDir)));
1016
1728
  }
1017
- var config3 = {
1729
+ var config2 = {
1018
1730
  certificateDir: "INVALID",
1019
1731
  forceCA: false,
1020
1732
  pkiDir: "INVALID"
1021
1733
  };
1022
- var n4 = makePath;
1734
+ var CA_LOCK_MAX_WAIT_MS = 5 * 6e4;
1735
+ var CA_LOCK_RETRY = { minTimeout: 20, maxTimeout: 250 };
1023
1736
  function octetStringToIpAddress(a) {
1024
1737
  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();
1025
1738
  }
1026
- (0, import_node_assert7.default)(octetStringToIpAddress("c07b9179") === "192.123.145.121");
1027
- async function construct_CertificateAuthority(certificateAuthority) {
1028
- const subject = certificateAuthority.subject;
1029
- const caRootDir = import_node_path5.default.resolve(certificateAuthority.rootDir);
1030
- async function make_folders() {
1031
- mkdirRecursiveSync(caRootDir);
1032
- ensurePrivateDirectory(import_node_path5.default.join(caRootDir, "private"));
1033
- mkdirRecursiveSync(import_node_path5.default.join(caRootDir, "public"));
1034
- mkdirRecursiveSync(import_node_path5.default.join(caRootDir, "certs"));
1035
- mkdirRecursiveSync(import_node_path5.default.join(caRootDir, "crl"));
1036
- mkdirRecursiveSync(import_node_path5.default.join(caRootDir, "conf"));
1037
- }
1038
- await make_folders();
1039
- async function construct_default_files() {
1040
- const serial = import_node_path5.default.join(caRootDir, "serial");
1041
- if (!import_node_fs7.default.existsSync(serial)) {
1042
- await import_node_fs7.default.promises.writeFile(serial, "1000");
1043
- }
1044
- const crlNumber = import_node_path5.default.join(caRootDir, "crlnumber");
1045
- if (!import_node_fs7.default.existsSync(crlNumber)) {
1046
- await import_node_fs7.default.promises.writeFile(crlNumber, "1000");
1047
- }
1048
- const indexFile = import_node_path5.default.join(caRootDir, "index.txt");
1049
- if (!import_node_fs7.default.existsSync(indexFile)) {
1050
- await import_node_fs7.default.promises.writeFile(indexFile, "");
1051
- }
1052
- }
1053
- await construct_default_files();
1054
- const caKeyExists = import_node_fs7.default.existsSync(import_node_path5.default.join(caRootDir, "private/cakey.pem"));
1055
- const caCertExists = import_node_fs7.default.existsSync(import_node_path5.default.join(caRootDir, "public/cacert.pem"));
1056
- if (caKeyExists && caCertExists && !config3.forceCA) {
1057
- restrictPrivateFilePermissions(import_node_path5.default.join(caRootDir, "private/cakey.pem"), 384);
1058
- await certificateAuthority._ensurePrivateKeyProtection();
1059
- debugLog("CA private key and certificate already exist ... skipping");
1060
- return;
1061
- }
1062
- if (caKeyExists && !caCertExists) {
1063
- debugLog("CA private key exists but cacert.pem is missing \u2014 rebuilding CA");
1064
- import_node_fs7.default.unlinkSync(import_node_path5.default.join(caRootDir, "private/cakey.pem"));
1065
- const staleCsr = import_node_path5.default.join(caRootDir, "private/cakey.csr");
1066
- if (import_node_fs7.default.existsSync(staleCsr)) {
1067
- import_node_fs7.default.unlinkSync(staleCsr);
1068
- }
1069
- }
1070
- displayTitle("Create Certificate Authority (CA)");
1071
- const indexFileAttr = import_node_path5.default.join(caRootDir, "index.txt.attr");
1072
- if (!import_node_fs7.default.existsSync(indexFileAttr)) {
1073
- await import_node_fs7.default.promises.writeFile(indexFileAttr, "unique_subject = no");
1074
- }
1075
- const caConfigFile = certificateAuthority.configFile;
1076
- if (1) {
1077
- await import_node_fs7.default.promises.writeFile(caConfigFile, renderCaConfig(caRootDir));
1078
- }
1079
- const subjectOpt = ["-subj", subject.toString()];
1080
- const caCommonName = subject.commonName || "NodeOPCUA-CA";
1081
- setEnv("ALTNAME", `URI:urn:${caCommonName}`);
1082
- certificateAuthority._wireRevocationEnvVars();
1083
- const options = { cwd: caRootDir };
1084
- const configFile = generateStaticConfig("conf/caconfig.cnf", options);
1085
- const configOption = ["-config", n4(configFile)];
1086
- const keySize = certificateAuthority.keySize;
1087
- const privateKeyFilename = import_node_path5.default.join(caRootDir, "private/cakey.pem");
1088
- const csrFilename = import_node_path5.default.join(caRootDir, "private/cakey.csr");
1089
- displayTitle(`Generate the CA private Key - ${keySize}`);
1090
- const passin = await certificateAuthority._opensslPassin();
1091
- await (0, import_node_opcua_crypto2.generatePrivateKeyFile)(privateKeyFilename, keySize, { passphrase: await certificateAuthority._privateKeyPassphrase() });
1092
- restrictPrivateFilePermissions(privateKeyFilename, 384);
1093
- displayTitle("Generate a certificate request for the CA key");
1094
- await execute_openssl(
1095
- [
1096
- "req",
1097
- "-new",
1098
- "-sha256",
1099
- "-text",
1100
- "-extensions",
1101
- "v3_ca_req",
1102
- ...configOption,
1103
- "-key",
1104
- n4(privateKeyFilename),
1105
- "-out",
1106
- n4(csrFilename),
1107
- ...subjectOpt,
1108
- ...passin.args
1109
- ],
1110
- { ...options, env: passin.env }
1111
- );
1112
- const issuerCA = certificateAuthority._issuerCA;
1113
- if (issuerCA) {
1114
- displayTitle("Generate CA Certificate (signed by issuer CA)");
1115
- const issuerCert = import_node_path5.default.resolve(issuerCA.caCertificate);
1116
- const issuerKey = import_node_path5.default.resolve(issuerCA.rootDir, "private/cakey.pem");
1117
- const issuerSerial = import_node_path5.default.resolve(issuerCA.rootDir, "serial");
1118
- const issuerPassin = await issuerCA._opensslPassin();
1119
- await execute_openssl(
1120
- [
1121
- "x509",
1122
- "-sha256",
1123
- "-req",
1124
- "-days",
1125
- "3650",
1126
- "-text",
1127
- "-extensions",
1128
- "v3_ca",
1129
- "-extfile",
1130
- n4(configFile),
1131
- "-in",
1132
- "private/cakey.csr",
1133
- "-CA",
1134
- n4(issuerCert),
1135
- "-CAkey",
1136
- n4(issuerKey),
1137
- "-CAserial",
1138
- n4(issuerSerial),
1139
- "-out",
1140
- "public/cacert.pem",
1141
- ...issuerPassin.args
1142
- ],
1143
- { ...options, env: issuerPassin.env }
1144
- );
1145
- } else {
1146
- displayTitle("Generate CA Certificate (self-signed)");
1147
- await execute_openssl(
1148
- [
1149
- "x509",
1150
- "-sha256",
1151
- "-req",
1152
- "-days",
1153
- "3650",
1154
- "-text",
1155
- "-extensions",
1156
- "v3_ca",
1157
- "-extfile",
1158
- n4(configFile),
1159
- "-in",
1160
- "private/cakey.csr",
1161
- "-signkey",
1162
- n4(privateKeyFilename),
1163
- "-out",
1164
- "public/cacert.pem",
1165
- ...passin.args
1166
- ],
1167
- { ...options, env: passin.env }
1168
- );
1169
- }
1170
- displaySubtitle("generate initial CRL (Certificate Revocation List)");
1171
- await regenerateCrl(certificateAuthority.revocationList, configOption, options, passin);
1172
- displayTitle("Create Certificate Authority (CA) ---> DONE");
1173
- }
1174
- async function regenerateCrl(revocationList, configOption, options, passin) {
1175
- displaySubtitle("regenerate CRL (Certificate Revocation List)");
1176
- await execute_openssl(["ca", "-gencrl", ...configOption, "-out", "crl/revocation_list.crl", ...passin.args], {
1177
- ...options,
1178
- env: passin.env
1179
- });
1180
- await execute_openssl(["crl", "-in", "crl/revocation_list.crl", "-out", "crl/revocation_list.der", "-outform", "der"], options);
1181
- displaySubtitle("Display (Certificate Revocation List)");
1182
- await execute_openssl(["crl", "-in", n4(revocationList), "-text", "-noout"], options);
1183
- }
1184
- function parseOpenSSLDate(dateStr) {
1185
- const raw = dateStr?.split(",")[0] ?? "";
1186
- if (raw.length < 12) return "";
1187
- const yy = parseInt(raw.substring(0, 2), 10);
1188
- const year = yy >= 70 ? 1900 + yy : 2e3 + yy;
1189
- const month = raw.substring(2, 4);
1190
- const day = raw.substring(4, 6);
1191
- const hour = raw.substring(6, 8);
1192
- const min = raw.substring(8, 10);
1193
- const sec = raw.substring(10, 12);
1194
- return `${year}-${month}-${day}T${hour}:${min}:${sec}Z`;
1195
- }
1739
+ (0, import_node_assert5.default)(octetStringToIpAddress("c07b9179") === "192.123.145.121");
1196
1740
  function validateRevocationUrl(url, fieldName) {
1197
1741
  if (url === void 0) {
1198
1742
  return void 0;
@@ -1220,7 +1764,7 @@ function validateRevocationUrl(url, fieldName) {
1220
1764
  }
1221
1765
  return url;
1222
1766
  }
1223
- var CertificateAuthority = class {
1767
+ var CertificateAuthorityCore = class {
1224
1768
  /** RSA key size used when generating the CA private key. */
1225
1769
  keySize;
1226
1770
  /** Root filesystem path of the CA directory structure. */
@@ -1237,14 +1781,41 @@ var CertificateAuthority = class {
1237
1781
  /** resolved once (see `privateKeyPassphrase`); `#passphraseResolved` distinguishes "none" from "not yet" */
1238
1782
  #resolvedPassphrase;
1239
1783
  #passphraseResolved = false;
1784
+ /** Signing backend: `openssl` shells out to the CLI, `native` signs in-process. */
1785
+ #backend;
1786
+ /** External signing key (HSM/KMS), when one was supplied instead of a key file. */
1787
+ #signer;
1788
+ /** Read access to `index.txt` / `certs/<SERIAL>.pem`. */
1789
+ #db;
1240
1790
  constructor(options) {
1241
- (0, import_node_assert7.default)(Object.prototype.hasOwnProperty.call(options, "location"));
1242
- (0, import_node_assert7.default)(Object.prototype.hasOwnProperty.call(options, "keySize"));
1791
+ (0, import_node_assert5.default)(Object.prototype.hasOwnProperty.call(options, "location"));
1792
+ (0, import_node_assert5.default)(Object.prototype.hasOwnProperty.call(options, "keySize"));
1243
1793
  this.location = options.location;
1244
1794
  this.keySize = options.keySize || 2048;
1245
- this.subject = new import_node_opcua_crypto2.Subject(options.subject || defaultSubject);
1795
+ this.subject = new import_node_opcua_crypto5.Subject(options.subject || defaultSubject);
1246
1796
  this._issuerCA = options.issuerCA;
1247
1797
  this.#privateKeyPassphrase = options.privateKeyPassphrase;
1798
+ this.#signer = options.signer;
1799
+ if (options.signer) {
1800
+ const algorithm = options.signer.algorithm;
1801
+ if (algorithm.name !== "RSASSA-PKCS1-v1_5" && algorithm.name !== "ECDSA") {
1802
+ throw new Error(
1803
+ `CertificateAuthority: signer algorithm ${algorithm.name} is not supported - use RSASSA-PKCS1-v1_5 or ECDSA.`
1804
+ );
1805
+ }
1806
+ if (algorithm.name === "ECDSA" && !algorithm.namedCurve) {
1807
+ throw new Error(
1808
+ "CertificateAuthority: an ECDSA signer must declare its namedCurve (P-256, P-384 or P-521) - importing the signer's public key needs the curve, and an SPKI import cannot infer it."
1809
+ );
1810
+ }
1811
+ }
1812
+ if (options.signer && !options.backend.supportsExternalSigner) {
1813
+ throw new Error(
1814
+ "CertificateAuthority: this backend cannot sign with an external signer - it loads its key from a file. Use a backend that supports one, such as NativeCaBackend."
1815
+ );
1816
+ }
1817
+ this.#backend = options.backend;
1818
+ this.#db = new CaDatabase(this.location);
1248
1819
  if (options.crlDistributionUrl !== void 0) {
1249
1820
  this.setCrlDistributionUrl(options.crlDistributionUrl);
1250
1821
  }
@@ -1308,42 +1879,17 @@ var CertificateAuthority = class {
1308
1879
  setCaIssuersUrl(url) {
1309
1880
  this._caIssuersUrl = validateRevocationUrl(url, "caIssuersUrl");
1310
1881
  }
1311
- /**
1312
- * @internal
1313
- * Populate the OpenSSL config substitution env vars (`CDP_URL` and
1314
- * `AIA_VALUE`) from the configured URLs, or unset them so the
1315
- * matching `{{#KEY}}...{{/KEY}}` blocks in the templates are
1316
- * stripped. MUST be called before every `generateStaticConfig`
1317
- * invocation that signs a certificate.
1318
- */
1319
- _wireRevocationEnvVars() {
1320
- unsetEnv("CDP_URL");
1321
- unsetEnv("AIA_VALUE");
1322
- if (this._crlDistributionUrl) {
1323
- setEnv("CDP_URL", this._crlDistributionUrl);
1324
- }
1325
- const aiaLegs = [];
1326
- if (this._ocspResponderUrl) {
1327
- aiaLegs.push(`OCSP;URI:${this._ocspResponderUrl}`);
1328
- }
1329
- if (this._caIssuersUrl) {
1330
- aiaLegs.push(`caIssuers;URI:${this._caIssuersUrl}`);
1331
- }
1332
- if (aiaLegs.length > 0) {
1333
- setEnv("AIA_VALUE", aiaLegs.join(","));
1334
- }
1335
- }
1336
1882
  /** Absolute path to the CA root directory (alias for {@link location}). */
1337
1883
  get rootDir() {
1338
1884
  return this.location;
1339
1885
  }
1340
1886
  /** Path to the OpenSSL configuration file (`conf/caconfig.cnf`). */
1341
1887
  get configFile() {
1342
- return import_node_path5.default.normalize(import_node_path5.default.join(this.rootDir, "./conf/caconfig.cnf"));
1888
+ return import_node_path7.default.normalize(import_node_path7.default.join(this.rootDir, "./conf/caconfig.cnf"));
1343
1889
  }
1344
1890
  /** Path to the CA private key (`private/cakey.pem`); may be passphrase-encrypted, see {@link getPrivateKey}. */
1345
1891
  get privateKey() {
1346
- return import_node_path5.default.join(import_node_path5.default.resolve(this.rootDir), "private/cakey.pem");
1892
+ return import_node_path7.default.join(import_node_path7.default.resolve(this.rootDir), "private/cakey.pem");
1347
1893
  }
1348
1894
  /**
1349
1895
  * The CA private key, decrypted with the configured `privateKeyPassphrase`
@@ -1351,7 +1897,51 @@ var CertificateAuthority = class {
1351
1897
  * on an encrypted key with no or the wrong passphrase.
1352
1898
  */
1353
1899
  async getPrivateKey() {
1354
- return (0, import_node_opcua_crypto2.readPrivateKey)(this.privateKey, await this._privateKeyPassphrase());
1900
+ this.#assertKeyIsOnDisk("getPrivateKey");
1901
+ return (0, import_node_opcua_crypto5.readPrivateKey)(this.privateKey, await this._privateKeyPassphrase());
1902
+ }
1903
+ /**
1904
+ * True when this CA signs with an external {@link CaSigner} (HSM/KMS)
1905
+ * rather than with `private/cakey.pem`. There is then no private key
1906
+ * file on disk, and never was one.
1907
+ */
1908
+ get hasExternalSigner() {
1909
+ return this.#signer !== void 0;
1910
+ }
1911
+ /**
1912
+ * Does `cert` certify the key this CA signs with? With the key on disk
1913
+ * that is a private-key match; with a signer there is no private key to
1914
+ * match, so compare the certified public key against the signer's own -
1915
+ * the same question, asked the only way an HSM allows.
1916
+ */
1917
+ async #certificateMatchesOurKey(certPem, certDer) {
1918
+ if (this.#signer) {
1919
+ const ours = Buffer.from(await this.#signer.getPublicKey());
1920
+ const certified = Buffer.from(new import_node_opcua_crypto5.x509.X509Certificate(certPem).publicKey.rawData);
1921
+ return ours.equals(certified);
1922
+ }
1923
+ return (0, import_node_opcua_crypto5.certificateMatchesPrivateKey)(certDer, await this.getPrivateKey());
1924
+ }
1925
+ /** Reject the key-file-only operations up front, rather than failing on a missing file later. */
1926
+ #assertKeyIsOnDisk(what) {
1927
+ if (this.#signer) {
1928
+ throw new Error(
1929
+ `CertificateAuthority.${what} is not available on a signer-backed CA: the private key lives in the signer (HSM/KMS) and is never written to disk.`
1930
+ );
1931
+ }
1932
+ }
1933
+ /**
1934
+ * @internal The key every signing operation goes through. An injected
1935
+ * {@link CaSigner} is returned as-is; otherwise the on-disk key is read
1936
+ * (and decrypted) and handed over as a plain `CryptoKey`. Both are
1937
+ * accepted by the `node-opcua-crypto` signing primitives, so callers
1938
+ * never branch on which one they got.
1939
+ */
1940
+ async _getSigningKey() {
1941
+ if (this.#signer) {
1942
+ return this.#signer;
1943
+ }
1944
+ return (0, import_node_opcua_crypto5.privateKeyToCryptoKey)(await this.getPrivateKey());
1355
1945
  }
1356
1946
  /**
1357
1947
  * Enable, disable, or rotate the passphrase protecting `private/cakey.pem`
@@ -1360,18 +1950,19 @@ var CertificateAuthority = class {
1360
1950
  * passphrase to continue using it.
1361
1951
  */
1362
1952
  async reencryptPrivateKey(oldPassphrase, newPassphrase) {
1953
+ this.#assertKeyIsOnDisk("reencryptPrivateKey");
1363
1954
  const oldPass = await resolvePrivateKeyPassphrase(oldPassphrase);
1364
1955
  const newPass = await resolvePrivateKeyPassphrase(newPassphrase);
1365
- const key = (0, import_node_opcua_crypto2.readPrivateKey)(this.privateKey, oldPass);
1956
+ const key = (0, import_node_opcua_crypto5.readPrivateKey)(this.privateKey, oldPass);
1366
1957
  await this.#rewritePrivateKeyFile(key, newPass);
1367
1958
  }
1368
1959
  async #rewritePrivateKeyFile(privateKey, passphrase) {
1369
1960
  const tmpFilename = `${this.privateKey}.${process.pid}-${Date.now()}.tmp`;
1370
1961
  try {
1371
- await (0, import_node_opcua_crypto2.writePrivateKeyFile)(tmpFilename, privateKey, { passphrase });
1372
- await import_node_fs7.default.promises.rename(tmpFilename, this.privateKey);
1962
+ await (0, import_node_opcua_crypto5.writePrivateKeyFile)(tmpFilename, privateKey, { passphrase });
1963
+ await import_node_fs8.default.promises.rename(tmpFilename, this.privateKey);
1373
1964
  } finally {
1374
- await import_node_fs7.default.promises.rm(tmpFilename, { force: true });
1965
+ await import_node_fs8.default.promises.rm(tmpFilename, { force: true });
1375
1966
  }
1376
1967
  }
1377
1968
  /** @internal resolve the configured passphrase, at most once per instance */
@@ -1382,10 +1973,6 @@ var CertificateAuthority = class {
1382
1973
  }
1383
1974
  return this.#resolvedPassphrase;
1384
1975
  }
1385
- /** @internal `-passin env:` argv + env for every openssl call that loads this CA's key (always emitted, empty when none) */
1386
- async _opensslPassin() {
1387
- return passinArg(await this._privateKeyPassphrase());
1388
- }
1389
1976
  /**
1390
1977
  * @internal On an existing key: encrypt it in place if a passphrase is
1391
1978
  * configured and it is still plaintext (secure by default: the option
@@ -1394,16 +1981,58 @@ var CertificateAuthority = class {
1394
1981
  * first signing operation.
1395
1982
  */
1396
1983
  async _ensurePrivateKeyProtection() {
1397
- if (!import_node_fs7.default.existsSync(this.privateKey)) {
1984
+ if (this.#signer || !import_node_fs8.default.existsSync(this.privateKey)) {
1398
1985
  return;
1399
1986
  }
1400
1987
  if (this.#privateKeyPassphrase !== void 0 && !isEncryptedPrivateKeyFile(this.privateKey)) {
1401
1988
  warningLog("CertificateAuthority: private key is plaintext but a passphrase is configured; encrypting it in place");
1402
- const plaintextKey = (0, import_node_opcua_crypto2.readPrivateKey)(this.privateKey);
1989
+ const plaintextKey = (0, import_node_opcua_crypto5.readPrivateKey)(this.privateKey);
1403
1990
  await this.#rewritePrivateKeyFile(plaintextKey, await this._privateKeyPassphrase());
1404
1991
  }
1405
1992
  await this.getPrivateKey();
1406
1993
  }
1994
+ /**
1995
+ * Acquire a file-based lock on this CA's directory for the duration of
1996
+ * `action` — serializes every operation that mutates the certificate
1997
+ * database (`index.txt`, `serial`, `crlnumber`, `certs/`,
1998
+ * `crl/revocation_list.*`) or the CA's own key/certificate, so
1999
+ * concurrent calls on one `CertificateAuthority` (or two instances
2000
+ * pointed at the same directory, in this or another process) cannot
2001
+ * interleave. Mirrors `CertificateManager.withLock2`.
2002
+ *
2003
+ * Only ever call this from a top-level public operation — nested calls
2004
+ * on the same instance would deadlock, since the underlying file lock
2005
+ * is not reentrant.
2006
+ *
2007
+ * The wait is bounded: a waiter gives up (throws) after
2008
+ * `CA_LOCK_MAX_WAIT_MS`. Without a bound, a holder whose openssl child
2009
+ * hangs would block every CA operation in every process forever and
2010
+ * silently — the lock library's keepalive keeps refreshing the lock
2011
+ * file's mtime as long as the holder process is alive, so stale-lock
2012
+ * recovery never fires for a live-but-stuck holder. A dead holder's
2013
+ * lock goes stale (default 2 minutes) and is taken over well within
2014
+ * this bound.
2015
+ */
2016
+ async #withCaLock(action) {
2017
+ const lockFileName = import_node_path7.default.join(this.rootDir, ".ca.lock");
2018
+ return (0, import_global_mutex.withLock)(
2019
+ {
2020
+ fileToLock: lockFileName,
2021
+ retries: { forever: true, maxRetryTime: CA_LOCK_MAX_WAIT_MS, ...CA_LOCK_RETRY }
2022
+ },
2023
+ action
2024
+ );
2025
+ }
2026
+ /**
2027
+ * @internal Run `action` under this CA's directory lock. For the one
2028
+ * legitimate cross-instance use: a subordinate CA's bootstrap signs its
2029
+ * certificate with `openssl x509 -CAserial`, which read-increment-writes
2030
+ * THIS issuer's `serial` file, and must therefore hold this issuer's
2031
+ * lock (ordering is always subordinate -> issuer, so no cycle).
2032
+ */
2033
+ async _withCaDirectoryLock(action) {
2034
+ return this.#withCaLock(action);
2035
+ }
1407
2036
  /** Path to the CA certificate in PEM format (`public/cacert.pem`). */
1408
2037
  get caCertificate() {
1409
2038
  return makePath(this.rootDir, "./public/cacert.pem");
@@ -1451,8 +2080,8 @@ var CertificateAuthority = class {
1451
2080
  * (call {@link initialize} first).
1452
2081
  */
1453
2082
  getCACertificateDER() {
1454
- const pem = (0, import_node_opcua_crypto2.readCertificatePEM)(this.caCertificate);
1455
- return (0, import_node_opcua_crypto2.convertPEMtoDER)(pem);
2083
+ const pem = (0, import_node_opcua_crypto5.readCertificatePEM)(this.caCertificate);
2084
+ return (0, import_node_opcua_crypto5.convertPEMtoDER)(pem);
1456
2085
  }
1457
2086
  /**
1458
2087
  * Return the CA certificate as a PEM-encoded string.
@@ -1461,7 +2090,7 @@ var CertificateAuthority = class {
1461
2090
  * (call {@link initialize} first).
1462
2091
  */
1463
2092
  getCACertificatePEM() {
1464
- const raw = (0, import_node_opcua_crypto2.readCertificatePEM)(this.caCertificate);
2093
+ const raw = (0, import_node_opcua_crypto5.readCertificatePEM)(this.caCertificate);
1465
2094
  const beginMarker = "-----BEGIN CERTIFICATE-----";
1466
2095
  const idx = raw.indexOf(beginMarker);
1467
2096
  if (idx > 0) {
@@ -1477,10 +2106,10 @@ var CertificateAuthority = class {
1477
2106
  */
1478
2107
  getCRLDER() {
1479
2108
  const crlPath = this.revocationListDER;
1480
- if (!import_node_fs7.default.existsSync(crlPath)) {
2109
+ if (!import_node_fs8.default.existsSync(crlPath)) {
1481
2110
  return Buffer.alloc(0);
1482
2111
  }
1483
- return import_node_fs7.default.readFileSync(crlPath);
2112
+ return import_node_fs8.default.readFileSync(crlPath);
1484
2113
  }
1485
2114
  /**
1486
2115
  * Return the current Certificate Revocation List as a
@@ -1490,10 +2119,10 @@ var CertificateAuthority = class {
1490
2119
  */
1491
2120
  getCRLPEM() {
1492
2121
  const crlPath = this.revocationList;
1493
- if (!import_node_fs7.default.existsSync(crlPath)) {
2122
+ if (!import_node_fs8.default.existsSync(crlPath)) {
1494
2123
  return "";
1495
2124
  }
1496
- const raw = import_node_fs7.default.readFileSync(crlPath, "utf-8");
2125
+ const raw = import_node_fs8.default.readFileSync(crlPath, "utf-8");
1497
2126
  const beginMarker = "-----BEGIN X509 CRL-----";
1498
2127
  const idx = raw.indexOf(beginMarker);
1499
2128
  if (idx > 0) {
@@ -1512,14 +2141,14 @@ var CertificateAuthority = class {
1512
2141
  * expiry date, and (for revoked certs) the revocation date.
1513
2142
  */
1514
2143
  getIssuedCertificates() {
1515
- return this._parseIndexTxt();
2144
+ return this.#db.readIndex();
1516
2145
  }
1517
2146
  /**
1518
2147
  * Return the total number of certificates recorded in
1519
2148
  * `index.txt`.
1520
2149
  */
1521
2150
  getIssuedCertificateCount() {
1522
- return this._parseIndexTxt().length;
2151
+ return this.#db.readIndex().length;
1523
2152
  }
1524
2153
  /**
1525
2154
  * Return the status of a certificate by its serial number.
@@ -1529,9 +2158,7 @@ var CertificateAuthority = class {
1529
2158
  * `undefined` if not found
1530
2159
  */
1531
2160
  getCertificateStatus(serial) {
1532
- const upper = serial.toUpperCase();
1533
- const record = this._parseIndexTxt().find((r) => r.serial.toUpperCase() === upper);
1534
- return record?.status;
2161
+ return this.#db.findBySerial(serial)?.status;
1535
2162
  }
1536
2163
  /**
1537
2164
  * Read a specific issued certificate by serial number and
@@ -1540,86 +2167,17 @@ var CertificateAuthority = class {
1540
2167
  * OpenSSL stores signed certificates in the `certs/`
1541
2168
  * directory using the naming convention `<SERIAL>.pem`.
1542
2169
  *
1543
- * @param serial - hex-encoded serial number (e.g. `"1000"`)
1544
- * @returns the DER buffer, or `undefined` if not found
1545
- */
1546
- getCertificateBySerial(serial) {
1547
- const upper = serial.toUpperCase();
1548
- const certFile = import_node_path5.default.join(this.rootDir, "certs", `${upper}.pem`);
1549
- if (!import_node_fs7.default.existsSync(certFile)) {
1550
- return void 0;
1551
- }
1552
- const pem = (0, import_node_opcua_crypto2.readCertificatePEM)(certFile);
1553
- return (0, import_node_opcua_crypto2.convertPEMtoDER)(pem);
1554
- }
1555
- /**
1556
- * Path to the OpenSSL certificate database file.
1557
- */
1558
- get indexFile() {
1559
- return import_node_path5.default.join(this.rootDir, "index.txt");
1560
- }
1561
- /**
1562
- * Parse the OpenSSL `index.txt` certificate database.
1563
- *
1564
- * Each line has tab-separated fields:
1565
- * ```
1566
- * status expiry [revocationDate] serial unknown subject
1567
- * ```
1568
- *
1569
- * - status: `V` (valid), `R` (revoked), `E` (expired)
1570
- * - expiry: `YYMMDDHHmmssZ`
1571
- * - revocationDate: present only for revoked certs
1572
- * - serial: hex string
1573
- * - unknown: always `"unknown"`
1574
- * - subject: X.500 slash-delimited string
1575
- */
1576
- _parseIndexTxt() {
1577
- const indexPath = this.indexFile;
1578
- if (!import_node_fs7.default.existsSync(indexPath)) {
1579
- return [];
1580
- }
1581
- const content = import_node_fs7.default.readFileSync(indexPath, "utf-8");
1582
- const lines = content.split("\n").filter((l) => l.trim().length > 0);
1583
- const records = [];
1584
- for (const line of lines) {
1585
- const fields = line.split(" ");
1586
- if (fields.length < 4) continue;
1587
- const statusChar = fields[0];
1588
- const expiryStr = fields[1];
1589
- let serial;
1590
- let subject;
1591
- let revocationDate;
1592
- if (statusChar === "R") {
1593
- revocationDate = fields[2];
1594
- serial = fields[3];
1595
- subject = fields.length >= 6 ? fields[5] : "";
1596
- } else {
1597
- serial = fields[3];
1598
- subject = fields.length >= 6 ? fields[5] : "";
1599
- }
1600
- let status;
1601
- switch (statusChar) {
1602
- case "V":
1603
- status = "valid";
1604
- break;
1605
- case "R":
1606
- status = "revoked";
1607
- break;
1608
- case "E":
1609
- status = "expired";
1610
- break;
1611
- default:
1612
- continue;
1613
- }
1614
- records.push({
1615
- serial,
1616
- status,
1617
- subject,
1618
- expiryDate: parseOpenSSLDate(expiryStr),
1619
- revocationDate: revocationDate ? parseOpenSSLDate(revocationDate) : void 0
1620
- });
1621
- }
1622
- return records;
2170
+ * @param serial - hex-encoded serial number (e.g. `"1000"`)
2171
+ * @returns the DER buffer, or `undefined` if not found
2172
+ */
2173
+ getCertificateBySerial(serial) {
2174
+ return this.#db.getCertificateBySerial(serial);
2175
+ }
2176
+ /**
2177
+ * Path to the OpenSSL certificate database file.
2178
+ */
2179
+ get indexFile() {
2180
+ return this.#db.indexFile;
1623
2181
  }
1624
2182
  // ---------------------------------------------------------------
1625
2183
  // Buffer-based CA operations (US-058)
@@ -1641,12 +2199,12 @@ var CertificateAuthority = class {
1641
2199
  * @returns the signed certificate as a DER-encoded buffer
1642
2200
  */
1643
2201
  async signCertificateRequestFromDER(csrDer, options) {
1644
- const tmpDir = await import_node_fs7.default.promises.mkdtemp(import_node_path5.default.join(import_node_os3.default.tmpdir(), "pki-sign-"));
2202
+ const tmpDir = await import_node_fs8.default.promises.mkdtemp(import_node_path7.default.join(import_node_os3.default.tmpdir(), "pki-sign-"));
1645
2203
  try {
1646
- const csrFile = import_node_path5.default.join(tmpDir, "request.csr");
1647
- const certFile = import_node_path5.default.join(tmpDir, "certificate.pem");
1648
- const csrPem = (0, import_node_opcua_crypto2.toPem)(csrDer, "CERTIFICATE REQUEST");
1649
- await import_node_fs7.default.promises.writeFile(csrFile, csrPem, "utf-8");
2204
+ const csrFile = import_node_path7.default.join(tmpDir, "request.csr");
2205
+ const certFile = import_node_path7.default.join(tmpDir, "certificate.pem");
2206
+ const csrPem = (0, import_node_opcua_crypto5.toPem)(csrDer, "CERTIFICATE REQUEST");
2207
+ await import_node_fs8.default.promises.writeFile(csrFile, csrPem, "utf-8");
1650
2208
  const signingParams = {};
1651
2209
  if (options?.validityMs !== void 0) signingParams.validityMs = options.validityMs;
1652
2210
  else signingParams.validity = options?.validity ?? 365;
@@ -1656,10 +2214,10 @@ var CertificateAuthority = class {
1656
2214
  if (options?.applicationUri) signingParams.applicationUri = options.applicationUri;
1657
2215
  if (options?.subject) signingParams.subject = options.subject;
1658
2216
  await this.signCertificateRequest(certFile, csrFile, signingParams);
1659
- const certPem = (0, import_node_opcua_crypto2.readCertificatePEM)(certFile);
1660
- return (0, import_node_opcua_crypto2.convertPEMtoDER)(certPem);
2217
+ const certPem = (0, import_node_opcua_crypto5.readCertificatePEM)(certFile);
2218
+ return (0, import_node_opcua_crypto5.convertPEMtoDER)(certPem);
1661
2219
  } finally {
1662
- await import_node_fs7.default.promises.rm(tmpDir, {
2220
+ await import_node_fs8.default.promises.rm(tmpDir, {
1663
2221
  recursive: true,
1664
2222
  force: true
1665
2223
  });
@@ -1709,27 +2267,35 @@ var CertificateAuthority = class {
1709
2267
  * @returns `{ certificateDer, privateKey }` — certificate as DER,
1710
2268
  * private key as a branded `PrivateKey` buffer
1711
2269
  */
2270
+ /**
2271
+ * An ephemeral key and a CSR for it, written into `tmpDir`. Both
2272
+ * `generateKeyPairAndSign*` methods need exactly this, and neither
2273
+ * needs a subprocess for it: the key comes from node's crypto and the
2274
+ * request is built and self-signed in process, so no `openssl.cnf` has
2275
+ * to be rendered either.
2276
+ */
2277
+ async #createEphemeralKeyAndCsr(tmpDir, keySize, options) {
2278
+ const privateKeyFile = import_node_path7.default.join(tmpDir, "private_key.pem");
2279
+ await (0, import_node_opcua_crypto5.generatePrivateKeyFile)(privateKeyFile, keySize);
2280
+ const { csr } = await (0, import_node_opcua_crypto5.createCertificateSigningRequest)({
2281
+ privateKey: await (0, import_node_opcua_crypto5.privateKeyToCryptoKey)((0, import_node_opcua_crypto5.readPrivateKey)(privateKeyFile)),
2282
+ subject: options.subject ? new import_node_opcua_crypto5.Subject(options.subject).toString() : void 0,
2283
+ applicationUri: options.applicationUri,
2284
+ dns: options.dns ?? [],
2285
+ ip: options.ip ?? [],
2286
+ purpose: import_node_opcua_crypto5.CertificatePurpose.ForApplication
2287
+ });
2288
+ const csrFile = import_node_path7.default.join(tmpDir, "request.csr");
2289
+ await import_node_fs8.default.promises.writeFile(csrFile, csr);
2290
+ return { privateKeyFile, csrFile };
2291
+ }
1712
2292
  async generateKeyPairAndSignDER(options) {
1713
2293
  const keySize = options.keySize ?? 2048;
1714
2294
  const startDate = options.startDate ?? /* @__PURE__ */ new Date();
1715
- const tmpDir = await import_node_fs7.default.promises.mkdtemp(import_node_path5.default.join(import_node_os3.default.tmpdir(), "pki-keygen-"));
2295
+ const tmpDir = await import_node_fs8.default.promises.mkdtemp(import_node_path7.default.join(import_node_os3.default.tmpdir(), "pki-keygen-"));
1716
2296
  try {
1717
- const privateKeyFile = import_node_path5.default.join(tmpDir, "private_key.pem");
1718
- await (0, import_node_opcua_crypto2.generatePrivateKeyFile)(privateKeyFile, keySize);
1719
- const configFile = import_node_path5.default.join(tmpDir, "openssl.cnf");
1720
- await import_node_fs7.default.promises.writeFile(configFile, configurationFileSimpleTemplate, "utf-8");
1721
- const csrFile = import_node_path5.default.join(tmpDir, "request.csr");
1722
- await createCertificateSigningRequestWithOpenSSL(csrFile, {
1723
- rootDir: tmpDir,
1724
- configFile,
1725
- privateKey: privateKeyFile,
1726
- applicationUri: options.applicationUri,
1727
- subject: options.subject,
1728
- dns: options.dns ?? [],
1729
- ip: options.ip ?? [],
1730
- purpose: import_node_opcua_crypto2.CertificatePurpose.ForApplication
1731
- });
1732
- const certFile = import_node_path5.default.join(tmpDir, "certificate.pem");
2297
+ const { privateKeyFile, csrFile } = await this.#createEphemeralKeyAndCsr(tmpDir, keySize, options);
2298
+ const certFile = import_node_path7.default.join(tmpDir, "certificate.pem");
1733
2299
  const signingParams = {
1734
2300
  applicationUri: options.applicationUri,
1735
2301
  dns: options.dns,
@@ -1739,12 +2305,12 @@ var CertificateAuthority = class {
1739
2305
  if (options.validityMs !== void 0) signingParams.validityMs = options.validityMs;
1740
2306
  else signingParams.validity = options.validity ?? 365;
1741
2307
  await this.signCertificateRequest(certFile, csrFile, signingParams);
1742
- const certPem = (0, import_node_opcua_crypto2.readCertificatePEM)(certFile);
1743
- const certificateDer = (0, import_node_opcua_crypto2.convertPEMtoDER)(certPem);
1744
- const privateKey = (0, import_node_opcua_crypto2.readPrivateKey)(privateKeyFile);
2308
+ const certPem = (0, import_node_opcua_crypto5.readCertificatePEM)(certFile);
2309
+ const certificateDer = (0, import_node_opcua_crypto5.convertPEMtoDER)(certPem);
2310
+ const privateKey = (0, import_node_opcua_crypto5.readPrivateKey)(privateKeyFile);
1745
2311
  return { certificateDer, privateKey };
1746
2312
  } finally {
1747
- await import_node_fs7.default.promises.rm(tmpDir, {
2313
+ await import_node_fs8.default.promises.rm(tmpDir, {
1748
2314
  recursive: true,
1749
2315
  force: true
1750
2316
  });
@@ -1765,24 +2331,10 @@ var CertificateAuthority = class {
1765
2331
  const keySize = options.keySize ?? 2048;
1766
2332
  const startDate = options.startDate ?? /* @__PURE__ */ new Date();
1767
2333
  const passphrase = options.passphrase ?? "";
1768
- const tmpDir = await import_node_fs7.default.promises.mkdtemp(import_node_path5.default.join(import_node_os3.default.tmpdir(), "pki-keygen-pfx-"));
2334
+ const tmpDir = await import_node_fs8.default.promises.mkdtemp(import_node_path7.default.join(import_node_os3.default.tmpdir(), "pki-keygen-pfx-"));
1769
2335
  try {
1770
- const privateKeyFile = import_node_path5.default.join(tmpDir, "private_key.pem");
1771
- await (0, import_node_opcua_crypto2.generatePrivateKeyFile)(privateKeyFile, keySize);
1772
- const configFile = import_node_path5.default.join(tmpDir, "openssl.cnf");
1773
- await import_node_fs7.default.promises.writeFile(configFile, configurationFileSimpleTemplate, "utf-8");
1774
- const csrFile = import_node_path5.default.join(tmpDir, "request.csr");
1775
- await createCertificateSigningRequestWithOpenSSL(csrFile, {
1776
- rootDir: tmpDir,
1777
- configFile,
1778
- privateKey: privateKeyFile,
1779
- applicationUri: options.applicationUri,
1780
- subject: options.subject,
1781
- dns: options.dns ?? [],
1782
- ip: options.ip ?? [],
1783
- purpose: import_node_opcua_crypto2.CertificatePurpose.ForApplication
1784
- });
1785
- const certFile = import_node_path5.default.join(tmpDir, "certificate.pem");
2336
+ const { privateKeyFile, csrFile } = await this.#createEphemeralKeyAndCsr(tmpDir, keySize, options);
2337
+ const certFile = import_node_path7.default.join(tmpDir, "certificate.pem");
1786
2338
  const signingParams = {
1787
2339
  applicationUri: options.applicationUri,
1788
2340
  dns: options.dns,
@@ -1792,17 +2344,20 @@ var CertificateAuthority = class {
1792
2344
  if (options.validityMs !== void 0) signingParams.validityMs = options.validityMs;
1793
2345
  else signingParams.validity = options.validity ?? 365;
1794
2346
  await this.signCertificateRequest(certFile, csrFile, signingParams);
1795
- const pfxFile = import_node_path5.default.join(tmpDir, "bundle.pfx");
1796
- await createPFX({
1797
- certificateFile: certFile,
1798
- privateKeyFile,
1799
- outputFile: pfxFile,
1800
- passphrase,
1801
- caCertificateFiles: [this.caCertificate]
2347
+ const blocks = (await import_node_fs8.default.promises.readFile(certFile, "utf-8")).match(
2348
+ /-----BEGIN CERTIFICATE-----[\s\S]*?-----END CERTIFICATE-----/g
2349
+ );
2350
+ if (!blocks || blocks.length === 0) {
2351
+ throw new Error(`generateKeyPairAndSignPFX: no certificate was produced in ${certFile}`);
2352
+ }
2353
+ return await (0, import_node_opcua_crypto5.createPfx)({
2354
+ certificate: (0, import_node_opcua_crypto5.convertPEMtoDER)(blocks[0]),
2355
+ certificateChain: blocks.slice(1).map(import_node_opcua_crypto5.convertPEMtoDER),
2356
+ privateKey: (0, import_node_opcua_crypto5.readPrivateKey)(privateKeyFile),
2357
+ password: passphrase
1802
2358
  });
1803
- return await import_node_fs7.default.promises.readFile(pfxFile);
1804
2359
  } finally {
1805
- await import_node_fs7.default.promises.rm(tmpDir, {
2360
+ await import_node_fs8.default.promises.rm(tmpDir, {
1806
2361
  recursive: true,
1807
2362
  force: true
1808
2363
  });
@@ -1821,10 +2376,10 @@ var CertificateAuthority = class {
1821
2376
  * @throws if the certificate's serial is not found in the CA
1822
2377
  */
1823
2378
  async revokeCertificateDER(certDer, reason) {
1824
- const info = (0, import_node_opcua_crypto2.exploreCertificate)(certDer);
2379
+ const info = (0, import_node_opcua_crypto5.exploreCertificate)(certDer);
1825
2380
  const serial = info.tbsCertificate.serialNumber.replace(/:/g, "").toUpperCase();
1826
- const storedCertFile = import_node_path5.default.join(this.rootDir, "certs", `${serial}.pem`);
1827
- if (!import_node_fs7.default.existsSync(storedCertFile)) {
2381
+ const storedCertFile = import_node_path7.default.join(this.rootDir, "certs", `${serial}.pem`);
2382
+ if (!import_node_fs8.default.existsSync(storedCertFile)) {
1828
2383
  throw new Error(`Cannot revoke: no stored certificate found for serial ${serial} at ${storedCertFile}`);
1829
2384
  }
1830
2385
  await this.revokeCertificate(storedCertFile, {
@@ -1837,7 +2392,69 @@ var CertificateAuthority = class {
1837
2392
  * already exist.
1838
2393
  */
1839
2394
  async initialize() {
1840
- await construct_CertificateAuthority(this);
2395
+ mkdirRecursiveSync(import_node_path7.default.resolve(this.rootDir));
2396
+ await this.#withCaLock(() => this.#bootstrap());
2397
+ }
2398
+ /**
2399
+ * @internal Shared (backend-agnostic) part of `initialize()`: directory
2400
+ * layout, default database files, the "already initialized" / "partial
2401
+ * init" checks, and the openssl config file — then delegates CSR
2402
+ * generation, CA-certificate signing, and the initial CRL to
2403
+ * {@link CaBackend.bootstrap}. Must be called under {@link #withCaLock}.
2404
+ */
2405
+ async #bootstrap() {
2406
+ const caRootDir = import_node_path7.default.resolve(this.rootDir);
2407
+ mkdirRecursiveSync(caRootDir);
2408
+ ensurePrivateDirectory(import_node_path7.default.join(caRootDir, "private"));
2409
+ mkdirRecursiveSync(import_node_path7.default.join(caRootDir, "public"));
2410
+ mkdirRecursiveSync(import_node_path7.default.join(caRootDir, "certs"));
2411
+ mkdirRecursiveSync(import_node_path7.default.join(caRootDir, "crl"));
2412
+ mkdirRecursiveSync(import_node_path7.default.join(caRootDir, "conf"));
2413
+ const serial = import_node_path7.default.join(caRootDir, "serial");
2414
+ if (!import_node_fs8.default.existsSync(serial)) {
2415
+ await import_node_fs8.default.promises.writeFile(serial, "1000");
2416
+ }
2417
+ const crlNumber = import_node_path7.default.join(caRootDir, "crlnumber");
2418
+ if (!import_node_fs8.default.existsSync(crlNumber)) {
2419
+ await import_node_fs8.default.promises.writeFile(crlNumber, "1000");
2420
+ }
2421
+ const indexFile = import_node_path7.default.join(caRootDir, "index.txt");
2422
+ if (!import_node_fs8.default.existsSync(indexFile)) {
2423
+ await import_node_fs8.default.promises.writeFile(indexFile, "");
2424
+ }
2425
+ const signerBacked = this.hasExternalSigner;
2426
+ const caKeyExists = signerBacked || import_node_fs8.default.existsSync(import_node_path7.default.join(caRootDir, "private/cakey.pem"));
2427
+ const caCertExists = import_node_fs8.default.existsSync(import_node_path7.default.join(caRootDir, "public/cacert.pem"));
2428
+ if (caKeyExists && caCertExists && !config2.forceCA) {
2429
+ if (!signerBacked) {
2430
+ restrictPrivateFilePermissions(import_node_path7.default.join(caRootDir, "private/cakey.pem"), 384);
2431
+ await this._ensurePrivateKeyProtection();
2432
+ }
2433
+ debugLog("CA private key and certificate already exist ... skipping");
2434
+ return;
2435
+ }
2436
+ if (!signerBacked && caKeyExists && !caCertExists) {
2437
+ debugLog("CA private key exists but cacert.pem is missing \u2014 rebuilding CA");
2438
+ import_node_fs8.default.unlinkSync(import_node_path7.default.join(caRootDir, "private/cakey.pem"));
2439
+ const staleCsr = import_node_path7.default.join(caRootDir, "private/cakey.csr");
2440
+ if (import_node_fs8.default.existsSync(staleCsr)) {
2441
+ import_node_fs8.default.unlinkSync(staleCsr);
2442
+ }
2443
+ }
2444
+ displayTitle("Create Certificate Authority (CA)");
2445
+ const indexFileAttr = import_node_path7.default.join(caRootDir, "index.txt.attr");
2446
+ if (!import_node_fs8.default.existsSync(indexFileAttr)) {
2447
+ await import_node_fs8.default.promises.writeFile(indexFileAttr, "unique_subject = no");
2448
+ }
2449
+ const caConfigFile = this.configFile;
2450
+ await import_node_fs8.default.promises.writeFile(caConfigFile, renderCaConfig(caRootDir));
2451
+ if (!signerBacked) {
2452
+ const privateKeyFilename = import_node_path7.default.join(caRootDir, "private/cakey.pem");
2453
+ displayTitle(`Generate the CA private Key - ${this.keySize}`);
2454
+ await (0, import_node_opcua_crypto5.generatePrivateKeyFile)(privateKeyFilename, this.keySize, { passphrase: await this._privateKeyPassphrase() });
2455
+ restrictPrivateFilePermissions(privateKeyFilename, 384);
2456
+ }
2457
+ await this.#backend.bootstrap(this);
1841
2458
  }
1842
2459
  /**
1843
2460
  * Initialize the CA directory structure and generate the
@@ -1859,22 +2476,26 @@ var CertificateAuthority = class {
1859
2476
  * @returns an {@link InitializeCSRResult} describing the CA state
1860
2477
  */
1861
2478
  async initializeCSR() {
1862
- const caRootDir = import_node_path5.default.resolve(this.rootDir);
2479
+ const caRootDir = import_node_path7.default.resolve(this.rootDir);
1863
2480
  mkdirRecursiveSync(caRootDir);
2481
+ return this.#withCaLock(() => this.#initializeCSRLocked(caRootDir));
2482
+ }
2483
+ async #initializeCSRLocked(caRootDir) {
1864
2484
  for (const dir of ["public", "certs", "crl", "conf"]) {
1865
- mkdirRecursiveSync(import_node_path5.default.join(caRootDir, dir));
2485
+ mkdirRecursiveSync(import_node_path7.default.join(caRootDir, dir));
1866
2486
  }
1867
- ensurePrivateDirectory(import_node_path5.default.join(caRootDir, "private"));
2487
+ ensurePrivateDirectory(import_node_path7.default.join(caRootDir, "private"));
1868
2488
  const caCertFile = this.caCertificate;
1869
- const privateKeyFile = import_node_path5.default.join(caRootDir, "private/cakey.pem");
1870
- const csrFile = import_node_path5.default.join(caRootDir, "private/cakey.csr");
1871
- if (import_node_fs7.default.existsSync(privateKeyFile)) {
2489
+ const privateKeyFile = import_node_path7.default.join(caRootDir, "private/cakey.pem");
2490
+ const csrFile = import_node_path7.default.join(caRootDir, "private/cakey.csr");
2491
+ const keyAvailable = this.hasExternalSigner || import_node_fs8.default.existsSync(privateKeyFile);
2492
+ if (!this.hasExternalSigner && import_node_fs8.default.existsSync(privateKeyFile)) {
1872
2493
  restrictPrivateFilePermissions(privateKeyFile, 384);
1873
2494
  await this._ensurePrivateKeyProtection();
1874
2495
  }
1875
- if (import_node_fs7.default.existsSync(caCertFile)) {
1876
- const certDer = (0, import_node_opcua_crypto2.convertPEMtoDER)((0, import_node_opcua_crypto2.readCertificatePEM)(caCertFile));
1877
- const certInfo = (0, import_node_opcua_crypto2.exploreCertificate)(certDer);
2496
+ if (import_node_fs8.default.existsSync(caCertFile)) {
2497
+ const certDer = (0, import_node_opcua_crypto5.convertPEMtoDER)((0, import_node_opcua_crypto5.readCertificatePEM)(caCertFile));
2498
+ const certInfo = (0, import_node_opcua_crypto5.exploreCertificate)(certDer);
1878
2499
  const notAfter = certInfo.tbsCertificate.validity.notAfter;
1879
2500
  if (notAfter.getTime() < Date.now()) {
1880
2501
  debugLog("CA certificate has expired \u2014 generating renewal CSR");
@@ -1884,30 +2505,30 @@ var CertificateAuthority = class {
1884
2505
  debugLog("CA certificate already exists and is valid \u2014 ready");
1885
2506
  return { status: "ready" };
1886
2507
  }
1887
- if (import_node_fs7.default.existsSync(privateKeyFile) && import_node_fs7.default.existsSync(csrFile)) {
2508
+ if (keyAvailable && import_node_fs8.default.existsSync(csrFile)) {
1888
2509
  debugLog("CA key + CSR already exist \u2014 pending external signing");
1889
2510
  return { status: "pending", csrPath: csrFile };
1890
2511
  }
1891
- const serial = import_node_path5.default.join(caRootDir, "serial");
1892
- if (!import_node_fs7.default.existsSync(serial)) {
1893
- await import_node_fs7.default.promises.writeFile(serial, "1000");
2512
+ const serial = import_node_path7.default.join(caRootDir, "serial");
2513
+ if (!import_node_fs8.default.existsSync(serial)) {
2514
+ await import_node_fs8.default.promises.writeFile(serial, "1000");
1894
2515
  }
1895
- const crlNumber = import_node_path5.default.join(caRootDir, "crlnumber");
1896
- if (!import_node_fs7.default.existsSync(crlNumber)) {
1897
- await import_node_fs7.default.promises.writeFile(crlNumber, "1000");
2516
+ const crlNumber = import_node_path7.default.join(caRootDir, "crlnumber");
2517
+ if (!import_node_fs8.default.existsSync(crlNumber)) {
2518
+ await import_node_fs8.default.promises.writeFile(crlNumber, "1000");
1898
2519
  }
1899
- const indexFile = import_node_path5.default.join(caRootDir, "index.txt");
1900
- if (!import_node_fs7.default.existsSync(indexFile)) {
1901
- await import_node_fs7.default.promises.writeFile(indexFile, "");
2520
+ const indexFile = import_node_path7.default.join(caRootDir, "index.txt");
2521
+ if (!import_node_fs8.default.existsSync(indexFile)) {
2522
+ await import_node_fs8.default.promises.writeFile(indexFile, "");
1902
2523
  }
1903
- const indexFileAttr = import_node_path5.default.join(caRootDir, "index.txt.attr");
1904
- if (!import_node_fs7.default.existsSync(indexFileAttr)) {
1905
- await import_node_fs7.default.promises.writeFile(indexFileAttr, "unique_subject = no");
2524
+ const indexFileAttr = import_node_path7.default.join(caRootDir, "index.txt.attr");
2525
+ if (!import_node_fs8.default.existsSync(indexFileAttr)) {
2526
+ await import_node_fs8.default.promises.writeFile(indexFileAttr, "unique_subject = no");
1906
2527
  }
1907
2528
  const caConfigFile = this.configFile;
1908
- await import_node_fs7.default.promises.writeFile(caConfigFile, renderCaConfig(caRootDir));
1909
- if (!import_node_fs7.default.existsSync(privateKeyFile)) {
1910
- await (0, import_node_opcua_crypto2.generatePrivateKeyFile)(privateKeyFile, this.keySize, { passphrase: await this._privateKeyPassphrase() });
2529
+ await import_node_fs8.default.promises.writeFile(caConfigFile, renderCaConfig(caRootDir));
2530
+ if (!keyAvailable) {
2531
+ await (0, import_node_opcua_crypto5.generatePrivateKeyFile)(privateKeyFile, this.keySize, { passphrase: await this._privateKeyPassphrase() });
1911
2532
  restrictPrivateFilePermissions(privateKeyFile, 384);
1912
2533
  }
1913
2534
  await this._generateCSR(caRootDir, privateKeyFile, csrFile);
@@ -1927,53 +2548,34 @@ var CertificateAuthority = class {
1927
2548
  * renewal is needed, `"ready"` if the cert is still valid
1928
2549
  */
1929
2550
  async renewCSR(thresholdDays = 30) {
1930
- const caRootDir = import_node_path5.default.resolve(this.rootDir);
2551
+ const caRootDir = import_node_path7.default.resolve(this.rootDir);
1931
2552
  const caCertFile = this.caCertificate;
1932
- const privateKeyFile = import_node_path5.default.join(caRootDir, "private/cakey.pem");
1933
- const csrFile = import_node_path5.default.join(caRootDir, "private/cakey.csr");
1934
- if (!import_node_fs7.default.existsSync(caCertFile)) {
2553
+ const privateKeyFile = import_node_path7.default.join(caRootDir, "private/cakey.pem");
2554
+ const csrFile = import_node_path7.default.join(caRootDir, "private/cakey.csr");
2555
+ if (!import_node_fs8.default.existsSync(caCertFile)) {
1935
2556
  return this.initializeCSR();
1936
2557
  }
1937
- const certDer = (0, import_node_opcua_crypto2.convertPEMtoDER)((0, import_node_opcua_crypto2.readCertificatePEM)(caCertFile));
1938
- const certInfo = (0, import_node_opcua_crypto2.exploreCertificate)(certDer);
2558
+ const certDer = (0, import_node_opcua_crypto5.convertPEMtoDER)((0, import_node_opcua_crypto5.readCertificatePEM)(caCertFile));
2559
+ const certInfo = (0, import_node_opcua_crypto5.exploreCertificate)(certDer);
1939
2560
  const notAfter = certInfo.tbsCertificate.validity.notAfter;
1940
2561
  const thresholdMs = thresholdDays * 24 * 60 * 60 * 1e3;
1941
2562
  if (notAfter.getTime() - Date.now() < thresholdMs) {
1942
2563
  debugLog(`CA certificate expires within ${thresholdDays} days \u2014 generating renewal CSR`);
1943
- await this._generateCSR(caRootDir, privateKeyFile, csrFile);
2564
+ await this.#withCaLock(() => this._generateCSR(caRootDir, privateKeyFile, csrFile));
1944
2565
  return { status: "expired", csrPath: csrFile, expiryDate: notAfter };
1945
2566
  }
1946
2567
  return { status: "ready" };
1947
2568
  }
1948
2569
  /**
1949
2570
  * Generate a CSR using the existing private key.
2571
+ * Must be called under {@link #withCaLock} — the lock is taken by the
2572
+ * public callers (`initializeCSR`, `renewCSR`), not here, so that
2573
+ * `initializeCSR` can hold one lock across key generation AND CSR
2574
+ * generation without the non-reentrant file lock deadlocking.
1950
2575
  * @internal
1951
2576
  */
1952
2577
  async _generateCSR(caRootDir, privateKeyFile, csrFile) {
1953
- processAltNames({});
1954
- const options = { cwd: caRootDir };
1955
- const configFile = generateStaticConfig("conf/caconfig.cnf", options);
1956
- const passin = await this._opensslPassin();
1957
- await execute_openssl(
1958
- [
1959
- "req",
1960
- "-new",
1961
- "-sha256",
1962
- "-text",
1963
- "-extensions",
1964
- "v3_ca_req",
1965
- "-config",
1966
- n4(configFile),
1967
- "-key",
1968
- n4(privateKeyFile),
1969
- "-out",
1970
- n4(csrFile),
1971
- "-subj",
1972
- this.subject.toString(),
1973
- ...passin.args
1974
- ],
1975
- { ...options, env: passin.env }
1976
- );
2578
+ await this.#backend.generateCaCsr(this, caRootDir, privateKeyFile, csrFile);
1977
2579
  }
1978
2580
  /**
1979
2581
  * Install an externally-signed CA certificate and generate
@@ -1992,43 +2594,41 @@ var CertificateAuthority = class {
1992
2594
  * `status: "success"` or `status: "error"` and a `reason`
1993
2595
  */
1994
2596
  async installCACertificate(signedCertFile) {
1995
- const caRootDir = import_node_path5.default.resolve(this.rootDir);
1996
- const caCertFile = this.caCertificate;
1997
- const fullPem = await import_node_fs7.default.promises.readFile(signedCertFile, "utf8");
1998
- const pemBlocks = fullPem.match(/-----BEGIN CERTIFICATE-----[\s\S]*?-----END CERTIFICATE-----/g);
1999
- if (!pemBlocks || pemBlocks.length === 0) {
2000
- return {
2001
- status: "error",
2002
- reason: "no_certificate_found",
2003
- message: "The provided file does not contain any PEM-encoded certificate."
2004
- };
2005
- }
2006
- const certDer = (0, import_node_opcua_crypto2.convertPEMtoDER)(pemBlocks[0]);
2007
- const privateKey = await this.getPrivateKey();
2008
- if (!(0, import_node_opcua_crypto2.certificateMatchesPrivateKey)(certDer, privateKey)) {
2009
- return {
2010
- status: "error",
2011
- reason: "certificate_key_mismatch",
2012
- message: "The provided certificate does not match the CA private key. Ensure the certificate was signed from the CSR generated by initializeCSR()."
2013
- };
2014
- }
2015
- await import_node_fs7.default.promises.writeFile(caCertFile, `${pemBlocks[0]}
2597
+ return this.#withCaLock(async () => {
2598
+ const caCertFile = this.caCertificate;
2599
+ const fullPem = await import_node_fs8.default.promises.readFile(signedCertFile, "utf8");
2600
+ const pemBlocks = fullPem.match(/-----BEGIN CERTIFICATE-----[\s\S]*?-----END CERTIFICATE-----/g);
2601
+ if (!pemBlocks || pemBlocks.length === 0) {
2602
+ return {
2603
+ status: "error",
2604
+ reason: "no_certificate_found",
2605
+ message: "The provided file does not contain any PEM-encoded certificate."
2606
+ };
2607
+ }
2608
+ const certDer = (0, import_node_opcua_crypto5.convertPEMtoDER)(pemBlocks[0]);
2609
+ if (!await this.#certificateMatchesOurKey(pemBlocks[0], certDer)) {
2610
+ return {
2611
+ status: "error",
2612
+ reason: "certificate_key_mismatch",
2613
+ message: "The provided certificate does not match the CA private key. Ensure the certificate was signed from the CSR generated by initializeCSR()."
2614
+ };
2615
+ }
2616
+ await import_node_fs8.default.promises.writeFile(caCertFile, `${pemBlocks[0]}
2016
2617
  `);
2017
- const issuerChainFile = this.issuerCertificateChain;
2018
- if (pemBlocks.length > 1) {
2019
- const issuerPem = `${pemBlocks.slice(1).join("\n")}
2618
+ const issuerChainFile = this.issuerCertificateChain;
2619
+ if (pemBlocks.length > 1) {
2620
+ const issuerPem = `${pemBlocks.slice(1).join("\n")}
2020
2621
  `;
2021
- await import_node_fs7.default.promises.writeFile(issuerChainFile, issuerPem);
2022
- debugLog(`Stored ${pemBlocks.length - 1} issuer certificate(s) in issuer_chain.pem`);
2023
- } else {
2024
- if (import_node_fs7.default.existsSync(issuerChainFile)) {
2025
- await import_node_fs7.default.promises.unlink(issuerChainFile);
2622
+ await import_node_fs8.default.promises.writeFile(issuerChainFile, issuerPem);
2623
+ debugLog(`Stored ${pemBlocks.length - 1} issuer certificate(s) in issuer_chain.pem`);
2624
+ } else {
2625
+ if (import_node_fs8.default.existsSync(issuerChainFile)) {
2626
+ await import_node_fs8.default.promises.unlink(issuerChainFile);
2627
+ }
2026
2628
  }
2027
- }
2028
- const options = { cwd: caRootDir };
2029
- const configFile = generateStaticConfig("conf/caconfig.cnf", options);
2030
- await regenerateCrl(this.revocationList, ["-config", n4(configFile)], options, await this._opensslPassin());
2031
- return { status: "success" };
2629
+ await this.#backend.regenerateCrl(this);
2630
+ return { status: "success" };
2631
+ });
2032
2632
  }
2033
2633
  /**
2034
2634
  * Sign a CSR with CA extensions (`v3_ca`), producing a
@@ -2044,39 +2644,11 @@ var CertificateAuthority = class {
2044
2644
  * @param params - signing parameters
2045
2645
  */
2046
2646
  async signCACertificateRequest(certFile, csrFile, params) {
2047
- const caRootDir = import_node_path5.default.resolve(this.rootDir);
2048
- const options = { cwd: caRootDir };
2049
- this._wireRevocationEnvVars();
2050
- const configFile = generateStaticConfig("conf/caconfig.cnf", options);
2051
- const validity = params.validity ?? 3650;
2052
- const passin = await this._opensslPassin();
2053
- await execute_openssl(
2054
- [
2055
- "x509",
2056
- "-sha256",
2057
- "-req",
2058
- "-days",
2059
- String(validity),
2060
- "-text",
2061
- "-extensions",
2062
- "v3_ca",
2063
- "-extfile",
2064
- n4(configFile),
2065
- "-in",
2066
- n4(csrFile),
2067
- "-CA",
2068
- n4(this.caCertificate),
2069
- "-CAkey",
2070
- n4(import_node_path5.default.join(caRootDir, "private/cakey.pem")),
2071
- "-CAserial",
2072
- n4(import_node_path5.default.join(caRootDir, "serial")),
2073
- "-out",
2074
- n4(certFile),
2075
- ...passin.args
2076
- ],
2077
- { ...options, env: passin.env }
2078
- );
2079
- await this.constructCertificateChain(certFile);
2647
+ await this.#withCaLock(async () => {
2648
+ const validity = params.validity ?? 3650;
2649
+ await this.#backend.signSubordinateCsr(this, csrFile, certFile, validity);
2650
+ await this.constructCertificateChain(certFile);
2651
+ });
2080
2652
  }
2081
2653
  /**
2082
2654
  * Rebuild the combined CA certificate + CRL file.
@@ -2087,13 +2659,13 @@ var CertificateAuthority = class {
2087
2659
  */
2088
2660
  async constructCACertificateWithCRL() {
2089
2661
  const cacertWithCRL = this.caCertificateWithCrl;
2090
- if (import_node_fs7.default.existsSync(this.revocationList)) {
2091
- await import_node_fs7.default.promises.writeFile(
2662
+ if (import_node_fs8.default.existsSync(this.revocationList)) {
2663
+ await import_node_fs8.default.promises.writeFile(
2092
2664
  cacertWithCRL,
2093
- import_node_fs7.default.readFileSync(this.caCertificate, "utf8") + import_node_fs7.default.readFileSync(this.revocationList, "utf8")
2665
+ import_node_fs8.default.readFileSync(this.caCertificate, "utf8") + import_node_fs8.default.readFileSync(this.revocationList, "utf8")
2094
2666
  );
2095
2667
  } else {
2096
- await import_node_fs7.default.promises.writeFile(cacertWithCRL, import_node_fs7.default.readFileSync(this.caCertificate));
2668
+ await import_node_fs8.default.promises.writeFile(cacertWithCRL, import_node_fs8.default.readFileSync(this.caCertificate));
2097
2669
  }
2098
2670
  }
2099
2671
  /**
@@ -2103,15 +2675,15 @@ var CertificateAuthority = class {
2103
2675
  * @param certificate - path to the certificate file to extend
2104
2676
  */
2105
2677
  async constructCertificateChain(certificate) {
2106
- (0, import_node_assert7.default)(import_node_fs7.default.existsSync(certificate));
2107
- (0, import_node_assert7.default)(import_node_fs7.default.existsSync(this.caCertificate));
2678
+ (0, import_node_assert5.default)(import_node_fs8.default.existsSync(certificate));
2679
+ (0, import_node_assert5.default)(import_node_fs8.default.existsSync(this.caCertificate));
2108
2680
  debugLog(import_chalk5.default.yellow(" certificate file :"), import_chalk5.default.cyan(certificate));
2109
- let chain = await import_node_fs7.default.promises.readFile(certificate, "utf8");
2110
- chain += await import_node_fs7.default.promises.readFile(this.caCertificate, "utf8");
2111
- if (import_node_fs7.default.existsSync(this.issuerCertificateChain)) {
2112
- chain += await import_node_fs7.default.promises.readFile(this.issuerCertificateChain, "utf8");
2681
+ let chain = await import_node_fs8.default.promises.readFile(certificate, "utf8");
2682
+ chain += await import_node_fs8.default.promises.readFile(this.caCertificate, "utf8");
2683
+ if (import_node_fs8.default.existsSync(this.issuerCertificateChain)) {
2684
+ chain += await import_node_fs8.default.promises.readFile(this.issuerCertificateChain, "utf8");
2113
2685
  }
2114
- await import_node_fs7.default.promises.writeFile(certificate, chain);
2686
+ await import_node_fs8.default.promises.writeFile(certificate, chain);
2115
2687
  }
2116
2688
  /**
2117
2689
  * Create a self-signed certificate using OpenSSL.
@@ -2121,52 +2693,30 @@ var CertificateAuthority = class {
2121
2693
  * @param params - certificate parameters (subject, validity, SANs)
2122
2694
  */
2123
2695
  async createSelfSignedCertificate(certificateFile, privateKey, params) {
2124
- (0, import_node_assert7.default)(typeof privateKey === "string");
2125
- (0, import_node_assert7.default)(import_node_fs7.default.existsSync(privateKey));
2696
+ (0, import_node_assert5.default)(typeof privateKey === "string");
2697
+ (0, import_node_assert5.default)(import_node_fs8.default.existsSync(privateKey));
2126
2698
  if (!certificateFileExist(certificateFile)) {
2127
2699
  return;
2128
2700
  }
2129
2701
  adjustDate(params);
2130
2702
  adjustApplicationUri(params);
2131
- processAltNames(params);
2132
- const csrFile = `${certificateFile}_csr`;
2133
- (0, import_node_assert7.default)(csrFile);
2134
- const configFile = generateStaticConfig(this.configFile, { cwd: this.rootDir });
2135
- const options = {
2136
- cwd: this.rootDir,
2137
- openssl_conf: makePath(configFile)
2138
- };
2139
- const subject = params.subject ? new import_node_opcua_crypto2.Subject(params.subject).toString() : "";
2140
- const subjectOptions = subject && subject.length > 1 ? ["-subj", subject] : [];
2141
- displaySubtitle("- the certificate signing request");
2142
- await execute_openssl(
2143
- ["req", "-new", "-sha256", "-text", ...subjectOptions, "-batch", "-key", n4(privateKey), "-out", n4(csrFile)],
2144
- options
2145
- );
2146
- displaySubtitle("- creating the self-signed certificate");
2147
- await execute_openssl(
2148
- [
2149
- "ca",
2150
- "-selfsign",
2151
- "-keyfile",
2152
- n4(privateKey),
2153
- "-startdate",
2154
- x509Date(params.startDate),
2155
- "-enddate",
2156
- x509Date(params.endDate),
2157
- "-batch",
2158
- "-out",
2159
- n4(certificateFile),
2160
- "-in",
2161
- n4(csrFile)
2162
- ],
2163
- options
2164
- );
2165
- displaySubtitle("- dump the certificate for a check");
2166
- await execute_openssl(["x509", "-in", n4(certificateFile), "-dates", "-fingerprint", "-purpose", "-noout"], {});
2167
- displaySubtitle("- verify self-signed certificate");
2168
- await execute_openssl_no_failure(["verify", "-verbose", "-CAfile", n4(certificateFile), n4(certificateFile)], options);
2169
- await import_node_fs7.default.promises.unlink(csrFile);
2703
+ params.dns = params.dns || [];
2704
+ params.ip = params.ip || [];
2705
+ await this.#withCaLock(async () => {
2706
+ await this.#backend.createSelfSignedCertificate(this, certificateFile, privateKey, params);
2707
+ });
2708
+ }
2709
+ /**
2710
+ * Regenerate `crl/revocation_list.{crl,der}` from the current database
2711
+ * state, without changing any certificate's status. Normally
2712
+ * unnecessary — `revokeCertificate` already regenerates the CRL as
2713
+ * part of revoking — but useful to force a refresh (e.g. after
2714
+ * switching a CA's `backend` between `"openssl"` and `"native"`).
2715
+ */
2716
+ async regenerateCrl() {
2717
+ await this.#withCaLock(async () => {
2718
+ await this.#backend.regenerateCrl(this);
2719
+ });
2170
2720
  }
2171
2721
  /**
2172
2722
  * Revoke a certificate and regenerate the CRL.
@@ -2187,49 +2737,12 @@ var CertificateAuthority = class {
2187
2737
  "certificateHold",
2188
2738
  "removeFromCRL"
2189
2739
  ];
2190
- const configFile = generateStaticConfig("conf/caconfig.cnf", { cwd: this.rootDir });
2191
- const options = {
2192
- cwd: this.rootDir,
2193
- openssl_conf: makePath(configFile)
2194
- };
2195
- setEnv("ALTNAME", "");
2196
- const randomFile = import_node_path5.default.join(this.rootDir, "random.rnd");
2197
- setEnv("RANDFILE", randomFile);
2198
- const configOption = ["-config", n4(configFile)];
2199
2740
  const reason = params.reason || "keyCompromise";
2200
- (0, import_node_assert7.default)(crlReasons.indexOf(reason) >= 0);
2741
+ (0, import_node_assert5.default)(crlReasons.indexOf(reason) >= 0);
2201
2742
  displayTitle(`Revoking certificate ${certificate}`);
2202
- displaySubtitle("Revoke certificate");
2203
- const passin = await this._opensslPassin();
2204
- await execute_openssl_no_failure(
2205
- ["ca", "-verbose", ...configOption, "-revoke", certificate, "-crl_reason", reason, ...passin.args],
2206
- { ...options, env: passin.env }
2207
- );
2208
- await regenerateCrl(this.revocationList, configOption, options, passin);
2209
- displaySubtitle("Verify that certificate is revoked");
2210
- await execute_openssl_no_failure(
2211
- [
2212
- "verify",
2213
- "-verbose",
2214
- "-CRLfile",
2215
- n4(this.revocationList),
2216
- "-CAfile",
2217
- n4(this.caCertificate),
2218
- "-crl_check",
2219
- n4(certificate)
2220
- ],
2221
- options
2222
- );
2223
- displaySubtitle("Produce CRL in DER form ");
2224
- await execute_openssl(
2225
- ["crl", "-in", n4(this.revocationList), "-out", "crl/revocation_list.der", "-outform", "der"],
2226
- options
2227
- );
2228
- displaySubtitle("Produce CRL in PEM form ");
2229
- await execute_openssl(
2230
- ["crl", "-in", n4(this.revocationList), "-out", "crl/revocation_list.pem", "-outform", "pem", "-text"],
2231
- options
2232
- );
2743
+ await this.#withCaLock(async () => {
2744
+ await this.#backend.revoke(this, certificate, reason);
2745
+ });
2233
2746
  }
2234
2747
  /**
2235
2748
  * Sign a Certificate Signing Request (CSR) with this CA.
@@ -2244,17 +2757,15 @@ var CertificateAuthority = class {
2244
2757
  * @returns the path to the signed certificate
2245
2758
  */
2246
2759
  async signCertificateRequest(certificate, certificateSigningRequestFilename, params1) {
2247
- await ensure_openssl_installed();
2248
- (0, import_node_assert7.default)(import_node_fs7.default.existsSync(certificateSigningRequestFilename));
2760
+ await this.#backend.preflight();
2761
+ (0, import_node_assert5.default)(import_node_fs8.default.existsSync(certificateSigningRequestFilename));
2249
2762
  if (!certificateFileExist(certificate)) {
2250
2763
  return "";
2251
2764
  }
2252
2765
  adjustDate(params1);
2253
2766
  adjustApplicationUri(params1);
2254
- processAltNames(params1);
2255
- const options = { cwd: this.rootDir };
2256
- const csr = await (0, import_node_opcua_crypto2.readCertificateSigningRequest)(certificateSigningRequestFilename);
2257
- const csrInfo = (0, import_node_opcua_crypto2.exploreCertificateSigningRequest)(csr);
2767
+ const csr = await (0, import_node_opcua_crypto5.readCertificateSigningRequest)(certificateSigningRequestFilename);
2768
+ const csrInfo = (0, import_node_opcua_crypto5.exploreCertificateSigningRequest)(csr);
2258
2769
  const applicationUri = csrInfo.extensionRequest.subjectAltName.uniformResourceIdentifier ? csrInfo.extensionRequest.subjectAltName.uniformResourceIdentifier[0] : void 0;
2259
2770
  if (typeof applicationUri !== "string") {
2260
2771
  throw new Error("Cannot find applicationUri in CSR");
@@ -2262,91 +2773,129 @@ var CertificateAuthority = class {
2262
2773
  const dns = csrInfo.extensionRequest.subjectAltName.dNSName || [];
2263
2774
  let ip = csrInfo.extensionRequest.subjectAltName.iPAddress || [];
2264
2775
  ip = ip.map(octetStringToIpAddress);
2265
- const params = {
2266
- applicationUri,
2267
- dns,
2268
- ip
2269
- };
2270
- processAltNames(params);
2271
- this._wireRevocationEnvVars();
2272
- const configFile = generateStaticConfig("conf/caconfig.cnf", options);
2273
- displaySubtitle("- then we ask the authority to sign the certificate signing request");
2274
- const passin = await this._opensslPassin();
2275
- await execute_openssl(
2276
- [
2277
- "ca",
2278
- "-config",
2279
- configFile,
2280
- "-startdate",
2281
- x509Date(params1.startDate),
2282
- "-enddate",
2283
- x509Date(params1.endDate),
2284
- "-batch",
2285
- "-out",
2286
- n4(certificate),
2287
- "-in",
2288
- n4(certificateSigningRequestFilename),
2289
- ...passin.args
2290
- ],
2291
- { ...options, env: passin.env }
2292
- );
2293
- displaySubtitle("- dump the certificate for a check");
2294
- await execute_openssl(["x509", "-in", n4(certificate), "-dates", "-fingerprint", "-purpose", "-noout"], options);
2295
- displaySubtitle("- construct CA certificate with CRL");
2296
- await this.constructCACertificateWithCRL();
2297
- displaySubtitle("- construct certificate chain");
2298
- await this.constructCertificateChain(certificate);
2299
- displaySubtitle("- verify certificate against the root CA");
2300
- await this.verifyCertificate(certificate);
2301
- return certificate;
2776
+ const sanOverride = { applicationUri, dns, ip };
2777
+ return this.#withCaLock(async () => {
2778
+ await this.#backend.signEndEntityCsr(this, certificate, certificateSigningRequestFilename, params1, sanOverride);
2779
+ displaySubtitle("- construct CA certificate with CRL");
2780
+ await this.constructCACertificateWithCRL();
2781
+ displaySubtitle("- construct certificate chain");
2782
+ await this.constructCertificateChain(certificate);
2783
+ displaySubtitle("- verify certificate against the root CA");
2784
+ await this.verifyCertificate(certificate);
2785
+ return certificate;
2786
+ });
2302
2787
  }
2303
2788
  /**
2304
- * Verify a certificate against this CA.
2789
+ * Check that `certificate` really was signed by this CA, and throw if it
2790
+ * was not.
2305
2791
  *
2306
- * @param certificate - path to the certificate file to verify
2792
+ * This used to do nothing: `openssl verify` crashes on Windows, so the
2793
+ * check was left as a placeholder. It no longer needs a subprocess -
2794
+ * `verifyCertificateSignature` does it in pure JS, the same way
2795
+ * {@link CertificateManager} already validates a chain - so the check
2796
+ * that `signCertificateRequest` always claimed to perform now actually
2797
+ * happens on every issuance, whichever backend did the signing.
2798
+ *
2799
+ * Only the leading certificate is examined: the file may be a chain,
2800
+ * and the rest of it is this CA's own certificate and its issuers.
2801
+ *
2802
+ * @param certificate - path to the certificate (or chain) to verify
2307
2803
  */
2308
2804
  async verifyCertificate(certificate) {
2309
- const isImplemented = false;
2310
- if (isImplemented) {
2311
- const options = { cwd: this.rootDir };
2312
- const configFile = generateStaticConfig("conf/caconfig.cnf", options);
2313
- setEnv("OPENSSL_CONF", makePath(configFile));
2314
- await execute_openssl_no_failure(
2315
- ["verify", "-verbose", "-CAfile", n4(this.caCertificateWithCrl), n4(certificate)],
2316
- options
2805
+ const pem = await import_node_fs8.default.promises.readFile(certificate, "utf-8");
2806
+ const blocks = pem.match(/-----BEGIN CERTIFICATE-----[\s\S]*?-----END CERTIFICATE-----/g);
2807
+ if (!blocks || blocks.length === 0) {
2808
+ throw new Error(`verifyCertificate: ${certificate} contains no PEM-encoded certificate`);
2809
+ }
2810
+ const caCertificateDer = (0, import_node_opcua_crypto5.convertPEMtoDER)((0, import_node_opcua_crypto5.readCertificatePEM)(this.caCertificate));
2811
+ if (!(0, import_node_opcua_crypto5.verifyCertificateSignature)((0, import_node_opcua_crypto5.convertPEMtoDER)(blocks[0]), caCertificateDer)) {
2812
+ throw new Error(
2813
+ `verifyCertificate: ${certificate} was not signed by this certificate authority (${this.caCertificate})`
2317
2814
  );
2318
2815
  }
2319
2816
  }
2320
2817
  };
2321
2818
 
2819
+ // packages/node-opcua-pki/lib/ca/certificate_authority.ts
2820
+ var CertificateAuthority = class extends CertificateAuthorityCore {
2821
+ constructor(options) {
2822
+ if (options.signer && options.backend === "openssl") {
2823
+ throw new Error(
2824
+ "CertificateAuthority: backend 'openssl' cannot be combined with 'signer' - the openssl CLI loads its key from a file and cannot call an external signer. Drop the backend option (a signer implies 'native') or drop the signer."
2825
+ );
2826
+ }
2827
+ const backend = options.signer || options.backend === "native" ? new NativeCaBackend() : new OpenSslCaBackend();
2828
+ super({ ...options, backend });
2829
+ }
2830
+ /**
2831
+ * @internal `-passin env:` argv + env for an openssl call that loads
2832
+ * this CA's key (always emitted, empty when none).
2833
+ *
2834
+ * The openssl backend builds its own now; this remains so that external
2835
+ * code calling it keeps working, and lives here rather than on the core
2836
+ * because the flag means nothing to a backend that spawns nothing.
2837
+ */
2838
+ async _opensslPassin() {
2839
+ return passinArg(await this._privateKeyPassphrase());
2840
+ }
2841
+ /**
2842
+ * @internal
2843
+ * Legacy shim: publish the `CDP_URL` / `AIA_VALUE` config substitution
2844
+ * values to the shared env registry, or unset them so the matching
2845
+ * `{{#KEY}}...{{/KEY}}` blocks are stripped. Nothing in this package
2846
+ * reads the registry any more - every openssl config render receives
2847
+ * these values explicitly, from the same {@link caConfigEnvOverrides}
2848
+ * builder this delegates to, so the two cannot drift - kept only for
2849
+ * external code that renders openssl config templates against the
2850
+ * registry directly.
2851
+ *
2852
+ * It lives on this class rather than the core because it is meaningful
2853
+ * only to the openssl backend.
2854
+ */
2855
+ _wireRevocationEnvVars() {
2856
+ const overrides = caConfigEnvOverrides(this);
2857
+ if (overrides.CDP_URL) {
2858
+ setEnv("CDP_URL", overrides.CDP_URL);
2859
+ } else {
2860
+ unsetEnv("CDP_URL");
2861
+ }
2862
+ if (overrides.AIA_VALUE) {
2863
+ setEnv("AIA_VALUE", overrides.AIA_VALUE);
2864
+ } else {
2865
+ unsetEnv("AIA_VALUE");
2866
+ }
2867
+ }
2868
+ };
2869
+
2322
2870
  // packages/node-opcua-pki/lib/pki/certificate_manager.ts
2323
2871
  var import_node_events = require("events");
2324
- var import_node_fs10 = __toESM(require("fs"));
2325
- var import_node_path6 = __toESM(require("path"));
2326
- var import_global_mutex = require("@ster5/global-mutex");
2872
+ var import_node_fs11 = __toESM(require("fs"));
2873
+ var import_node_path8 = __toESM(require("path"));
2874
+ var import_global_mutex2 = require("@ster5/global-mutex");
2327
2875
  var import_chalk6 = __toESM(require("chalk"));
2328
2876
  var import_chokidar = __toESM(require("chokidar"));
2329
- var import_node_opcua_crypto5 = require("node-opcua-crypto");
2877
+ var import_node_opcua_crypto8 = require("node-opcua-crypto");
2330
2878
 
2331
2879
  // packages/node-opcua-pki/lib/toolbox/without_openssl/create_certificate_signing_request.ts
2332
- var import_node_assert8 = __toESM(require("assert"));
2333
- var import_node_fs8 = __toESM(require("fs"));
2334
- var import_node_opcua_crypto3 = require("node-opcua-crypto");
2880
+ var import_node_assert6 = __toESM(require("assert"));
2881
+ var import_node_fs9 = __toESM(require("fs"));
2882
+ var import_node_opcua_crypto6 = require("node-opcua-crypto");
2335
2883
  async function createCertificateSigningRequestAsync(certificateSigningRequestFilename, params) {
2336
- (0, import_node_assert8.default)(params);
2337
- (0, import_node_assert8.default)(params.rootDir);
2338
- (0, import_node_assert8.default)(params.configFile);
2339
- (0, import_node_assert8.default)(params.privateKey);
2884
+ (0, import_node_assert6.default)(params);
2885
+ (0, import_node_assert6.default)(params.rootDir);
2886
+ (0, import_node_assert6.default)(params.configFile);
2887
+ (0, import_node_assert6.default)(params.privateKey);
2340
2888
  if (typeof params.privateKey === "string") {
2341
- (0, import_node_assert8.default)(import_node_fs8.default.existsSync(params.privateKey), `Private key must exist${params.privateKey}`);
2889
+ (0, import_node_assert6.default)(import_node_fs9.default.existsSync(params.privateKey), `Private key must exist${params.privateKey}`);
2342
2890
  }
2343
- (0, import_node_assert8.default)(import_node_fs8.default.existsSync(params.rootDir), "RootDir key must exist");
2344
- (0, import_node_assert8.default)(typeof certificateSigningRequestFilename === "string");
2345
- const subject = params.subject ? new import_node_opcua_crypto3.Subject(params.subject).toString() : void 0;
2891
+ (0, import_node_assert6.default)(import_node_fs9.default.existsSync(params.rootDir), "RootDir key must exist");
2892
+ (0, import_node_assert6.default)(typeof certificateSigningRequestFilename === "string");
2893
+ const subject = params.subject ? new import_node_opcua_crypto6.Subject(params.subject).toString() : void 0;
2346
2894
  displaySubtitle("- Creating a Certificate Signing Request with subtile");
2347
- const privateKeyPem = typeof params.privateKey === "string" ? await import_node_fs8.default.promises.readFile(params.privateKey, "utf-8") : (0, import_node_opcua_crypto3.coercePrivateKeyPem)(params.privateKey);
2348
- const privateKey = await (0, import_node_opcua_crypto3.pemToPrivateKey)(privateKeyPem);
2349
- const { csr } = await (0, import_node_opcua_crypto3.createCertificateSigningRequest)({
2895
+ const privateKey = isOpaqueSigner(params.privateKey) ? params.privateKey : await (0, import_node_opcua_crypto6.pemToPrivateKey)(
2896
+ typeof params.privateKey === "string" ? await import_node_fs9.default.promises.readFile(params.privateKey, "utf-8") : (0, import_node_opcua_crypto6.coercePrivateKeyPem)(params.privateKey)
2897
+ );
2898
+ const { csr } = await (0, import_node_opcua_crypto6.createCertificateSigningRequest)({
2350
2899
  privateKey,
2351
2900
  dns: params.dns,
2352
2901
  ip: params.ip,
@@ -2354,37 +2903,40 @@ async function createCertificateSigningRequestAsync(certificateSigningRequestFil
2354
2903
  applicationUri: params.applicationUri,
2355
2904
  purpose: params.purpose
2356
2905
  });
2357
- await import_node_fs8.default.promises.writeFile(certificateSigningRequestFilename, csr, "utf-8");
2358
- display(`- privateKey ${typeof params.privateKey === "string" ? params.privateKey : "<in-memory>"}`);
2906
+ await import_node_fs9.default.promises.writeFile(certificateSigningRequestFilename, csr, "utf-8");
2907
+ display(
2908
+ `- privateKey ${typeof params.privateKey === "string" ? params.privateKey : isOpaqueSigner(params.privateKey) ? "<opaque-signer>" : "<in-memory>"}`
2909
+ );
2359
2910
  display(`- certificateSigningRequestFilename ${certificateSigningRequestFilename}`);
2360
2911
  }
2361
2912
 
2362
2913
  // packages/node-opcua-pki/lib/toolbox/without_openssl/create_self_signed_certificate.ts
2363
- var import_node_assert9 = __toESM(require("assert"));
2364
- var import_node_fs9 = __toESM(require("fs"));
2365
- var import_node_opcua_crypto4 = require("node-opcua-crypto");
2914
+ var import_node_assert7 = __toESM(require("assert"));
2915
+ var import_node_fs10 = __toESM(require("fs"));
2916
+ var import_node_opcua_crypto7 = require("node-opcua-crypto");
2366
2917
  async function createSelfSignedCertificateAsync(certificate, params) {
2367
- params.purpose = params.purpose || import_node_opcua_crypto4.CertificatePurpose.ForApplication;
2368
- (0, import_node_assert9.default)(params.purpose, "Please provide a Certificate Purpose");
2369
- (0, import_node_assert9.default)(import_node_fs9.default.existsSync(params.configFile));
2370
- (0, import_node_assert9.default)(import_node_fs9.default.existsSync(params.rootDir));
2918
+ params.purpose = params.purpose || import_node_opcua_crypto7.CertificatePurpose.ForApplication;
2919
+ (0, import_node_assert7.default)(params.purpose, "Please provide a Certificate Purpose");
2920
+ (0, import_node_assert7.default)(import_node_fs10.default.existsSync(params.configFile));
2921
+ (0, import_node_assert7.default)(import_node_fs10.default.existsSync(params.rootDir));
2371
2922
  if (typeof params.privateKey === "string") {
2372
- (0, import_node_assert9.default)(import_node_fs9.default.existsSync(params.privateKey));
2923
+ (0, import_node_assert7.default)(import_node_fs10.default.existsSync(params.privateKey));
2373
2924
  }
2374
2925
  if (!params.subject) {
2375
2926
  throw Error("Missing subject");
2376
2927
  }
2377
- (0, import_node_assert9.default)(typeof params.applicationUri === "string");
2378
- (0, import_node_assert9.default)(Array.isArray(params.dns));
2928
+ (0, import_node_assert7.default)(typeof params.applicationUri === "string");
2929
+ (0, import_node_assert7.default)(Array.isArray(params.dns));
2379
2930
  adjustDate(params);
2380
- (0, import_node_assert9.default)(Object.prototype.hasOwnProperty.call(params, "validity"));
2381
- let subject = new import_node_opcua_crypto4.Subject(params.subject);
2931
+ (0, import_node_assert7.default)(Object.prototype.hasOwnProperty.call(params, "validity"));
2932
+ let subject = new import_node_opcua_crypto7.Subject(params.subject);
2382
2933
  subject = subject.toString();
2383
2934
  const purpose = params.purpose;
2384
2935
  displayTitle("Generate a certificate request");
2385
- const privateKeyPem = typeof params.privateKey === "string" ? await import_node_fs9.default.promises.readFile(params.privateKey, "utf-8") : (0, import_node_opcua_crypto4.coercePrivateKeyPem)(params.privateKey);
2386
- const privateKey = await (0, import_node_opcua_crypto4.pemToPrivateKey)(privateKeyPem);
2387
- const { cert } = await (0, import_node_opcua_crypto4.createSelfSignedCertificate)({
2936
+ const privateKey = isOpaqueSigner(params.privateKey) ? params.privateKey : await (0, import_node_opcua_crypto7.pemToPrivateKey)(
2937
+ typeof params.privateKey === "string" ? await import_node_fs10.default.promises.readFile(params.privateKey, "utf-8") : (0, import_node_opcua_crypto7.coercePrivateKeyPem)(params.privateKey)
2938
+ );
2939
+ const { cert } = await (0, import_node_opcua_crypto7.createSelfSignedCertificate)({
2388
2940
  privateKey,
2389
2941
  notBefore: params.startDate,
2390
2942
  notAfter: params.endDate,
@@ -2395,18 +2947,22 @@ async function createSelfSignedCertificateAsync(certificate, params) {
2395
2947
  applicationUri: params.applicationUri,
2396
2948
  purpose
2397
2949
  });
2398
- await import_node_fs9.default.promises.writeFile(certificate, cert, "utf-8");
2950
+ await import_node_fs10.default.promises.writeFile(certificate, cert, "utf-8");
2399
2951
  }
2400
2952
  async function createSelfSignedCertificate(certificate, params) {
2401
2953
  await createSelfSignedCertificateAsync(certificate, params);
2402
2954
  }
2403
2955
 
2956
+ // packages/node-opcua-pki/lib/pki/templates/simple_config_template.cnf.ts
2957
+ var config3 = '##################################################################################################\n## SIMPLE OPENSSL CONFIG FILE FOR SELF-SIGNED CERTIFICATE GENERATION\n################################################################################################################\n\ndistinguished_name = req_distinguished_name\ndefault_md = sha1\n\ndefault_md = sha256 # The default digest algorithm\n\n[ v3_ca ]\nsubjectKeyIdentifier = hash\nauthorityKeyIdentifier = keyid:always,issuer:always\n\n# authorityKeyIdentifier = keyid\nbasicConstraints = CA:TRUE\nkeyUsage = critical, cRLSign, keyCertSign\nnsComment = "Self-signed Certificate for CA generated by Node-OPCUA Certificate utility"\n#nsCertType = sslCA, emailCA\n#subjectAltName = email:copy\n#issuerAltName = issuer:copy\n#obj = DER:02:03\n# crlDistributionPoints = @crl_info\n# [ crl_info ]\n# URI.0 = http://localhost:8900/crl.pem\nsubjectAltName = $ENV::ALTNAME\n\n[ req ]\ndays = 390\nreq_extensions = v3_req\nx509_extensions = v3_ca\n\n[v3_req]\nbasicConstraints = CA:false\nkeyUsage = critical, nonRepudiation, digitalSignature, keyEncipherment, dataEncipherment\nsubjectAltName = $ENV::ALTNAME\n\n[ v3_ca_signed]\nsubjectKeyIdentifier = hash\nauthorityKeyIdentifier = keyid,issuer\nbasicConstraints = critical, CA:FALSE\nkeyUsage = nonRepudiation, digitalSignature, keyEncipherment, dataEncipherment\nextendedKeyUsage = clientAuth,serverAuth \nnsComment = "certificate generated by Node-OPCUA Certificate utility and signed by a CA"\nsubjectAltName = $ENV::ALTNAME\n[ v3_selfsigned]\nsubjectKeyIdentifier = hash\nauthorityKeyIdentifier = keyid,issuer\nbasicConstraints = critical, CA:FALSE\nkeyUsage = nonRepudiation, digitalSignature, keyEncipherment, dataEncipherment\nextendedKeyUsage = clientAuth,serverAuth \nnsComment = "Self-signed certificate generated by Node-OPCUA Certificate utility"\nsubjectAltName = $ENV::ALTNAME\n[ req_distinguished_name ]\ncountryName = Country Name (2 letter code)\ncountryName_default = FR\ncountryName_min = 2\ncountryName_max = 2\n# stateOrProvinceName = State or Province Name (full name)\n# stateOrProvinceName_default = Ile de France\n# localityName = Locality Name (city, district)\n# localityName_default = Paris\norganizationName = Organization Name (company)\norganizationName_default = NodeOPCUA\n# organizationalUnitName = Organizational Unit Name (department, division)\n# organizationalUnitName_default = R&D\ncommonName = Common Name (hostname, FQDN, IP, or your name)\ncommonName_max = 256\ncommonName_default = NodeOPCUA\n# emailAddress = Email Address\n# emailAddress_max = 40\n# emailAddress_default = node-opcua (at) node-opcua (dot) com\nsubjectAltName = $ENV::ALTNAME';
2958
+ var simple_config_template_cnf_default = config3;
2959
+
2404
2960
  // packages/node-opcua-pki/lib/pki/certificate_manager.ts
2405
- var configurationFileSimpleTemplate2 = simple_config_template_cnf_default;
2406
- var fsWriteFile = import_node_fs10.default.promises.writeFile;
2961
+ var configurationFileSimpleTemplate = simple_config_template_cnf_default;
2962
+ var fsWriteFile = import_node_fs11.default.promises.writeFile;
2407
2963
  function getOrComputeInfo(entry) {
2408
2964
  if (!entry.info) {
2409
- entry.info = (0, import_node_opcua_crypto5.exploreCertificate)(entry.certificate);
2965
+ entry.info = (0, import_node_opcua_crypto8.exploreCertificate)(entry.certificate);
2410
2966
  }
2411
2967
  return entry.info;
2412
2968
  }
@@ -2433,14 +2989,14 @@ function coerceCertificateChain(certificate) {
2433
2989
  if (Array.isArray(certificate)) {
2434
2990
  if (certificate.length === 0) return [];
2435
2991
  return certificate.reduce((acc, cert) => {
2436
- return acc.concat((0, import_node_opcua_crypto5.split_der)(cert));
2992
+ return acc.concat((0, import_node_opcua_crypto8.split_der)(cert));
2437
2993
  }, []);
2438
2994
  }
2439
- return (0, import_node_opcua_crypto5.split_der)(certificate);
2995
+ return (0, import_node_opcua_crypto8.split_der)(certificate);
2440
2996
  }
2441
2997
  function makeFingerprint(certificate) {
2442
2998
  const chain = coerceCertificateChain(certificate);
2443
- return (0, import_node_opcua_crypto5.makeSHA1Thumbprint)(chain[0]).toString("hex");
2999
+ return (0, import_node_opcua_crypto8.makeSHA1Thumbprint)(chain[0]).toString("hex");
2444
3000
  }
2445
3001
  function short(stringToShorten) {
2446
3002
  return stringToShorten.substring(0, 10);
@@ -2450,7 +3006,7 @@ function buildIdealCertificateName(certificate) {
2450
3006
  const chain = coerceCertificateChain(certificate);
2451
3007
  const fingerprint = makeFingerprint(chain);
2452
3008
  try {
2453
- const commonName = (0, import_node_opcua_crypto5.exploreCertificate)(chain[0]).tbsCertificate.subject.commonName || "";
3009
+ const commonName = (0, import_node_opcua_crypto8.exploreCertificate)(chain[0]).tbsCertificate.subject.commonName || "";
2454
3010
  const sanitizedCommonName = commonName.replace(forbiddenChars, "_");
2455
3011
  return `${sanitizedCommonName}[${fingerprint}]`;
2456
3012
  } catch (_err) {
@@ -2467,7 +3023,7 @@ function isSelfSigned2(info) {
2467
3023
  return info.tbsCertificate.extensions?.subjectKeyIdentifier === info.tbsCertificate.extensions?.authorityKeyIdentifier?.keyIdentifier;
2468
3024
  }
2469
3025
  function isSelfSigned3(certificate) {
2470
- const info = (0, import_node_opcua_crypto5.exploreCertificate)(certificate);
3026
+ const info = (0, import_node_opcua_crypto8.exploreCertificate)(certificate);
2471
3027
  return isSelfSigned2(info);
2472
3028
  }
2473
3029
  function _isIssuerInfo(info) {
@@ -2483,7 +3039,7 @@ function _isIssuerInfo(info) {
2483
3039
  }
2484
3040
  function isIssuer(certificate) {
2485
3041
  try {
2486
- const info = (0, import_node_opcua_crypto5.exploreCertificate)(certificate);
3042
+ const info = (0, import_node_opcua_crypto8.exploreCertificate)(certificate);
2487
3043
  return _isIssuerInfo(info);
2488
3044
  } catch (_err) {
2489
3045
  return false;
@@ -2491,7 +3047,7 @@ function isIssuer(certificate) {
2491
3047
  }
2492
3048
  function isIntermediateIssuer(certificate) {
2493
3049
  try {
2494
- const info = (0, import_node_opcua_crypto5.exploreCertificate)(certificate);
3050
+ const info = (0, import_node_opcua_crypto8.exploreCertificate)(certificate);
2495
3051
  if (!_isIssuerInfo(info)) {
2496
3052
  return false;
2497
3053
  }
@@ -2502,7 +3058,7 @@ function isIntermediateIssuer(certificate) {
2502
3058
  }
2503
3059
  function isRootIssuer(certificate) {
2504
3060
  try {
2505
- const info = (0, import_node_opcua_crypto5.exploreCertificate)(certificate);
3061
+ const info = (0, import_node_opcua_crypto8.exploreCertificate)(certificate);
2506
3062
  if (!_isIssuerInfo(info)) {
2507
3063
  return false;
2508
3064
  }
@@ -2517,7 +3073,7 @@ function findIssuerCertificateInChain(certificate, chain) {
2517
3073
  if (!firstCertificate) {
2518
3074
  return null;
2519
3075
  }
2520
- const certInfo = (0, import_node_opcua_crypto5.exploreCertificate)(firstCertificate);
3076
+ const certInfo = (0, import_node_opcua_crypto8.exploreCertificate)(firstCertificate);
2521
3077
  if (isSelfSigned2(certInfo)) {
2522
3078
  return firstCertificate;
2523
3079
  }
@@ -2528,7 +3084,7 @@ function findIssuerCertificateInChain(certificate, chain) {
2528
3084
  }
2529
3085
  const coercedChain = coerceCertificateChain(chain);
2530
3086
  const potentialIssuers = coercedChain.filter((c) => {
2531
- const info = (0, import_node_opcua_crypto5.exploreCertificate)(c);
3087
+ const info = (0, import_node_opcua_crypto8.exploreCertificate)(c);
2532
3088
  return info.tbsCertificate.extensions && info.tbsCertificate.extensions.subjectKeyIdentifier === wantedIssuerKey;
2533
3089
  });
2534
3090
  if (potentialIssuers.length === 1) {
@@ -2563,7 +3119,31 @@ var CertificateManager = class _CertificateManager extends import_node_events.Ev
2563
3119
  // even if the consumer forgets to call dispose().
2564
3120
  static #activeInstances = /* @__PURE__ */ new Set();
2565
3121
  static #cleanupInstalled = false;
2566
- static #installProcessCleanup() {
3122
+ static #exitHandler;
3123
+ /**
3124
+ * Install a best-effort `exit` hook that closes any watcher
3125
+ * still open when the process terminates.
3126
+ *
3127
+ * **This library never terminates the host process.** No
3128
+ * SIGINT/SIGTERM handler is installed: deciding how (and
3129
+ * whether) to shut down on a signal is the application's
3130
+ * responsibility, and a listener registered here would both
3131
+ * pre-empt the application's own graceful shutdown and
3132
+ * silently suppress Node's default signal behaviour.
3133
+ *
3134
+ * Nothing here is load-bearing for process exit. The native
3135
+ * `fs.watch` handles are `unref()`'d when the watchers are
3136
+ * created (see `#readCertificates`), so an undisposed
3137
+ * CertificateManager never keeps the event loop alive. This
3138
+ * hook is only tidiness on the way out.
3139
+ *
3140
+ * `exit` rather than `beforeExit`: `beforeExit` fires when
3141
+ * the loop merely drains and the loop can subsequently be
3142
+ * resurrected, which would leave a still-in-use instance
3143
+ * marked Disposed. `exit` is terminal, synchronous-only and
3144
+ * cannot alter the exit code.
3145
+ */
3146
+ static #installExitCleanup() {
2567
3147
  if (_CertificateManager.#cleanupInstalled) return;
2568
3148
  _CertificateManager.#cleanupInstalled = true;
2569
3149
  const closeDanglingWatchers = () => {
@@ -2579,13 +3159,22 @@ var CertificateManager = class _CertificateManager extends import_node_events.Ev
2579
3159
  }
2580
3160
  _CertificateManager.#activeInstances.clear();
2581
3161
  };
2582
- process.on("beforeExit", closeDanglingWatchers);
2583
- for (const signal of ["SIGINT", "SIGTERM"]) {
2584
- process.once(signal, () => {
2585
- closeDanglingWatchers();
2586
- process.exit();
2587
- });
3162
+ _CertificateManager.#exitHandler = closeDanglingWatchers;
3163
+ process.on("exit", closeDanglingWatchers);
3164
+ }
3165
+ /**
3166
+ * Remove the `exit` hook once the last instance is disposed,
3167
+ * so a library that is initialized and disposed repeatedly
3168
+ * does not accumulate process listeners. A later
3169
+ * `initialize()` re-arms it.
3170
+ */
3171
+ static #uninstallExitCleanupIfIdle() {
3172
+ if (_CertificateManager.#activeInstances.size > 0) return;
3173
+ if (_CertificateManager.#exitHandler) {
3174
+ process.removeListener("exit", _CertificateManager.#exitHandler);
3175
+ _CertificateManager.#exitHandler = void 0;
2588
3176
  }
3177
+ _CertificateManager.#cleanupInstalled = false;
2589
3178
  }
2590
3179
  /**
2591
3180
  * Dispose **all** active CertificateManager instances,
@@ -2651,6 +3240,9 @@ var CertificateManager = class _CertificateManager extends import_node_events.Ev
2651
3240
  #disableFileWatchers;
2652
3241
  #privateKeyPassphrase;
2653
3242
  #privateKeyProvider;
3243
+ #keyOperations;
3244
+ /** The stable lazy wrap handed out by {@link getKeyOperations} for non-opaque configurations. */
3245
+ #lazyLocalKeyOperations;
2654
3246
  /**
2655
3247
  * The on-disk key, decrypted once and kept for the instance's lifetime,
2656
3248
  * so the passphrase (or its resolver function) is consulted at most
@@ -2694,14 +3286,25 @@ var CertificateManager = class _CertificateManager extends import_node_events.Ev
2694
3286
  this.#disableFileWatchers = options.disableFileWatchers ?? process.env.OPCUA_PKI_DISABLE_FILE_WATCHERS === "true";
2695
3287
  this.#privateKeyPassphrase = options.privateKeyPassphrase;
2696
3288
  this.#privateKeyProvider = options.privateKeyProvider;
3289
+ this.#keyOperations = options.keyOperations;
3290
+ if (this.#keyOperations && this.#privateKeyProvider) {
3291
+ throw new Error(
3292
+ "CertificateManager: 'keyOperations' and 'privateKeyProvider' are mutually exclusive: one hides the key, the other sources its material"
3293
+ );
3294
+ }
3295
+ if (this.#keyOperations && this.#privateKeyPassphrase) {
3296
+ throw new Error(
3297
+ "CertificateManager: 'privateKeyPassphrase' is meaningless with 'keyOperations': there is no key material for a passphrase to protect"
3298
+ );
3299
+ }
2697
3300
  mkdirRecursiveSync(options.location);
2698
- if (!import_node_fs10.default.existsSync(this.#location)) {
3301
+ if (!import_node_fs11.default.existsSync(this.#location)) {
2699
3302
  throw new Error(`CertificateManager cannot access location ${this.#location}`);
2700
3303
  }
2701
3304
  }
2702
3305
  /** Path to the OpenSSL configuration file. */
2703
3306
  get configFile() {
2704
- return import_node_path6.default.join(this.rootDir, "own/openssl.cnf");
3307
+ return import_node_path8.default.join(this.rootDir, "own/openssl.cnf");
2705
3308
  }
2706
3309
  /** Root directory of the PKI store. */
2707
3310
  get rootDir() {
@@ -2717,7 +3320,7 @@ var CertificateManager = class _CertificateManager extends import_node_events.Ev
2717
3320
  * be no meaningful file in that case).
2718
3321
  */
2719
3322
  get privateKey() {
2720
- return import_node_path6.default.join(this.rootDir, "own/private/private_key.pem");
3323
+ return import_node_path8.default.join(this.rootDir, "own/private/private_key.pem");
2721
3324
  }
2722
3325
  /**
2723
3326
  * Resolve the private key: from `privateKeyProvider` if configured,
@@ -2735,6 +3338,11 @@ var CertificateManager = class _CertificateManager extends import_node_events.Ev
2735
3338
  * authority on what the current key is.
2736
3339
  */
2737
3340
  async getPrivateKey() {
3341
+ if (this.#keyOperations) {
3342
+ throw new import_node_opcua_crypto8.PrivateKeyUnavailableError(
3343
+ "CertificateManager.getPrivateKey is not available when keyOperations is configured: the private key is held by the key-operations provider (HSM/KMS) and cannot be read \u2014 use getKeyOperations() instead"
3344
+ );
3345
+ }
2738
3346
  if (this.#privateKeyProvider) {
2739
3347
  return await this.#privateKeyProvider.getPrivateKey();
2740
3348
  }
@@ -2744,7 +3352,7 @@ var CertificateManager = class _CertificateManager extends import_node_events.Ev
2744
3352
  if (!this.#privateKeyPromise) {
2745
3353
  this.#privateKeyPromise = (async () => {
2746
3354
  const passphrase = await resolvePrivateKeyPassphrase(this.#privateKeyPassphrase);
2747
- return (0, import_node_opcua_crypto5.readPrivateKey)(this.privateKey, passphrase);
3355
+ return (0, import_node_opcua_crypto8.readPrivateKey)(this.privateKey, passphrase);
2748
3356
  })().then(
2749
3357
  (key) => {
2750
3358
  this.#cachedPrivateKey = key;
@@ -2760,6 +3368,75 @@ var CertificateManager = class _CertificateManager extends import_node_events.Ev
2760
3368
  }
2761
3369
  /** In-flight first read of the on-disk key, so concurrent callers share one passphrase resolution. */
2762
3370
  #privateKeyPromise;
3371
+ /**
3372
+ * True when this manager's key is opaque — configured through
3373
+ * `keyOperations`, held by an HSM/KMS, never obtainable as material.
3374
+ * When true, {@link getPrivateKey} throws `PrivateKeyUnavailableError`
3375
+ * and {@link getKeyOperations} is the only way to use the key.
3376
+ */
3377
+ isPrivateKeyOpaque() {
3378
+ return !!this.#keyOperations;
3379
+ }
3380
+ /**
3381
+ * The key as an opaque {@link IKeyOperations} — the recommended way to
3382
+ * *use* the private key regardless of where it lives.
3383
+ *
3384
+ * Returns the configured `keyOperations` object when the key is opaque.
3385
+ * Otherwise returns a stable lazy wrap over {@link getPrivateKey}: its
3386
+ * methods resolve the key on first use (disk read, passphrase,
3387
+ * `privateKeyProvider` — all async), so the wrap offers no synchronous
3388
+ * fast path; callers that need one resolve the key themselves and build
3389
+ * a `LocalKeyOperations` over it. The wrap follows key rotation: a
3390
+ * `privateKeyProvider` that starts returning a different key gets a
3391
+ * fresh underlying `LocalKeyOperations`.
3392
+ */
3393
+ getKeyOperations() {
3394
+ if (this.#keyOperations) {
3395
+ return this.#keyOperations;
3396
+ }
3397
+ if (!this.#lazyLocalKeyOperations) {
3398
+ let cached;
3399
+ const resolve = async () => {
3400
+ const key = await this.getPrivateKey();
3401
+ if (!cached || cached.key !== key) {
3402
+ cached = { key, ops: (0, import_node_opcua_crypto8.keyOperationsFromPrivateKey)(key) };
3403
+ }
3404
+ return cached.ops;
3405
+ };
3406
+ this.#lazyLocalKeyOperations = {
3407
+ sign: async (data, params) => (await resolve()).sign(data, params),
3408
+ decryptBlock: async (block, params) => (await resolve()).decryptBlock(block, params),
3409
+ getKeyMetadata: async () => (await resolve()).getKeyMetadata(),
3410
+ getPublicKey: async () => (await resolve()).getPublicKey()
3411
+ };
3412
+ }
3413
+ return this.#lazyLocalKeyOperations;
3414
+ }
3415
+ /**
3416
+ * Fail closed at `initialize()` time, not on the first certificate
3417
+ * operation, if the key cannot actually be used: wrong/missing
3418
+ * passphrase on an encrypted key, a broken `privateKeyProvider`, or an
3419
+ * unreachable `keyOperations` provider (probed via `getKeyMetadata`).
3420
+ */
3421
+ async #probePrivateKey() {
3422
+ if (this.#keyOperations) {
3423
+ await this.#keyOperations.getKeyMetadata();
3424
+ return;
3425
+ }
3426
+ await this.getPrivateKey();
3427
+ }
3428
+ /**
3429
+ * The key to hand to a certificate-issuance primitive: the raw
3430
+ * {@link PrivateKey} for a local configuration, or a {@link CaSigner}
3431
+ * adapted from `keyOperations` when the key is opaque — which requires
3432
+ * the provider to implement `getPublicKey` (the adapter says so if not).
3433
+ */
3434
+ async #resolveSigningKey() {
3435
+ if (this.#keyOperations) {
3436
+ return (0, import_node_opcua_crypto8.caSignerFromKeyOperations)(this.#keyOperations);
3437
+ }
3438
+ return await this.getPrivateKey();
3439
+ }
2763
3440
  /**
2764
3441
  * Enable, disable, or rotate the passphrase protecting the on-disk
2765
3442
  * private key: decrypt with `oldPassphrase` (omit if the key is
@@ -2781,13 +3458,18 @@ var CertificateManager = class _CertificateManager extends import_node_events.Ev
2781
3458
  * disk file for this method to rewrite).
2782
3459
  */
2783
3460
  async reencryptPrivateKey(oldPassphrase, newPassphrase) {
3461
+ if (this.#keyOperations) {
3462
+ throw new import_node_opcua_crypto8.PrivateKeyUnavailableError(
3463
+ "reencryptPrivateKey: not supported when keyOperations is configured \u2014 there is no key material to rewrite"
3464
+ );
3465
+ }
2784
3466
  if (this.#privateKeyProvider) {
2785
3467
  throw new Error("reencryptPrivateKey: not supported when a privateKeyProvider is configured");
2786
3468
  }
2787
3469
  const oldPass = await resolvePrivateKeyPassphrase(oldPassphrase);
2788
3470
  const newPass = await resolvePrivateKeyPassphrase(newPassphrase);
2789
3471
  await this.withLock2(async () => {
2790
- const privateKey = (0, import_node_opcua_crypto5.readPrivateKey)(this.privateKey, oldPass);
3472
+ const privateKey = (0, import_node_opcua_crypto8.readPrivateKey)(this.privateKey, oldPass);
2791
3473
  await this.#rewritePrivateKeyFile(privateKey, newPass);
2792
3474
  });
2793
3475
  this.#cachedPrivateKey = void 0;
@@ -2803,15 +3485,15 @@ var CertificateManager = class _CertificateManager extends import_node_events.Ev
2803
3485
  async #rewritePrivateKeyFile(privateKey, passphrase) {
2804
3486
  const tmpFilename = `${this.privateKey}.${process.pid}-${Date.now()}.tmp`;
2805
3487
  try {
2806
- await (0, import_node_opcua_crypto5.writePrivateKeyFile)(tmpFilename, privateKey, { passphrase });
2807
- await import_node_fs10.default.promises.rename(tmpFilename, this.privateKey);
3488
+ await (0, import_node_opcua_crypto8.writePrivateKeyFile)(tmpFilename, privateKey, { passphrase });
3489
+ await import_node_fs11.default.promises.rename(tmpFilename, this.privateKey);
2808
3490
  } finally {
2809
- await import_node_fs10.default.promises.rm(tmpFilename, { force: true });
3491
+ await import_node_fs11.default.promises.rm(tmpFilename, { force: true });
2810
3492
  }
2811
3493
  }
2812
3494
  /** Path to the OpenSSL random seed file. */
2813
3495
  get randomFile() {
2814
- return import_node_path6.default.join(this.rootDir, "./random.rnd");
3496
+ return import_node_path8.default.join(this.rootDir, "./random.rnd");
2815
3497
  }
2816
3498
  /**
2817
3499
  * Move a certificate to the rejected store.
@@ -2848,30 +3530,30 @@ var CertificateManager = class _CertificateManager extends import_node_events.Ev
2848
3530
  }
2849
3531
  /** Path to the rejected certificates folder. */
2850
3532
  get rejectedFolder() {
2851
- return import_node_path6.default.join(this.rootDir, "rejected");
3533
+ return import_node_path8.default.join(this.rootDir, "rejected");
2852
3534
  }
2853
3535
  /** Path to the trusted certificates folder. */
2854
3536
  get trustedFolder() {
2855
- return import_node_path6.default.join(this.rootDir, "trusted/certs");
3537
+ return import_node_path8.default.join(this.rootDir, "trusted/certs");
2856
3538
  }
2857
3539
  /** Path to the trusted CRL folder. */
2858
3540
  get crlFolder() {
2859
- return import_node_path6.default.join(this.rootDir, "trusted/crl");
3541
+ return import_node_path8.default.join(this.rootDir, "trusted/crl");
2860
3542
  }
2861
3543
  /** Path to the issuer (CA) certificates folder. */
2862
3544
  get issuersCertFolder() {
2863
- return import_node_path6.default.join(this.rootDir, "issuers/certs");
3545
+ return import_node_path8.default.join(this.rootDir, "issuers/certs");
2864
3546
  }
2865
3547
  /** Path to the issuer CRL folder. */
2866
3548
  get issuersCrlFolder() {
2867
- return import_node_path6.default.join(this.rootDir, "issuers/crl");
3549
+ return import_node_path8.default.join(this.rootDir, "issuers/crl");
2868
3550
  }
2869
3551
  /** Path to the own certificate folder. */
2870
3552
  get ownCertFolder() {
2871
- return import_node_path6.default.join(this.rootDir, "own/certs");
3553
+ return import_node_path8.default.join(this.rootDir, "own/certs");
2872
3554
  }
2873
3555
  get ownPrivateFolder() {
2874
- return import_node_path6.default.join(this.rootDir, "own/private");
3556
+ return import_node_path8.default.join(this.rootDir, "own/private");
2875
3557
  }
2876
3558
  /**
2877
3559
  * Check if a certificate is in the trusted store.
@@ -2902,13 +3584,13 @@ var CertificateManager = class _CertificateManager extends import_node_events.Ev
2902
3584
  return "Good";
2903
3585
  }
2904
3586
  try {
2905
- (0, import_node_opcua_crypto5.exploreCertificateInfo)(chain[0]);
3587
+ (0, import_node_opcua_crypto8.exploreCertificateInfo)(chain[0]);
2906
3588
  } catch (_err) {
2907
3589
  return "BadCertificateInvalid";
2908
3590
  }
2909
- const filename = import_node_path6.default.join(this.rejectedFolder, `${buildIdealCertificateName(leafCertificate)}.pem`);
3591
+ const filename = import_node_path8.default.join(this.rejectedFolder, `${buildIdealCertificateName(leafCertificate)}.pem`);
2910
3592
  debugLog("certificate has never been seen before and is now rejected (untrusted) ", filename);
2911
- await fsWriteFile(filename, (0, import_node_opcua_crypto5.toPem)(chain, "CERTIFICATE"));
3593
+ await fsWriteFile(filename, (0, import_node_opcua_crypto8.toPem)(chain, "CERTIFICATE"));
2912
3594
  this.#thumbs.rejected.set(fingerprint, { certificate: leafCertificate, filename });
2913
3595
  }
2914
3596
  return "BadCertificateUntrusted";
@@ -2922,7 +3604,7 @@ var CertificateManager = class _CertificateManager extends import_node_events.Ev
2922
3604
  }
2923
3605
  const chain = coerceCertificateChain(certificateOrChain);
2924
3606
  debugLog("NB CERTIFICATE IN CHAIN = ", chain.length);
2925
- const info = (0, import_node_opcua_crypto5.exploreCertificate)(chain[0]);
3607
+ const info = (0, import_node_opcua_crypto8.exploreCertificate)(chain[0]);
2926
3608
  let hasValidIssuer = false;
2927
3609
  let hasTrustedIssuer = false;
2928
3610
  const hasIssuerKey = info.tbsCertificate.extensions?.authorityKeyIdentifier?.keyIdentifier;
@@ -2969,7 +3651,7 @@ var CertificateManager = class _CertificateManager extends import_node_events.Ev
2969
3651
  if (issuerStatus !== "Good" /* Good */ && issuerStatus !== "BadCertificateUntrusted" /* BadCertificateUntrusted */) {
2970
3652
  return "BadSecurityChecksFailed" /* BadSecurityChecksFailed */;
2971
3653
  }
2972
- const isCertificateSignatureOK = (0, import_node_opcua_crypto5.verifyCertificateSignature)(chain[0], issuerCertificate);
3654
+ const isCertificateSignatureOK = (0, import_node_opcua_crypto8.verifyCertificateSignature)(chain[0], issuerCertificate);
2973
3655
  if (!isCertificateSignatureOK) {
2974
3656
  debugLog(" the certificate was not signed by the issuer as it claim to be ! Danger");
2975
3657
  return "BadSecurityChecksFailed" /* BadSecurityChecksFailed */;
@@ -2995,7 +3677,7 @@ var CertificateManager = class _CertificateManager extends import_node_events.Ev
2995
3677
  return "BadSecurityChecksFailed" /* BadSecurityChecksFailed */;
2996
3678
  }
2997
3679
  } else {
2998
- const isCertificateSignatureOK = (0, import_node_opcua_crypto5.verifyCertificateSignature)(chain[0], chain[0]);
3680
+ const isCertificateSignatureOK = (0, import_node_opcua_crypto8.verifyCertificateSignature)(chain[0], chain[0]);
2999
3681
  if (!isCertificateSignatureOK) {
3000
3682
  debugLog("Self-signed Certificate signature is not valid");
3001
3683
  return "BadSecurityChecksFailed" /* BadSecurityChecksFailed */;
@@ -3010,23 +3692,19 @@ var CertificateManager = class _CertificateManager extends import_node_events.Ev
3010
3692
  return "BadCertificateUntrusted" /* BadCertificateUntrusted */;
3011
3693
  }
3012
3694
  }
3013
- const _c2 = chain[1] ? (0, import_node_opcua_crypto5.exploreCertificateInfo)(chain[1]) : "non";
3014
- debugLog("chain[1] info=", _c2);
3015
- const certificateInfo = (0, import_node_opcua_crypto5.exploreCertificateInfo)(chain[0]);
3695
+ const { validity } = (0, import_node_opcua_crypto8.exploreCertificate)(chain[0]).tbsCertificate;
3016
3696
  const now = /* @__PURE__ */ new Date();
3017
3697
  let isTimeInvalid = false;
3018
- if (certificateInfo.notBefore.getTime() > now.getTime()) {
3698
+ if (validity.notBefore.getTime() > now.getTime()) {
3019
3699
  debugLog(
3020
- `${import_chalk6.default.red("certificate is invalid : certificate is not active yet !")} not before date =${certificateInfo.notBefore}`
3700
+ `${import_chalk6.default.red("certificate is invalid : certificate is not active yet !")} not before date =${validity.notBefore}`
3021
3701
  );
3022
3702
  if (!options.acceptPendingCertificate) {
3023
3703
  isTimeInvalid = true;
3024
3704
  }
3025
3705
  }
3026
- if (certificateInfo.notAfter.getTime() <= now.getTime()) {
3027
- debugLog(
3028
- `${import_chalk6.default.red("certificate is invalid : certificate has expired !")} not after date =${certificateInfo.notAfter}`
3029
- );
3706
+ if (validity.notAfter.getTime() <= now.getTime()) {
3707
+ debugLog(`${import_chalk6.default.red("certificate is invalid : certificate has expired !")} not after date =${validity.notAfter}`);
3030
3708
  if (!options.acceptOutdatedCertificate) {
3031
3709
  isTimeInvalid = true;
3032
3710
  }
@@ -3065,7 +3743,7 @@ var CertificateManager = class _CertificateManager extends import_node_events.Ev
3065
3743
  const chain = coerceCertificateChain(certificate);
3066
3744
  for (const element of chain) {
3067
3745
  try {
3068
- (0, import_node_opcua_crypto5.exploreCertificateInfo)(element);
3746
+ (0, import_node_opcua_crypto8.exploreCertificate)(element);
3069
3747
  } catch (_err) {
3070
3748
  return "BadCertificateInvalid" /* BadCertificateInvalid */;
3071
3749
  }
@@ -3118,55 +3796,55 @@ var CertificateManager = class _CertificateManager extends import_node_events.Ev
3118
3796
  this.#initializingPromise = void 0;
3119
3797
  this.state = 2 /* Initialized */;
3120
3798
  _CertificateManager.#activeInstances.add(this);
3121
- _CertificateManager.#installProcessCleanup();
3799
+ _CertificateManager.#installExitCleanup();
3122
3800
  }
3123
3801
  async #initialize() {
3124
3802
  const pkiDir = this.#location;
3125
3803
  mkdirRecursiveSync(pkiDir);
3126
- mkdirRecursiveSync(import_node_path6.default.join(pkiDir, "own"));
3127
- mkdirRecursiveSync(import_node_path6.default.join(pkiDir, "own/certs"));
3128
- ensurePrivateDirectory(import_node_path6.default.join(pkiDir, "own/private"));
3129
- mkdirRecursiveSync(import_node_path6.default.join(pkiDir, "rejected"));
3130
- mkdirRecursiveSync(import_node_path6.default.join(pkiDir, "trusted"));
3131
- mkdirRecursiveSync(import_node_path6.default.join(pkiDir, "trusted/certs"));
3132
- mkdirRecursiveSync(import_node_path6.default.join(pkiDir, "trusted/crl"));
3133
- mkdirRecursiveSync(import_node_path6.default.join(pkiDir, "issuers"));
3134
- mkdirRecursiveSync(import_node_path6.default.join(pkiDir, "issuers/certs"));
3135
- mkdirRecursiveSync(import_node_path6.default.join(pkiDir, "issuers/crl"));
3136
- const ownsDiskKey = !this.#privateKeyProvider;
3137
- const needsKeyGeneration = ownsDiskKey && !import_node_fs10.default.existsSync(this.privateKey);
3804
+ mkdirRecursiveSync(import_node_path8.default.join(pkiDir, "own"));
3805
+ mkdirRecursiveSync(import_node_path8.default.join(pkiDir, "own/certs"));
3806
+ ensurePrivateDirectory(import_node_path8.default.join(pkiDir, "own/private"));
3807
+ mkdirRecursiveSync(import_node_path8.default.join(pkiDir, "rejected"));
3808
+ mkdirRecursiveSync(import_node_path8.default.join(pkiDir, "trusted"));
3809
+ mkdirRecursiveSync(import_node_path8.default.join(pkiDir, "trusted/certs"));
3810
+ mkdirRecursiveSync(import_node_path8.default.join(pkiDir, "trusted/crl"));
3811
+ mkdirRecursiveSync(import_node_path8.default.join(pkiDir, "issuers"));
3812
+ mkdirRecursiveSync(import_node_path8.default.join(pkiDir, "issuers/certs"));
3813
+ mkdirRecursiveSync(import_node_path8.default.join(pkiDir, "issuers/crl"));
3814
+ const ownsDiskKey = !this.#privateKeyProvider && !this.#keyOperations;
3815
+ const needsKeyGeneration = ownsDiskKey && !import_node_fs11.default.existsSync(this.privateKey);
3138
3816
  const needsKeyEncryption = ownsDiskKey && !needsKeyGeneration && this.#privateKeyPassphrase !== void 0 && !isEncryptedPrivateKeyFile(this.privateKey);
3139
- if (!import_node_fs10.default.existsSync(this.configFile) || needsKeyGeneration || needsKeyEncryption) {
3817
+ if (!import_node_fs11.default.existsSync(this.configFile) || needsKeyGeneration || needsKeyEncryption) {
3140
3818
  return await this.withLock2(async () => {
3141
3819
  if (this.state === 3 /* Disposing */ || this.state === 4 /* Disposed */) {
3142
3820
  return;
3143
3821
  }
3144
- if (!import_node_fs10.default.existsSync(this.configFile)) {
3145
- import_node_fs10.default.writeFileSync(this.configFile, configurationFileSimpleTemplate2);
3822
+ if (!import_node_fs11.default.existsSync(this.configFile)) {
3823
+ import_node_fs11.default.writeFileSync(this.configFile, configurationFileSimpleTemplate);
3146
3824
  }
3147
- if (ownsDiskKey && !import_node_fs10.default.existsSync(this.privateKey)) {
3825
+ if (ownsDiskKey && !import_node_fs11.default.existsSync(this.privateKey)) {
3148
3826
  debugLog("generating private key ...");
3149
3827
  const passphrase = await resolvePrivateKeyPassphrase(this.#privateKeyPassphrase);
3150
- await (0, import_node_opcua_crypto5.generatePrivateKeyFile)(this.privateKey, this.keySize, { passphrase });
3151
- this.#cachedPrivateKey = (0, import_node_opcua_crypto5.readPrivateKey)(this.privateKey, passphrase);
3828
+ await (0, import_node_opcua_crypto8.generatePrivateKeyFile)(this.privateKey, this.keySize, { passphrase });
3829
+ this.#cachedPrivateKey = (0, import_node_opcua_crypto8.readPrivateKey)(this.privateKey, passphrase);
3152
3830
  } else if (ownsDiskKey && this.#privateKeyPassphrase !== void 0 && !isEncryptedPrivateKeyFile(this.privateKey)) {
3153
3831
  warningLog("initialize: private key is plaintext but a passphrase is configured; encrypting it in place");
3154
3832
  const passphrase = await resolvePrivateKeyPassphrase(this.#privateKeyPassphrase);
3155
- const plaintextKey = (0, import_node_opcua_crypto5.readPrivateKey)(this.privateKey);
3833
+ const plaintextKey = (0, import_node_opcua_crypto8.readPrivateKey)(this.privateKey);
3156
3834
  await this.#rewritePrivateKeyFile(plaintextKey, passphrase);
3157
3835
  this.#cachedPrivateKey = plaintextKey;
3158
3836
  }
3159
3837
  if (ownsDiskKey) {
3160
3838
  restrictPrivateFilePermissions(this.privateKey, 384);
3161
3839
  }
3162
- await this.getPrivateKey();
3840
+ await this.#probePrivateKey();
3163
3841
  await this.#readCertificates();
3164
3842
  });
3165
3843
  } else {
3166
3844
  if (ownsDiskKey) {
3167
3845
  restrictPrivateFilePermissions(this.privateKey, 384);
3168
3846
  }
3169
- await this.getPrivateKey();
3847
+ await this.#probePrivateKey();
3170
3848
  await this.#readCertificates();
3171
3849
  }
3172
3850
  }
@@ -3190,7 +3868,7 @@ var CertificateManager = class _CertificateManager extends import_node_events.Ev
3190
3868
  }
3191
3869
  try {
3192
3870
  this.state = 3 /* Disposing */;
3193
- await (0, import_global_mutex.drainPendingLocks)();
3871
+ await (0, import_global_mutex2.drainPendingLocks)();
3194
3872
  for (const unreff of this.#pendingUnrefs) {
3195
3873
  unreff();
3196
3874
  }
@@ -3205,6 +3883,7 @@ var CertificateManager = class _CertificateManager extends import_node_events.Ev
3205
3883
  this.#cachedPrivateKey = void 0;
3206
3884
  this.#privateKeyPromise = void 0;
3207
3885
  _CertificateManager.#activeInstances.delete(this);
3886
+ _CertificateManager.#uninstallExitCleanupIfIdle();
3208
3887
  }
3209
3888
  }
3210
3889
  /**
@@ -3232,8 +3911,8 @@ var CertificateManager = class _CertificateManager extends import_node_events.Ev
3232
3911
  await this.#readCertificates();
3233
3912
  }
3234
3913
  async withLock2(action) {
3235
- const lockFileName = import_node_path6.default.join(this.rootDir, "mutex");
3236
- return (0, import_global_mutex.withLock)({ fileToLock: lockFileName }, async () => {
3914
+ const lockFileName = import_node_path8.default.join(this.rootDir, "mutex");
3915
+ return (0, import_global_mutex2.withLock)({ fileToLock: lockFileName }, async () => {
3237
3916
  return await action();
3238
3917
  });
3239
3918
  }
@@ -3250,16 +3929,16 @@ var CertificateManager = class _CertificateManager extends import_node_events.Ev
3250
3929
  if (typeof params.applicationUri !== "string") {
3251
3930
  throw new Error("createSelfSignedCertificate: expecting applicationUri to be a string");
3252
3931
  }
3253
- if (!this.#privateKeyProvider && !import_node_fs10.default.existsSync(this.privateKey)) {
3932
+ if (!this.#privateKeyProvider && !this.#keyOperations && !import_node_fs11.default.existsSync(this.privateKey)) {
3254
3933
  throw new Error(`Cannot find private key ${this.privateKey}`);
3255
3934
  }
3256
- let certificateFilename = import_node_path6.default.join(this.rootDir, "own/certs/self_signed_certificate.pem");
3935
+ let certificateFilename = import_node_path8.default.join(this.rootDir, "own/certs/self_signed_certificate.pem");
3257
3936
  certificateFilename = params.outputFile || certificateFilename;
3258
3937
  const _params = {
3259
3938
  ...params,
3260
3939
  rootDir: this.rootDir,
3261
3940
  configFile: this.configFile,
3262
- privateKey: await this.getPrivateKey(),
3941
+ privateKey: await this.#resolveSigningKey(),
3263
3942
  subject: params.subject || "CN=FIXME"
3264
3943
  };
3265
3944
  await this.withLock2(async () => {
@@ -3285,14 +3964,14 @@ var CertificateManager = class _CertificateManager extends import_node_events.Ev
3285
3964
  }
3286
3965
  const _params = {
3287
3966
  ...params,
3288
- rootDir: import_node_path6.default.resolve(this.rootDir),
3289
- configFile: import_node_path6.default.resolve(this.configFile),
3290
- privateKey: await this.getPrivateKey()
3967
+ rootDir: import_node_path8.default.resolve(this.rootDir),
3968
+ configFile: import_node_path8.default.resolve(this.configFile),
3969
+ privateKey: await this.#resolveSigningKey()
3291
3970
  };
3292
3971
  return await this.withLock2(async () => {
3293
3972
  const now = /* @__PURE__ */ new Date();
3294
3973
  const today = `${now.toISOString().slice(0, 10)}_${now.getTime()}`;
3295
- const certificateSigningRequestFilename = import_node_path6.default.join(this.rootDir, "own/certs", `certificate_${today}.csr`);
3974
+ const certificateSigningRequestFilename = import_node_path8.default.join(this.rootDir, "own/certs", `certificate_${today}.csr`);
3296
3975
  await createCertificateSigningRequestAsync(certificateSigningRequestFilename, _params);
3297
3976
  return certificateSigningRequestFilename;
3298
3977
  });
@@ -3312,13 +3991,13 @@ var CertificateManager = class _CertificateManager extends import_node_events.Ev
3312
3991
  return status;
3313
3992
  }
3314
3993
  }
3315
- const pemCertificate = (0, import_node_opcua_crypto5.toPem)(certificate, "CERTIFICATE");
3994
+ const pemCertificate = (0, import_node_opcua_crypto8.toPem)(certificate, "CERTIFICATE");
3316
3995
  const fingerprint = makeFingerprint(certificate);
3317
3996
  if (this.#thumbs.issuers.certs.has(fingerprint)) {
3318
3997
  return "Good" /* Good */;
3319
3998
  }
3320
- const filename = import_node_path6.default.join(this.issuersCertFolder, `issuer_${buildIdealCertificateName(certificate)}.pem`);
3321
- await import_node_fs10.default.promises.writeFile(filename, pemCertificate, "ascii");
3999
+ const filename = import_node_path8.default.join(this.issuersCertFolder, `issuer_${buildIdealCertificateName(certificate)}.pem`);
4000
+ await import_node_fs11.default.promises.writeFile(filename, pemCertificate, "ascii");
3322
4001
  this.#thumbs.issuers.certs.set(fingerprint, { certificate, filename });
3323
4002
  if (addInTrustList) {
3324
4003
  await this.trustCertificate(certificate);
@@ -3352,15 +4031,15 @@ var CertificateManager = class _CertificateManager extends import_node_events.Ev
3352
4031
  try {
3353
4032
  const index = target === "trusted" ? this.#thumbs.crl : this.#thumbs.issuersCrl;
3354
4033
  const folder = target === "trusted" ? this.crlFolder : this.issuersCrlFolder;
3355
- const crlInfo = (0, import_node_opcua_crypto5.exploreCertificateRevocationList)(crl);
4034
+ const crlInfo = (0, import_node_opcua_crypto8.exploreCertificateRevocationList)(crl);
3356
4035
  const key = crlInfo.tbsCertList.issuerFingerprint;
3357
4036
  if (!index.has(key)) {
3358
4037
  index.set(key, { crls: [], serialNumbers: {} });
3359
4038
  }
3360
- const pemCertificate = (0, import_node_opcua_crypto5.toPem)(crl, "X509 CRL");
4039
+ const pemCertificate = (0, import_node_opcua_crypto8.toPem)(crl, "X509 CRL");
3361
4040
  const sanitizedKey = key.replace(/:/g, "");
3362
- const filename = import_node_path6.default.join(folder, `crl_[${sanitizedKey}].pem`);
3363
- await import_node_fs10.default.promises.writeFile(filename, pemCertificate, "ascii");
4041
+ const filename = import_node_path8.default.join(folder, `crl_[${sanitizedKey}].pem`);
4042
+ await import_node_fs11.default.promises.writeFile(filename, pemCertificate, "ascii");
3364
4043
  await this.#onCrlFileAdded(index, filename);
3365
4044
  await this.#waitAndCheckCRLProcessingStatus();
3366
4045
  return "Good" /* Good */;
@@ -3379,11 +4058,11 @@ var CertificateManager = class _CertificateManager extends import_node_events.Ev
3379
4058
  async clearRevocationLists(target) {
3380
4059
  const clearFolder = async (folder, index) => {
3381
4060
  try {
3382
- const files = await import_node_fs10.default.promises.readdir(folder);
4061
+ const files = await import_node_fs11.default.promises.readdir(folder);
3383
4062
  for (const file of files) {
3384
- const ext = import_node_path6.default.extname(file).toLowerCase();
4063
+ const ext = import_node_path8.default.extname(file).toLowerCase();
3385
4064
  if (ext === ".crl" || ext === ".pem" || ext === ".der") {
3386
- await import_node_fs10.default.promises.unlink(import_node_path6.default.join(folder, file));
4065
+ await import_node_fs11.default.promises.unlink(import_node_path8.default.join(folder, file));
3387
4066
  }
3388
4067
  }
3389
4068
  } catch (err) {
@@ -3425,7 +4104,7 @@ var CertificateManager = class _CertificateManager extends import_node_events.Ev
3425
4104
  return null;
3426
4105
  }
3427
4106
  try {
3428
- await import_node_fs10.default.promises.unlink(entry.filename);
4107
+ await import_node_fs11.default.promises.unlink(entry.filename);
3429
4108
  } catch (err) {
3430
4109
  if (err.code !== "ENOENT") {
3431
4110
  throw err;
@@ -3449,7 +4128,7 @@ var CertificateManager = class _CertificateManager extends import_node_events.Ev
3449
4128
  return null;
3450
4129
  }
3451
4130
  try {
3452
- await import_node_fs10.default.promises.unlink(entry.filename);
4131
+ await import_node_fs11.default.promises.unlink(entry.filename);
3453
4132
  } catch (err) {
3454
4133
  if (err.code !== "ENOENT") {
3455
4134
  throw err;
@@ -3465,14 +4144,14 @@ var CertificateManager = class _CertificateManager extends import_node_events.Ev
3465
4144
  * @param target - "issuers", "trusted", or "all" (default "all")
3466
4145
  */
3467
4146
  async removeRevocationListsForIssuer(issuerCertificate, target = "all") {
3468
- const issuerInfo = (0, import_node_opcua_crypto5.exploreCertificate)(issuerCertificate);
4147
+ const issuerInfo = (0, import_node_opcua_crypto8.exploreCertificate)(issuerCertificate);
3469
4148
  const issuerFingerprint = issuerInfo.tbsCertificate.subjectFingerPrint;
3470
4149
  const processIndex = async (index) => {
3471
4150
  const crlData = index.get(issuerFingerprint);
3472
4151
  if (!crlData) return;
3473
4152
  for (const crlEntry of crlData.crls) {
3474
4153
  try {
3475
- await import_node_fs10.default.promises.unlink(crlEntry.filename);
4154
+ await import_node_fs11.default.promises.unlink(crlEntry.filename);
3476
4155
  } catch (err) {
3477
4156
  if (err.code !== "ENOENT") {
3478
4157
  throw err;
@@ -3539,7 +4218,7 @@ var CertificateManager = class _CertificateManager extends import_node_events.Ev
3539
4218
  const opts = this.#addCertValidation;
3540
4219
  let leafInfo;
3541
4220
  try {
3542
- leafInfo = (0, import_node_opcua_crypto5.exploreCertificate)(leafCertificate);
4221
+ leafInfo = (0, import_node_opcua_crypto8.exploreCertificate)(leafCertificate);
3543
4222
  } catch (_err) {
3544
4223
  return "BadCertificateInvalid" /* BadCertificateInvalid */;
3545
4224
  }
@@ -3553,23 +4232,18 @@ var CertificateManager = class _CertificateManager extends import_node_events.Ev
3553
4232
  return "BadSecurityChecksFailed" /* BadSecurityChecksFailed */;
3554
4233
  }
3555
4234
  if (!opts.acceptExpiredCertificate) {
3556
- let certDetails;
3557
- try {
3558
- certDetails = (0, import_node_opcua_crypto5.exploreCertificateInfo)(currentCert);
3559
- } catch (_err) {
3560
- return "BadCertificateInvalid" /* BadCertificateInvalid */;
3561
- }
4235
+ const { validity } = currentInfo.tbsCertificate;
3562
4236
  const now = /* @__PURE__ */ new Date();
3563
- if (certDetails.notBefore.getTime() > now.getTime()) {
4237
+ if (validity.notBefore.getTime() > now.getTime()) {
3564
4238
  return "BadCertificateTimeInvalid" /* BadCertificateTimeInvalid */;
3565
4239
  }
3566
- if (certDetails.notAfter.getTime() <= now.getTime()) {
4240
+ if (validity.notAfter.getTime() <= now.getTime()) {
3567
4241
  return depth === 1 ? "BadCertificateTimeInvalid" /* BadCertificateTimeInvalid */ : "BadCertificateIssuerTimeInvalid" /* BadCertificateIssuerTimeInvalid */;
3568
4242
  }
3569
4243
  }
3570
4244
  if (isSelfSigned2(currentInfo)) {
3571
4245
  try {
3572
- if (!(0, import_node_opcua_crypto5.verifyCertificateSignature)(currentCert, currentCert)) {
4246
+ if (!(0, import_node_opcua_crypto8.verifyCertificateSignature)(currentCert, currentCert)) {
3573
4247
  return "BadCertificateInvalid" /* BadCertificateInvalid */;
3574
4248
  }
3575
4249
  } catch (_err) {
@@ -3585,7 +4259,7 @@ var CertificateManager = class _CertificateManager extends import_node_events.Ev
3585
4259
  }
3586
4260
  }
3587
4261
  try {
3588
- if (!(0, import_node_opcua_crypto5.verifyCertificateSignature)(currentCert, issuerCert)) {
4262
+ if (!(0, import_node_opcua_crypto8.verifyCertificateSignature)(currentCert, issuerCert)) {
3589
4263
  return "BadCertificateInvalid" /* BadCertificateInvalid */;
3590
4264
  }
3591
4265
  } catch (_err) {
@@ -3607,7 +4281,7 @@ var CertificateManager = class _CertificateManager extends import_node_events.Ev
3607
4281
  }
3608
4282
  currentCert = issuerCert;
3609
4283
  try {
3610
- currentInfo = (0, import_node_opcua_crypto5.exploreCertificate)(currentCert);
4284
+ currentInfo = (0, import_node_opcua_crypto8.exploreCertificate)(currentCert);
3611
4285
  } catch (_err) {
3612
4286
  return "BadCertificateInvalid" /* BadCertificateInvalid */;
3613
4287
  }
@@ -3632,7 +4306,7 @@ var CertificateManager = class _CertificateManager extends import_node_events.Ev
3632
4306
  for (const entry of this.#thumbs.trusted.values()) {
3633
4307
  if (!entry.certificate) continue;
3634
4308
  try {
3635
- if ((0, import_node_opcua_crypto5.verifyCertificateSignature)(entry.certificate, issuerCertificate)) {
4309
+ if ((0, import_node_opcua_crypto8.verifyCertificateSignature)(entry.certificate, issuerCertificate)) {
3636
4310
  return true;
3637
4311
  }
3638
4312
  } catch (_err) {
@@ -3656,7 +4330,7 @@ var CertificateManager = class _CertificateManager extends import_node_events.Ev
3656
4330
  */
3657
4331
  async findIssuerCertificate(certificate) {
3658
4332
  const firstCertificate = coerceCertificateChain(certificate)[0];
3659
- const certInfo = (0, import_node_opcua_crypto5.exploreCertificate)(firstCertificate);
4333
+ const certInfo = (0, import_node_opcua_crypto8.exploreCertificate)(firstCertificate);
3660
4334
  if (isSelfSigned2(certInfo)) {
3661
4335
  return firstCertificate;
3662
4336
  }
@@ -3714,7 +4388,7 @@ var CertificateManager = class _CertificateManager extends import_node_events.Ev
3714
4388
  let depth = 0;
3715
4389
  while (depth < maxDepth) {
3716
4390
  const lastCert = result[result.length - 1];
3717
- const lastInfo = (0, import_node_opcua_crypto5.exploreCertificate)(lastCert);
4391
+ const lastInfo = (0, import_node_opcua_crypto8.exploreCertificate)(lastCert);
3718
4392
  if (isSelfSigned2(lastInfo)) {
3719
4393
  const wasExtended = result.length > chain.length;
3720
4394
  return {
@@ -3741,7 +4415,7 @@ var CertificateManager = class _CertificateManager extends import_node_events.Ev
3741
4415
  return {
3742
4416
  chain: result,
3743
4417
  status: "AlreadyComplete" /* AlreadyComplete */,
3744
- message: `Chain ends at root "${(0, import_node_opcua_crypto5.exploreCertificate)(issuerCert).tbsCertificate.subject.commonName}" (already present in chain).`
4418
+ message: `Chain ends at root "${(0, import_node_opcua_crypto8.exploreCertificate)(issuerCert).tbsCertificate.subject.commonName}" (already present in chain).`
3745
4419
  };
3746
4420
  }
3747
4421
  result.push(issuerCert);
@@ -3780,9 +4454,9 @@ var CertificateManager = class _CertificateManager extends import_node_events.Ev
3780
4454
  const fingerprint = makeFingerprint(certificate);
3781
4455
  let status = await this.#checkRejectedOrTrusted(certificate);
3782
4456
  if (status === "unknown") {
3783
- const pem = (0, import_node_opcua_crypto5.toPem)(chain, "CERTIFICATE");
3784
- const filename = import_node_path6.default.join(this.rejectedFolder, `${buildIdealCertificateName(certificate)}.pem`);
3785
- await import_node_fs10.default.promises.writeFile(filename, pem);
4457
+ const pem = (0, import_node_opcua_crypto8.toPem)(chain, "CERTIFICATE");
4458
+ const filename = import_node_path8.default.join(this.rejectedFolder, `${buildIdealCertificateName(certificate)}.pem`);
4459
+ await import_node_fs11.default.promises.writeFile(filename, pem);
3786
4460
  this.#thumbs.rejected.set(fingerprint, { certificate, filename });
3787
4461
  status = "rejected";
3788
4462
  }
@@ -3798,10 +4472,10 @@ var CertificateManager = class _CertificateManager extends import_node_events.Ev
3798
4472
  throw new Error(`#moveCertificate: certificate ${fingerprint.substring(0, 10)} not found in ${status} index`);
3799
4473
  }
3800
4474
  const destFolder = newStatus === "trusted" ? this.trustedFolder : this.rejectedFolder;
3801
- const certificateDest = import_node_path6.default.join(destFolder, import_node_path6.default.basename(srcEntry.filename));
4475
+ const certificateDest = import_node_path8.default.join(destFolder, import_node_path8.default.basename(srcEntry.filename));
3802
4476
  debugLog("#moveCertificate", fingerprint.substring(0, 10), "old name", srcEntry.filename);
3803
4477
  debugLog("#moveCertificate", fingerprint.substring(0, 10), "new name", certificateDest);
3804
- await import_node_fs10.default.promises.rename(srcEntry.filename, certificateDest);
4478
+ await import_node_fs11.default.promises.rename(srcEntry.filename, certificateDest);
3805
4479
  indexSrc.delete(fingerprint);
3806
4480
  const indexDest = newStatus === "trusted" ? this.#thumbs.trusted : this.#thumbs.rejected;
3807
4481
  indexDest.set(fingerprint, { certificate, filename: certificateDest });
@@ -3809,7 +4483,7 @@ var CertificateManager = class _CertificateManager extends import_node_events.Ev
3809
4483
  });
3810
4484
  }
3811
4485
  #findAssociatedCRLs(issuerCertificate) {
3812
- const issuerCertificateInfo = (0, import_node_opcua_crypto5.exploreCertificate)(issuerCertificate);
4486
+ const issuerCertificateInfo = (0, import_node_opcua_crypto8.exploreCertificate)(issuerCertificate);
3813
4487
  const key = issuerCertificateInfo.tbsCertificate.subjectFingerPrint;
3814
4488
  return this.#thumbs.issuersCrl.get(key) ?? this.#thumbs.crl.get(key) ?? null;
3815
4489
  }
@@ -3848,7 +4522,7 @@ var CertificateManager = class _CertificateManager extends import_node_events.Ev
3848
4522
  if (!crls) {
3849
4523
  return "BadCertificateRevocationUnknown" /* BadCertificateRevocationUnknown */;
3850
4524
  }
3851
- const certInfo = (0, import_node_opcua_crypto5.exploreCertificate)(firstCertificate);
4525
+ const certInfo = (0, import_node_opcua_crypto8.exploreCertificate)(firstCertificate);
3852
4526
  const serialNumber = certInfo.tbsCertificate.serialNumber || certInfo.tbsCertificate.extensions?.authorityKeyIdentifier?.serial || "";
3853
4527
  const key = certInfo.tbsCertificate.extensions?.authorityKeyIdentifier?.authorityCertIssuerFingerPrint || "<unknown>";
3854
4528
  const crl2 = this.#thumbs.crl.get(key) ?? null;
@@ -3872,8 +4546,8 @@ var CertificateManager = class _CertificateManager extends import_node_events.Ev
3872
4546
  const nextCRL = this.#queue.shift();
3873
4547
  if (!nextCRL) return;
3874
4548
  const { index, filename } = nextCRL;
3875
- const crl = await (0, import_node_opcua_crypto5.readCertificateRevocationList)(filename);
3876
- const crlInfo = (0, import_node_opcua_crypto5.exploreCertificateRevocationList)(crl);
4549
+ const crl = await (0, import_node_opcua_crypto8.readCertificateRevocationList)(filename);
4550
+ const crlInfo = (0, import_node_opcua_crypto8.exploreCertificateRevocationList)(crl);
3877
4551
  debugLog(import_chalk6.default.cyan("add CRL in folder "), filename);
3878
4552
  const fingerprint = crlInfo.tbsCertList.issuerFingerprint;
3879
4553
  if (!index.has(fingerprint)) {
@@ -3913,14 +4587,15 @@ var CertificateManager = class _CertificateManager extends import_node_events.Ev
3913
4587
  const chokidarOptions = {
3914
4588
  usePolling,
3915
4589
  ...usePolling ? { interval: pollingInterval } : {},
4590
+ depth: 0,
3916
4591
  persistent: false
3917
4592
  };
3918
4593
  const allCapturedHandles = [];
3919
- const origWatch = import_node_fs10.default.watch;
4594
+ const origWatch = import_node_fs11.default.watch;
3920
4595
  let watcherReadyCount = 0;
3921
4596
  const totalWatchers = 5;
3922
- import_node_fs10.default.watch = ((...args) => {
3923
- const handle = origWatch.apply(import_node_fs10.default, args);
4597
+ import_node_fs11.default.watch = ((...args) => {
4598
+ const handle = origWatch.apply(import_node_fs11.default, args);
3924
4599
  handle.setMaxListeners(handle.getMaxListeners() + 1);
3925
4600
  handle.on("error", () => {
3926
4601
  });
@@ -3936,7 +4611,7 @@ var CertificateManager = class _CertificateManager extends import_node_events.Ev
3936
4611
  }
3937
4612
  watcherReadyCount++;
3938
4613
  if (watcherReadyCount >= totalWatchers) {
3939
- import_node_fs10.default.watch = origWatch;
4614
+ import_node_fs11.default.watch = origWatch;
3940
4615
  }
3941
4616
  };
3942
4617
  return { w, capturedHandles: allCapturedHandles.slice(startIdx), unreffAll };
@@ -3949,7 +4624,7 @@ var CertificateManager = class _CertificateManager extends import_node_events.Ev
3949
4624
  this.#scanCrlFolder(this.issuersCrlFolder, this.#thumbs.issuersCrl)
3950
4625
  ]);
3951
4626
  if (this.#disableFileWatchers) {
3952
- import_node_fs10.default.watch = origWatch;
4627
+ import_node_fs11.default.watch = origWatch;
3953
4628
  } else {
3954
4629
  this.#startWatcher(this.trustedFolder, this.#thumbs.trusted, createUnreffedWatcher, "trusted");
3955
4630
  this.#startWatcher(this.issuersCertFolder, this.#thumbs.issuers.certs, createUnreffedWatcher, "issuersCerts");
@@ -3964,19 +4639,19 @@ var CertificateManager = class _CertificateManager extends import_node_events.Ev
3964
4639
  * file reads, preventing main-loop stalls with large folders.
3965
4640
  */
3966
4641
  async #scanCertFolder(folder, index) {
3967
- if (!import_node_fs10.default.existsSync(folder)) return;
3968
- const files = await import_node_fs10.default.promises.readdir(folder);
4642
+ if (!import_node_fs11.default.existsSync(folder)) return;
4643
+ const files = await import_node_fs11.default.promises.readdir(folder);
3969
4644
  for (const file of files) {
3970
- const filename = import_node_path6.default.join(folder, file);
4645
+ const filename = import_node_path8.default.join(folder, file);
3971
4646
  try {
3972
- const stat = await import_node_fs10.default.promises.stat(filename);
4647
+ const stat = await import_node_fs11.default.promises.stat(filename);
3973
4648
  if (!stat.isFile()) continue;
3974
- const certs = await (0, import_node_opcua_crypto5.readCertificateChainAsync)(filename);
4649
+ const certs = await (0, import_node_opcua_crypto8.readCertificateChainAsync)(filename);
3975
4650
  if (certs.length === 0) continue;
3976
4651
  const certificate = certs[0];
3977
4652
  if (certs.length > 1) {
3978
4653
  try {
3979
- await import_node_fs10.default.promises.writeFile(filename, (0, import_node_opcua_crypto5.toPem)(certs, "CERTIFICATE"), "ascii");
4654
+ await import_node_fs11.default.promises.writeFile(filename, (0, import_node_opcua_crypto8.toPem)(certs, "CERTIFICATE"), "ascii");
3980
4655
  } catch (writeErr) {
3981
4656
  debugLog(`scanCertFolder: could not rewrite legacy PEM ${filename} (read-only fs?)`, writeErr);
3982
4657
  }
@@ -3990,7 +4665,7 @@ var CertificateManager = class _CertificateManager extends import_node_events.Ev
3990
4665
  }
3991
4666
  }
3992
4667
  }
3993
- const info = (0, import_node_opcua_crypto5.exploreCertificate)(certificate);
4668
+ const info = (0, import_node_opcua_crypto8.exploreCertificate)(certificate);
3994
4669
  const fingerprint = makeFingerprint(certificate);
3995
4670
  index.set(fingerprint, { certificate, filename, info });
3996
4671
  this.#filenameToHash.set(filename, fingerprint);
@@ -4003,12 +4678,12 @@ var CertificateManager = class _CertificateManager extends import_node_events.Ev
4003
4678
  * Scan a CRL folder and populate the in-memory CRL index.
4004
4679
  */
4005
4680
  async #scanCrlFolder(folder, index) {
4006
- if (!import_node_fs10.default.existsSync(folder)) return;
4007
- const files = await import_node_fs10.default.promises.readdir(folder);
4681
+ if (!import_node_fs11.default.existsSync(folder)) return;
4682
+ const files = await import_node_fs11.default.promises.readdir(folder);
4008
4683
  for (const file of files) {
4009
- const filename = import_node_path6.default.join(folder, file);
4684
+ const filename = import_node_path8.default.join(folder, file);
4010
4685
  try {
4011
- const stat = await import_node_fs10.default.promises.stat(filename);
4686
+ const stat = await import_node_fs11.default.promises.stat(filename);
4012
4687
  if (!stat.isFile()) continue;
4013
4688
  this.#onCrlFileAdded(index, filename);
4014
4689
  } catch (err) {
@@ -4076,8 +4751,8 @@ var CertificateManager = class _CertificateManager extends import_node_events.Ev
4076
4751
  w.on("add", (filename) => {
4077
4752
  debugLog(import_chalk6.default.cyan(`add in folder ${folder}`), filename);
4078
4753
  try {
4079
- const certificate = (0, import_node_opcua_crypto5.readCertificateChain)(filename)[0];
4080
- const info = (0, import_node_opcua_crypto5.exploreCertificate)(certificate);
4754
+ const certificate = (0, import_node_opcua_crypto8.readCertificateChain)(filename)[0];
4755
+ const info = (0, import_node_opcua_crypto8.exploreCertificate)(certificate);
4081
4756
  const fingerprint = makeFingerprint(certificate);
4082
4757
  const isNew = !index.has(fingerprint);
4083
4758
  index.set(fingerprint, { certificate, filename, info });
@@ -4099,13 +4774,13 @@ var CertificateManager = class _CertificateManager extends import_node_events.Ev
4099
4774
  w.on("change", (changedPath) => {
4100
4775
  debugLog(import_chalk6.default.cyan(`change in folder ${folder}`), changedPath);
4101
4776
  try {
4102
- const certificate = (0, import_node_opcua_crypto5.readCertificateChain)(changedPath)[0];
4777
+ const certificate = (0, import_node_opcua_crypto8.readCertificateChain)(changedPath)[0];
4103
4778
  const newFingerprint = makeFingerprint(certificate);
4104
4779
  const oldHash = this.#filenameToHash.get(changedPath);
4105
4780
  if (oldHash && oldHash !== newFingerprint) {
4106
4781
  index.delete(oldHash);
4107
4782
  }
4108
- index.set(newFingerprint, { certificate, filename: changedPath, info: (0, import_node_opcua_crypto5.exploreCertificate)(certificate) });
4783
+ index.set(newFingerprint, { certificate, filename: changedPath, info: (0, import_node_opcua_crypto8.exploreCertificate)(certificate) });
4109
4784
  this.#filenameToHash.set(changedPath, newFingerprint);
4110
4785
  this.emit("certificateChange", { store, certificate, fingerprint: newFingerprint, filename: changedPath });
4111
4786
  } catch (err) {
@@ -4133,13 +4808,141 @@ var CertificateManager = class _CertificateManager extends import_node_events.Ev
4133
4808
  });
4134
4809
  }
4135
4810
  };
4811
+
4812
+ // packages/node-opcua-pki/lib/pki/toolbox_pfx.ts
4813
+ var import_node_assert8 = __toESM(require("assert"));
4814
+ var import_node_fs12 = __toESM(require("fs"));
4815
+ var import_node_opcua_crypto9 = require("node-opcua-crypto");
4816
+ async function readPfxFile(options) {
4817
+ const { pfxFile, passphrase = "" } = options;
4818
+ (0, import_node_assert8.default)(import_node_fs12.default.existsSync(pfxFile), `PFX file does not exist: ${pfxFile}`);
4819
+ try {
4820
+ return await (0, import_node_opcua_crypto9.parsePfx)(await import_node_fs12.default.promises.readFile(pfxFile), passphrase);
4821
+ } catch (err) {
4822
+ if (err instanceof import_node_opcua_crypto9.Pkcs12UnsupportedAlgorithmError) {
4823
+ const explained = new Error(
4824
+ [
4825
+ `${pfxFile} uses an encryption algorithm that is not supported (${err.message}).`,
4826
+ "Bundles written by openssl 1.x default to 40-bit RC2, which openssl 3 itself",
4827
+ "rejects unless given -legacy. Convert the file once with:",
4828
+ " openssl pkcs12 -legacy -in old.pfx -nodes -out tmp.pem",
4829
+ " openssl pkcs12 -export -in tmp.pem -out new.pfx"
4830
+ ].join("\n")
4831
+ );
4832
+ explained.cause = err;
4833
+ throw explained;
4834
+ }
4835
+ throw err;
4836
+ }
4837
+ }
4838
+ async function createPFX(options) {
4839
+ const { certificateFile, privateKeyFile, privateKeyPassphrase, outputFile, passphrase = "", caCertificateFiles } = options;
4840
+ (0, import_node_assert8.default)(import_node_fs12.default.existsSync(certificateFile), `Certificate file does not exist: ${certificateFile}`);
4841
+ (0, import_node_assert8.default)(import_node_fs12.default.existsSync(privateKeyFile), `Private key file does not exist: ${privateKeyFile}`);
4842
+ const [certificate, ...bundledIssuers] = (0, import_node_opcua_crypto9.readCertificateChain)(certificateFile);
4843
+ if (!certificate) {
4844
+ throw new Error(`Certificate file holds no certificate: ${certificateFile}`);
4845
+ }
4846
+ const certificateChain = [...bundledIssuers];
4847
+ for (const caFile of caCertificateFiles ?? []) {
4848
+ (0, import_node_assert8.default)(import_node_fs12.default.existsSync(caFile), `CA certificate file does not exist: ${caFile}`);
4849
+ certificateChain.push(...(0, import_node_opcua_crypto9.readCertificateChain)(caFile));
4850
+ }
4851
+ const privateKey = (0, import_node_opcua_crypto9.readPrivateKey)(privateKeyFile, privateKeyPassphrase);
4852
+ if (!(0, import_node_opcua_crypto9.certificateMatchesPrivateKey)(certificate, privateKey)) {
4853
+ throw new Error(`The private key ${privateKeyFile} does not match the certificate ${certificateFile}`);
4854
+ }
4855
+ const pfx = await (0, import_node_opcua_crypto9.createPfx)({
4856
+ certificate,
4857
+ certificateChain,
4858
+ privateKey,
4859
+ password: passphrase,
4860
+ friendlyName: options.friendlyName
4861
+ });
4862
+ await import_node_fs12.default.promises.writeFile(outputFile, pfx);
4863
+ }
4864
+ async function extractCertificateFromPFX(options) {
4865
+ const { certificate } = await readPfxFile(options);
4866
+ return (0, import_node_opcua_crypto9.toPem)(certificate, "CERTIFICATE");
4867
+ }
4868
+ async function extractPrivateKeyFromPFX(options) {
4869
+ const { privateKey } = await readPfxFile(options);
4870
+ return (0, import_node_opcua_crypto9.coercePrivateKeyPem)(privateKey);
4871
+ }
4872
+ async function extractCACertificatesFromPFX(options) {
4873
+ const { certificateChain } = await readPfxFile(options);
4874
+ return certificateChain.length === 0 ? "" : (0, import_node_opcua_crypto9.certificatesToPem)(certificateChain);
4875
+ }
4876
+ async function extractAllFromPFX(options) {
4877
+ const { certificate, certificateChain, privateKey, friendlyName } = await readPfxFile(options);
4878
+ return {
4879
+ certificate: (0, import_node_opcua_crypto9.toPem)(certificate, "CERTIFICATE"),
4880
+ privateKey: (0, import_node_opcua_crypto9.coercePrivateKeyPem)(privateKey),
4881
+ caCertificates: certificateChain.length === 0 ? "" : (0, import_node_opcua_crypto9.certificatesToPem)(certificateChain),
4882
+ friendlyName
4883
+ };
4884
+ }
4885
+ async function convertPFXtoPEM(pfxFile, pemFile, passphrase = "") {
4886
+ const { certificate, certificateChain, privateKey } = await readPfxFile({ pfxFile, passphrase });
4887
+ const parts = [(0, import_node_opcua_crypto9.coercePrivateKeyPem)(privateKey), (0, import_node_opcua_crypto9.toPem)(certificate, "CERTIFICATE")];
4888
+ if (certificateChain.length > 0) {
4889
+ parts.push((0, import_node_opcua_crypto9.certificatesToPem)(certificateChain));
4890
+ }
4891
+ await import_node_fs12.default.promises.writeFile(pemFile, `${parts.map((part) => part.trimEnd()).join("\n")}
4892
+ `, "utf-8");
4893
+ }
4894
+ function formatThumbprint(certificate) {
4895
+ return ((0, import_node_opcua_crypto9.makeSHA1Thumbprint)(certificate).toString("hex").toUpperCase().match(/../g) ?? []).join(":");
4896
+ }
4897
+ function describePublicKey(certificate) {
4898
+ const algorithm = certificate.publicKey.algorithm;
4899
+ if (algorithm.modulusLength) {
4900
+ return `${algorithm.name} ${algorithm.modulusLength} bits`;
4901
+ }
4902
+ if (algorithm.namedCurve) {
4903
+ return `${algorithm.name} ${algorithm.namedCurve}`;
4904
+ }
4905
+ return algorithm.name;
4906
+ }
4907
+ function describeCertificate(der, indent) {
4908
+ const certificate = new import_node_opcua_crypto9.x509.X509Certificate(der);
4909
+ return [
4910
+ `${indent}subject : ${certificate.subject}`,
4911
+ `${indent}issuer : ${certificate.issuer}`,
4912
+ `${indent}serial number : ${certificate.serialNumber}`,
4913
+ `${indent}not before : ${certificate.notBefore.toISOString()}`,
4914
+ `${indent}not after : ${certificate.notAfter.toISOString()}`,
4915
+ `${indent}public key : ${describePublicKey(certificate)}`,
4916
+ `${indent}signature : ${certificate.signatureAlgorithm.name}`,
4917
+ `${indent}SHA-1 thumbprint : ${formatThumbprint(der)}`
4918
+ ];
4919
+ }
4920
+ async function dumpPFX(pfxFile, passphrase = "") {
4921
+ const { certificate, certificateChain, privateKey, friendlyName } = await readPfxFile({ pfxFile, passphrase });
4922
+ const lines = [`PFX bundle : ${pfxFile}`];
4923
+ if (friendlyName) {
4924
+ lines.push(`friendly name : ${friendlyName}`);
4925
+ }
4926
+ const keyVerdict = (0, import_node_opcua_crypto9.certificateMatchesPrivateKey)(certificate, privateKey) ? "matches this certificate" : "DOES NOT match this certificate";
4927
+ lines.push("", "certificate", ...describeCertificate(certificate, " "), ` private key : ${keyVerdict}`);
4928
+ lines.push("", `issuer chain : ${certificateChain.length} certificate(s)`);
4929
+ for (const [index, issuer] of certificateChain.entries()) {
4930
+ lines.push(` [${index}]`, ...describeCertificate(issuer, " "));
4931
+ }
4932
+ return `${lines.join("\n")}
4933
+ `;
4934
+ }
4136
4935
  // Annotate the CommonJS export names for ESM import in node:
4137
4936
  0 && (module.exports = {
4138
4937
  CertificateAuthority,
4938
+ CertificateAuthorityCore,
4139
4939
  CertificateManager,
4140
4940
  CertificateManagerState,
4141
4941
  ChainCompletionStatus,
4942
+ NativeCaBackend,
4943
+ OpenSslCaBackend,
4142
4944
  PrivateKeyPassphraseRequiredError,
4945
+ PrivateKeyUnavailableError,
4143
4946
  Subject,
4144
4947
  VerificationStatus,
4145
4948
  adjustApplicationUri,
@@ -4156,6 +4959,7 @@ var CertificateManager = class _CertificateManager extends import_node_events.Ev
4156
4959
  install_prerequisite,
4157
4960
  isIntermediateIssuer,
4158
4961
  isIssuer,
4962
+ isOpaqueSigner,
4159
4963
  isRootIssuer,
4160
4964
  makeFingerprint,
4161
4965
  quote,