node-opcua-pki 6.21.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.mjs CHANGED
@@ -13,16 +13,20 @@ import {
13
13
  ensurePrivateDirectory,
14
14
  g_config,
15
15
  isEncryptedPrivateKeyFile,
16
+ isOpaqueSigner,
16
17
  makePath,
17
18
  mkdirRecursiveSync,
18
19
  quote,
19
20
  resolvePrivateKeyPassphrase,
20
21
  restrictPrivateFilePermissions,
21
22
  warningLog
22
- } from "./chunk-2HTNH73K.mjs";
23
+ } from "./chunk-EQ4DGI4M.mjs";
23
24
 
24
25
  // packages/node-opcua-pki/lib/index.ts
25
- import { PrivateKeyPassphraseRequiredError } from "node-opcua-crypto";
26
+ import {
27
+ PrivateKeyPassphraseRequiredError,
28
+ PrivateKeyUnavailableError as PrivateKeyUnavailableError2
29
+ } from "node-opcua-crypto";
26
30
 
27
31
  // packages/node-opcua-pki/lib/ca/backends/openssl_ca_backend.ts
28
32
  import fs4 from "fs";
@@ -975,11 +979,14 @@ import { drainPendingLocks, withLock } from "@ster5/global-mutex";
975
979
  import chalk3 from "chalk";
976
980
  import chokidar from "chokidar";
977
981
  import {
982
+ caSignerFromKeyOperations,
978
983
  exploreCertificate,
979
984
  exploreCertificateInfo,
980
985
  exploreCertificateRevocationList,
981
986
  generatePrivateKeyFile,
987
+ keyOperationsFromPrivateKey,
982
988
  makeSHA1Thumbprint,
989
+ PrivateKeyUnavailableError,
983
990
  readCertificateChain,
984
991
  readCertificateChainAsync,
985
992
  readCertificateRevocationList,
@@ -1006,8 +1013,9 @@ async function createCertificateSigningRequestAsync(certificateSigningRequestFil
1006
1013
  assert3(typeof certificateSigningRequestFilename === "string");
1007
1014
  const subject = params.subject ? new Subject3(params.subject).toString() : void 0;
1008
1015
  displaySubtitle("- Creating a Certificate Signing Request with subtile");
1009
- const privateKeyPem = typeof params.privateKey === "string" ? await fs5.promises.readFile(params.privateKey, "utf-8") : coercePrivateKeyPem(params.privateKey);
1010
- const privateKey = await pemToPrivateKey(privateKeyPem);
1016
+ const privateKey = isOpaqueSigner(params.privateKey) ? params.privateKey : await pemToPrivateKey(
1017
+ typeof params.privateKey === "string" ? await fs5.promises.readFile(params.privateKey, "utf-8") : coercePrivateKeyPem(params.privateKey)
1018
+ );
1011
1019
  const { csr } = await createCertificateSigningRequest({
1012
1020
  privateKey,
1013
1021
  dns: params.dns,
@@ -1017,7 +1025,9 @@ async function createCertificateSigningRequestAsync(certificateSigningRequestFil
1017
1025
  purpose: params.purpose
1018
1026
  });
1019
1027
  await fs5.promises.writeFile(certificateSigningRequestFilename, csr, "utf-8");
1020
- display(`- privateKey ${typeof params.privateKey === "string" ? params.privateKey : "<in-memory>"}`);
1028
+ display(
1029
+ `- privateKey ${typeof params.privateKey === "string" ? params.privateKey : isOpaqueSigner(params.privateKey) ? "<opaque-signer>" : "<in-memory>"}`
1030
+ );
1021
1031
  display(`- certificateSigningRequestFilename ${certificateSigningRequestFilename}`);
1022
1032
  }
1023
1033
 
@@ -1050,8 +1060,9 @@ async function createSelfSignedCertificateAsync(certificate, params) {
1050
1060
  subject = subject.toString();
1051
1061
  const purpose = params.purpose;
1052
1062
  displayTitle("Generate a certificate request");
1053
- const privateKeyPem = typeof params.privateKey === "string" ? await fs6.promises.readFile(params.privateKey, "utf-8") : coercePrivateKeyPem2(params.privateKey);
1054
- const privateKey = await pemToPrivateKey2(privateKeyPem);
1063
+ const privateKey = isOpaqueSigner(params.privateKey) ? params.privateKey : await pemToPrivateKey2(
1064
+ typeof params.privateKey === "string" ? await fs6.promises.readFile(params.privateKey, "utf-8") : coercePrivateKeyPem2(params.privateKey)
1065
+ );
1055
1066
  const { cert } = await createSelfSignedCertificate1({
1056
1067
  privateKey,
1057
1068
  notBefore: params.startDate,
@@ -1356,6 +1367,9 @@ var CertificateManager = class _CertificateManager extends EventEmitter {
1356
1367
  #disableFileWatchers;
1357
1368
  #privateKeyPassphrase;
1358
1369
  #privateKeyProvider;
1370
+ #keyOperations;
1371
+ /** The stable lazy wrap handed out by {@link getKeyOperations} for non-opaque configurations. */
1372
+ #lazyLocalKeyOperations;
1359
1373
  /**
1360
1374
  * The on-disk key, decrypted once and kept for the instance's lifetime,
1361
1375
  * so the passphrase (or its resolver function) is consulted at most
@@ -1399,6 +1413,17 @@ var CertificateManager = class _CertificateManager extends EventEmitter {
1399
1413
  this.#disableFileWatchers = options.disableFileWatchers ?? process.env.OPCUA_PKI_DISABLE_FILE_WATCHERS === "true";
1400
1414
  this.#privateKeyPassphrase = options.privateKeyPassphrase;
1401
1415
  this.#privateKeyProvider = options.privateKeyProvider;
1416
+ this.#keyOperations = options.keyOperations;
1417
+ if (this.#keyOperations && this.#privateKeyProvider) {
1418
+ throw new Error(
1419
+ "CertificateManager: 'keyOperations' and 'privateKeyProvider' are mutually exclusive: one hides the key, the other sources its material"
1420
+ );
1421
+ }
1422
+ if (this.#keyOperations && this.#privateKeyPassphrase) {
1423
+ throw new Error(
1424
+ "CertificateManager: 'privateKeyPassphrase' is meaningless with 'keyOperations': there is no key material for a passphrase to protect"
1425
+ );
1426
+ }
1402
1427
  mkdirRecursiveSync(options.location);
1403
1428
  if (!fs7.existsSync(this.#location)) {
1404
1429
  throw new Error(`CertificateManager cannot access location ${this.#location}`);
@@ -1440,6 +1465,11 @@ var CertificateManager = class _CertificateManager extends EventEmitter {
1440
1465
  * authority on what the current key is.
1441
1466
  */
1442
1467
  async getPrivateKey() {
1468
+ if (this.#keyOperations) {
1469
+ throw new PrivateKeyUnavailableError(
1470
+ "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"
1471
+ );
1472
+ }
1443
1473
  if (this.#privateKeyProvider) {
1444
1474
  return await this.#privateKeyProvider.getPrivateKey();
1445
1475
  }
@@ -1465,6 +1495,75 @@ var CertificateManager = class _CertificateManager extends EventEmitter {
1465
1495
  }
1466
1496
  /** In-flight first read of the on-disk key, so concurrent callers share one passphrase resolution. */
1467
1497
  #privateKeyPromise;
1498
+ /**
1499
+ * True when this manager's key is opaque — configured through
1500
+ * `keyOperations`, held by an HSM/KMS, never obtainable as material.
1501
+ * When true, {@link getPrivateKey} throws `PrivateKeyUnavailableError`
1502
+ * and {@link getKeyOperations} is the only way to use the key.
1503
+ */
1504
+ isPrivateKeyOpaque() {
1505
+ return !!this.#keyOperations;
1506
+ }
1507
+ /**
1508
+ * The key as an opaque {@link IKeyOperations} — the recommended way to
1509
+ * *use* the private key regardless of where it lives.
1510
+ *
1511
+ * Returns the configured `keyOperations` object when the key is opaque.
1512
+ * Otherwise returns a stable lazy wrap over {@link getPrivateKey}: its
1513
+ * methods resolve the key on first use (disk read, passphrase,
1514
+ * `privateKeyProvider` — all async), so the wrap offers no synchronous
1515
+ * fast path; callers that need one resolve the key themselves and build
1516
+ * a `LocalKeyOperations` over it. The wrap follows key rotation: a
1517
+ * `privateKeyProvider` that starts returning a different key gets a
1518
+ * fresh underlying `LocalKeyOperations`.
1519
+ */
1520
+ getKeyOperations() {
1521
+ if (this.#keyOperations) {
1522
+ return this.#keyOperations;
1523
+ }
1524
+ if (!this.#lazyLocalKeyOperations) {
1525
+ let cached;
1526
+ const resolve = async () => {
1527
+ const key = await this.getPrivateKey();
1528
+ if (!cached || cached.key !== key) {
1529
+ cached = { key, ops: keyOperationsFromPrivateKey(key) };
1530
+ }
1531
+ return cached.ops;
1532
+ };
1533
+ this.#lazyLocalKeyOperations = {
1534
+ sign: async (data, params) => (await resolve()).sign(data, params),
1535
+ decryptBlock: async (block, params) => (await resolve()).decryptBlock(block, params),
1536
+ getKeyMetadata: async () => (await resolve()).getKeyMetadata(),
1537
+ getPublicKey: async () => (await resolve()).getPublicKey()
1538
+ };
1539
+ }
1540
+ return this.#lazyLocalKeyOperations;
1541
+ }
1542
+ /**
1543
+ * Fail closed at `initialize()` time, not on the first certificate
1544
+ * operation, if the key cannot actually be used: wrong/missing
1545
+ * passphrase on an encrypted key, a broken `privateKeyProvider`, or an
1546
+ * unreachable `keyOperations` provider (probed via `getKeyMetadata`).
1547
+ */
1548
+ async #probePrivateKey() {
1549
+ if (this.#keyOperations) {
1550
+ await this.#keyOperations.getKeyMetadata();
1551
+ return;
1552
+ }
1553
+ await this.getPrivateKey();
1554
+ }
1555
+ /**
1556
+ * The key to hand to a certificate-issuance primitive: the raw
1557
+ * {@link PrivateKey} for a local configuration, or a {@link CaSigner}
1558
+ * adapted from `keyOperations` when the key is opaque — which requires
1559
+ * the provider to implement `getPublicKey` (the adapter says so if not).
1560
+ */
1561
+ async #resolveSigningKey() {
1562
+ if (this.#keyOperations) {
1563
+ return caSignerFromKeyOperations(this.#keyOperations);
1564
+ }
1565
+ return await this.getPrivateKey();
1566
+ }
1468
1567
  /**
1469
1568
  * Enable, disable, or rotate the passphrase protecting the on-disk
1470
1569
  * private key: decrypt with `oldPassphrase` (omit if the key is
@@ -1486,6 +1585,11 @@ var CertificateManager = class _CertificateManager extends EventEmitter {
1486
1585
  * disk file for this method to rewrite).
1487
1586
  */
1488
1587
  async reencryptPrivateKey(oldPassphrase, newPassphrase) {
1588
+ if (this.#keyOperations) {
1589
+ throw new PrivateKeyUnavailableError(
1590
+ "reencryptPrivateKey: not supported when keyOperations is configured \u2014 there is no key material to rewrite"
1591
+ );
1592
+ }
1489
1593
  if (this.#privateKeyProvider) {
1490
1594
  throw new Error("reencryptPrivateKey: not supported when a privateKeyProvider is configured");
1491
1595
  }
@@ -1834,7 +1938,7 @@ var CertificateManager = class _CertificateManager extends EventEmitter {
1834
1938
  mkdirRecursiveSync(path4.join(pkiDir, "issuers"));
1835
1939
  mkdirRecursiveSync(path4.join(pkiDir, "issuers/certs"));
1836
1940
  mkdirRecursiveSync(path4.join(pkiDir, "issuers/crl"));
1837
- const ownsDiskKey = !this.#privateKeyProvider;
1941
+ const ownsDiskKey = !this.#privateKeyProvider && !this.#keyOperations;
1838
1942
  const needsKeyGeneration = ownsDiskKey && !fs7.existsSync(this.privateKey);
1839
1943
  const needsKeyEncryption = ownsDiskKey && !needsKeyGeneration && this.#privateKeyPassphrase !== void 0 && !isEncryptedPrivateKeyFile(this.privateKey);
1840
1944
  if (!fs7.existsSync(this.configFile) || needsKeyGeneration || needsKeyEncryption) {
@@ -1860,14 +1964,14 @@ var CertificateManager = class _CertificateManager extends EventEmitter {
1860
1964
  if (ownsDiskKey) {
1861
1965
  restrictPrivateFilePermissions(this.privateKey, 384);
1862
1966
  }
1863
- await this.getPrivateKey();
1967
+ await this.#probePrivateKey();
1864
1968
  await this.#readCertificates();
1865
1969
  });
1866
1970
  } else {
1867
1971
  if (ownsDiskKey) {
1868
1972
  restrictPrivateFilePermissions(this.privateKey, 384);
1869
1973
  }
1870
- await this.getPrivateKey();
1974
+ await this.#probePrivateKey();
1871
1975
  await this.#readCertificates();
1872
1976
  }
1873
1977
  }
@@ -1952,7 +2056,7 @@ var CertificateManager = class _CertificateManager extends EventEmitter {
1952
2056
  if (typeof params.applicationUri !== "string") {
1953
2057
  throw new Error("createSelfSignedCertificate: expecting applicationUri to be a string");
1954
2058
  }
1955
- if (!this.#privateKeyProvider && !fs7.existsSync(this.privateKey)) {
2059
+ if (!this.#privateKeyProvider && !this.#keyOperations && !fs7.existsSync(this.privateKey)) {
1956
2060
  throw new Error(`Cannot find private key ${this.privateKey}`);
1957
2061
  }
1958
2062
  let certificateFilename = path4.join(this.rootDir, "own/certs/self_signed_certificate.pem");
@@ -1961,7 +2065,7 @@ var CertificateManager = class _CertificateManager extends EventEmitter {
1961
2065
  ...params,
1962
2066
  rootDir: this.rootDir,
1963
2067
  configFile: this.configFile,
1964
- privateKey: await this.getPrivateKey(),
2068
+ privateKey: await this.#resolveSigningKey(),
1965
2069
  subject: params.subject || "CN=FIXME"
1966
2070
  };
1967
2071
  await this.withLock2(async () => {
@@ -1989,7 +2093,7 @@ var CertificateManager = class _CertificateManager extends EventEmitter {
1989
2093
  ...params,
1990
2094
  rootDir: path4.resolve(this.rootDir),
1991
2095
  configFile: path4.resolve(this.configFile),
1992
- privateKey: await this.getPrivateKey()
2096
+ privateKey: await this.#resolveSigningKey()
1993
2097
  };
1994
2098
  return await this.withLock2(async () => {
1995
2099
  const now = /* @__PURE__ */ new Date();
@@ -2976,6 +3080,7 @@ export {
2976
3080
  NativeCaBackend,
2977
3081
  OpenSslCaBackend,
2978
3082
  PrivateKeyPassphraseRequiredError,
3083
+ PrivateKeyUnavailableError2 as PrivateKeyUnavailableError,
2979
3084
  Subject,
2980
3085
  VerificationStatus,
2981
3086
  adjustApplicationUri,
@@ -2992,6 +3097,7 @@ export {
2992
3097
  install_prerequisite,
2993
3098
  isIntermediateIssuer,
2994
3099
  isIssuer,
3100
+ isOpaqueSigner,
2995
3101
  isRootIssuer,
2996
3102
  makeFingerprint,
2997
3103
  quote,