node-opcua-pki 6.21.0 → 6.22.1

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/core.mjs CHANGED
@@ -7,7 +7,7 @@ import {
7
7
  defaultSubject,
8
8
  escapeOpensslConfDoubleQuoted,
9
9
  renderCaConfig
10
- } from "./chunk-2HTNH73K.mjs";
10
+ } from "./chunk-EQ4DGI4M.mjs";
11
11
  export {
12
12
  CaDatabase,
13
13
  CertificateAuthorityCore,
package/dist/index.d.mts CHANGED
@@ -1,7 +1,7 @@
1
- import { PrivateKey, Certificate, SubjectOptions, DER, CertificateRevocationList } from 'node-opcua-crypto';
2
- export { PrivateKeyPassphraseRequiredError, Subject, SubjectOptions } from 'node-opcua-crypto';
3
- import { C as CaBackend, a as CertificateAuthorityCore, P as Params, b as ProcessAltNamesParam, c as CertificateAuthorityCoreOptions, K as KeySize, d as PrivateKeyPassphrase, e as PrivateKeyProvider, f as CreateSelfSignCertificateParam, F as Filename } from './core-C6faZClF.mjs';
4
- export { g as CertificateStatus, h as CreateCertificateSigningRequestOptions, i as CreateCertificateSigningRequestWithConfigOptions, j as CreateSelfSignCertificateWithConfigParam, G as GenerateKeyPairAndSignOptions, k as GenerateKeyPairAndSignPFXOptions, I as InitializeCSRResult, l as InstallCACertificateResult, m as KeyLength, N as NativeCaBackend, n as PkiBackendCapabilities, S as SignCertificateOptions, o as StartDateEndDateParam, T as Thumbprint, p as adjustApplicationUri, q as adjustDate, r as quote, s as resolvePrivateKeyPassphrase } from './core-C6faZClF.mjs';
1
+ import { IKeyOperations, PrivateKey, Certificate, SubjectOptions, DER, CertificateRevocationList } from 'node-opcua-crypto';
2
+ export { AsymmetricDecryptParams, AsymmetricSignParams, IKeyOperations, KeyMetadata, PrivateKeyPassphraseRequiredError, PrivateKeyUnavailableError, Subject, SubjectOptions } from 'node-opcua-crypto';
3
+ import { C as CaBackend, a as CertificateAuthorityCore, P as Params, b as ProcessAltNamesParam, c as CertificateAuthorityCoreOptions, K as KeySize, d as PrivateKeyPassphrase, e as PrivateKeyProvider, f as CreateSelfSignCertificateParam, F as Filename } from './core-BwqSJPHc.mjs';
4
+ export { g as CertificateStatus, h as CreateCertificateSigningRequestOptions, i as CreateCertificateSigningRequestWithConfigOptions, j as CreateSelfSignCertificateWithConfigParam, G as GenerateKeyPairAndSignOptions, k as GenerateKeyPairAndSignPFXOptions, I as InitializeCSRResult, l as InstallCACertificateResult, m as KeyLength, N as NativeCaBackend, n as PkiBackendCapabilities, S as SignCertificateOptions, o as StartDateEndDateParam, T as Thumbprint, p as adjustApplicationUri, q as adjustDate, r as isOpaqueSigner, s as quote, t as resolvePrivateKeyPassphrase } from './core-BwqSJPHc.mjs';
5
5
  import { EventEmitter } from 'node:events';
6
6
 
7
7
  /**
@@ -239,6 +239,29 @@ interface CertificateManagerOptions {
239
239
  * ignored.
240
240
  */
241
241
  privateKeyProvider?: PrivateKeyProvider;
242
+ /**
243
+ * Use a private key this manager can never read: an opaque
244
+ * {@link IKeyOperations} provider (HSM, KMS, TPM, OS keystore, ...).
245
+ *
246
+ * The distinction with `privateKeyProvider` matters: a
247
+ * `privateKeyProvider` *sources raw key material* from elsewhere and
248
+ * hands it back; `keyOperations` never reveals the key — only sign and
249
+ * decrypt operations on it. Prefer `keyOperations` whenever the key
250
+ * does not need to be exportable.
251
+ *
252
+ * When set:
253
+ * - {@link CertificateManager.getPrivateKey} and
254
+ * {@link CertificateManager.reencryptPrivateKey} throw
255
+ * `PrivateKeyUnavailableError` — there is no key material to return
256
+ * or rewrite, and no on-disk key is generated or expected;
257
+ * - {@link CertificateManager.getKeyOperations} returns this object;
258
+ * - `initialize()` fails closed if the provider cannot answer
259
+ * `getKeyMetadata()` (unreachable HSM, misconfiguration);
260
+ * - mutually exclusive with `privateKeyProvider` and
261
+ * `privateKeyPassphrase` — both describe key *material*, which an
262
+ * opaque configuration does not have.
263
+ */
264
+ keyOperations?: IKeyOperations;
242
265
  }
243
266
  /**
244
267
  * Parameters for {@link createSelfSignedCertificate}.
@@ -341,6 +364,8 @@ declare enum VerificationStatus {
341
364
  }
342
365
  declare function coerceCertificateChain(certificate: Certificate | Certificate[]): Certificate[];
343
366
  declare function makeFingerprint(certificate: Certificate | Certificate[] | CertificateRevocationList): string;
367
+ declare function toFilenameLabel(commonName: string): string;
368
+ declare function safeStoreJoin(folder: string, filename: string): string;
344
369
  /**
345
370
  * Check if the provided certificate acts as an issuer (CA)
346
371
  * @param certificate - the DER-encoded certificate
@@ -533,6 +558,27 @@ declare class CertificateManager extends EventEmitter {
533
558
  * authority on what the current key is.
534
559
  */
535
560
  getPrivateKey(): Promise<PrivateKey>;
561
+ /**
562
+ * True when this manager's key is opaque — configured through
563
+ * `keyOperations`, held by an HSM/KMS, never obtainable as material.
564
+ * When true, {@link getPrivateKey} throws `PrivateKeyUnavailableError`
565
+ * and {@link getKeyOperations} is the only way to use the key.
566
+ */
567
+ isPrivateKeyOpaque(): boolean;
568
+ /**
569
+ * The key as an opaque {@link IKeyOperations} — the recommended way to
570
+ * *use* the private key regardless of where it lives.
571
+ *
572
+ * Returns the configured `keyOperations` object when the key is opaque.
573
+ * Otherwise returns a stable lazy wrap over {@link getPrivateKey}: its
574
+ * methods resolve the key on first use (disk read, passphrase,
575
+ * `privateKeyProvider` — all async), so the wrap offers no synchronous
576
+ * fast path; callers that need one resolve the key themselves and build
577
+ * a `LocalKeyOperations` over it. The wrap follows key rotation: a
578
+ * `privateKeyProvider` that starts returning a different key gets a
579
+ * fresh underlying `LocalKeyOperations`.
580
+ */
581
+ getKeyOperations(): IKeyOperations;
536
582
  /**
537
583
  * Enable, disable, or rotate the passphrase protecting the on-disk
538
584
  * private key: decrypt with `oldPassphrase` (omit if the key is
@@ -954,4 +1000,4 @@ declare function convertPFXtoPEM(pfxFile: Filename, pemFile: Filename, passphras
954
1000
  */
955
1001
  declare function dumpPFX(pfxFile: Filename, passphrase?: string): Promise<string>;
956
1002
 
957
- export { type AddCertificateValidationOptions, CaBackend, CertificateAuthority, CertificateAuthorityCore, CertificateAuthorityCoreOptions, type CertificateAuthorityOptions, CertificateManager, type CertificateManagerEvents, type CertificateManagerOptions, CertificateManagerState, type CertificateStore, type ChainCompletionResult, ChainCompletionStatus, type CreatePFXOptions, CreateSelfSignCertificateParam, type CreateSelfSignCertificateParam1, type CrlStore, type ExtractPFXOptions, type ExtractPFXResult, Filename, KeySize, OpenSslCaBackend, Params, PrivateKeyPassphrase, PrivateKeyProvider, ProcessAltNamesParam, VerificationStatus, type VerifyCertificateOptions, coerceCertificateChain, convertPFXtoPEM, createPFX, dumpPFX, extractAllFromPFX, extractCACertificatesFromPFX, extractCertificateFromPFX, extractPrivateKeyFromPFX, findIssuerCertificateInChain, install_prerequisite, isIntermediateIssuer, isIssuer, isRootIssuer, makeFingerprint };
1003
+ export { type AddCertificateValidationOptions, CaBackend, CertificateAuthority, CertificateAuthorityCore, CertificateAuthorityCoreOptions, type CertificateAuthorityOptions, CertificateManager, type CertificateManagerEvents, type CertificateManagerOptions, CertificateManagerState, type CertificateStore, type ChainCompletionResult, ChainCompletionStatus, type CreatePFXOptions, CreateSelfSignCertificateParam, type CreateSelfSignCertificateParam1, type CrlStore, type ExtractPFXOptions, type ExtractPFXResult, Filename, KeySize, OpenSslCaBackend, Params, PrivateKeyPassphrase, PrivateKeyProvider, ProcessAltNamesParam, VerificationStatus, type VerifyCertificateOptions, coerceCertificateChain, convertPFXtoPEM, createPFX, dumpPFX, extractAllFromPFX, extractCACertificatesFromPFX, extractCertificateFromPFX, extractPrivateKeyFromPFX, findIssuerCertificateInChain, install_prerequisite, isIntermediateIssuer, isIssuer, isRootIssuer, makeFingerprint, safeStoreJoin, toFilenameLabel };
package/dist/index.d.ts CHANGED
@@ -1,7 +1,7 @@
1
- import { PrivateKey, Certificate, SubjectOptions, DER, CertificateRevocationList } from 'node-opcua-crypto';
2
- export { PrivateKeyPassphraseRequiredError, Subject, SubjectOptions } from 'node-opcua-crypto';
3
- import { C as CaBackend, a as CertificateAuthorityCore, P as Params, b as ProcessAltNamesParam, c as CertificateAuthorityCoreOptions, K as KeySize, d as PrivateKeyPassphrase, e as PrivateKeyProvider, f as CreateSelfSignCertificateParam, F as Filename } from './core-C6faZClF.js';
4
- export { g as CertificateStatus, h as CreateCertificateSigningRequestOptions, i as CreateCertificateSigningRequestWithConfigOptions, j as CreateSelfSignCertificateWithConfigParam, G as GenerateKeyPairAndSignOptions, k as GenerateKeyPairAndSignPFXOptions, I as InitializeCSRResult, l as InstallCACertificateResult, m as KeyLength, N as NativeCaBackend, n as PkiBackendCapabilities, S as SignCertificateOptions, o as StartDateEndDateParam, T as Thumbprint, p as adjustApplicationUri, q as adjustDate, r as quote, s as resolvePrivateKeyPassphrase } from './core-C6faZClF.js';
1
+ import { IKeyOperations, PrivateKey, Certificate, SubjectOptions, DER, CertificateRevocationList } from 'node-opcua-crypto';
2
+ export { AsymmetricDecryptParams, AsymmetricSignParams, IKeyOperations, KeyMetadata, PrivateKeyPassphraseRequiredError, PrivateKeyUnavailableError, Subject, SubjectOptions } from 'node-opcua-crypto';
3
+ import { C as CaBackend, a as CertificateAuthorityCore, P as Params, b as ProcessAltNamesParam, c as CertificateAuthorityCoreOptions, K as KeySize, d as PrivateKeyPassphrase, e as PrivateKeyProvider, f as CreateSelfSignCertificateParam, F as Filename } from './core-BwqSJPHc.js';
4
+ export { g as CertificateStatus, h as CreateCertificateSigningRequestOptions, i as CreateCertificateSigningRequestWithConfigOptions, j as CreateSelfSignCertificateWithConfigParam, G as GenerateKeyPairAndSignOptions, k as GenerateKeyPairAndSignPFXOptions, I as InitializeCSRResult, l as InstallCACertificateResult, m as KeyLength, N as NativeCaBackend, n as PkiBackendCapabilities, S as SignCertificateOptions, o as StartDateEndDateParam, T as Thumbprint, p as adjustApplicationUri, q as adjustDate, r as isOpaqueSigner, s as quote, t as resolvePrivateKeyPassphrase } from './core-BwqSJPHc.js';
5
5
  import { EventEmitter } from 'node:events';
6
6
 
7
7
  /**
@@ -239,6 +239,29 @@ interface CertificateManagerOptions {
239
239
  * ignored.
240
240
  */
241
241
  privateKeyProvider?: PrivateKeyProvider;
242
+ /**
243
+ * Use a private key this manager can never read: an opaque
244
+ * {@link IKeyOperations} provider (HSM, KMS, TPM, OS keystore, ...).
245
+ *
246
+ * The distinction with `privateKeyProvider` matters: a
247
+ * `privateKeyProvider` *sources raw key material* from elsewhere and
248
+ * hands it back; `keyOperations` never reveals the key — only sign and
249
+ * decrypt operations on it. Prefer `keyOperations` whenever the key
250
+ * does not need to be exportable.
251
+ *
252
+ * When set:
253
+ * - {@link CertificateManager.getPrivateKey} and
254
+ * {@link CertificateManager.reencryptPrivateKey} throw
255
+ * `PrivateKeyUnavailableError` — there is no key material to return
256
+ * or rewrite, and no on-disk key is generated or expected;
257
+ * - {@link CertificateManager.getKeyOperations} returns this object;
258
+ * - `initialize()` fails closed if the provider cannot answer
259
+ * `getKeyMetadata()` (unreachable HSM, misconfiguration);
260
+ * - mutually exclusive with `privateKeyProvider` and
261
+ * `privateKeyPassphrase` — both describe key *material*, which an
262
+ * opaque configuration does not have.
263
+ */
264
+ keyOperations?: IKeyOperations;
242
265
  }
243
266
  /**
244
267
  * Parameters for {@link createSelfSignedCertificate}.
@@ -341,6 +364,8 @@ declare enum VerificationStatus {
341
364
  }
342
365
  declare function coerceCertificateChain(certificate: Certificate | Certificate[]): Certificate[];
343
366
  declare function makeFingerprint(certificate: Certificate | Certificate[] | CertificateRevocationList): string;
367
+ declare function toFilenameLabel(commonName: string): string;
368
+ declare function safeStoreJoin(folder: string, filename: string): string;
344
369
  /**
345
370
  * Check if the provided certificate acts as an issuer (CA)
346
371
  * @param certificate - the DER-encoded certificate
@@ -533,6 +558,27 @@ declare class CertificateManager extends EventEmitter {
533
558
  * authority on what the current key is.
534
559
  */
535
560
  getPrivateKey(): Promise<PrivateKey>;
561
+ /**
562
+ * True when this manager's key is opaque — configured through
563
+ * `keyOperations`, held by an HSM/KMS, never obtainable as material.
564
+ * When true, {@link getPrivateKey} throws `PrivateKeyUnavailableError`
565
+ * and {@link getKeyOperations} is the only way to use the key.
566
+ */
567
+ isPrivateKeyOpaque(): boolean;
568
+ /**
569
+ * The key as an opaque {@link IKeyOperations} — the recommended way to
570
+ * *use* the private key regardless of where it lives.
571
+ *
572
+ * Returns the configured `keyOperations` object when the key is opaque.
573
+ * Otherwise returns a stable lazy wrap over {@link getPrivateKey}: its
574
+ * methods resolve the key on first use (disk read, passphrase,
575
+ * `privateKeyProvider` — all async), so the wrap offers no synchronous
576
+ * fast path; callers that need one resolve the key themselves and build
577
+ * a `LocalKeyOperations` over it. The wrap follows key rotation: a
578
+ * `privateKeyProvider` that starts returning a different key gets a
579
+ * fresh underlying `LocalKeyOperations`.
580
+ */
581
+ getKeyOperations(): IKeyOperations;
536
582
  /**
537
583
  * Enable, disable, or rotate the passphrase protecting the on-disk
538
584
  * private key: decrypt with `oldPassphrase` (omit if the key is
@@ -954,4 +1000,4 @@ declare function convertPFXtoPEM(pfxFile: Filename, pemFile: Filename, passphras
954
1000
  */
955
1001
  declare function dumpPFX(pfxFile: Filename, passphrase?: string): Promise<string>;
956
1002
 
957
- export { type AddCertificateValidationOptions, CaBackend, CertificateAuthority, CertificateAuthorityCore, CertificateAuthorityCoreOptions, type CertificateAuthorityOptions, CertificateManager, type CertificateManagerEvents, type CertificateManagerOptions, CertificateManagerState, type CertificateStore, type ChainCompletionResult, ChainCompletionStatus, type CreatePFXOptions, CreateSelfSignCertificateParam, type CreateSelfSignCertificateParam1, type CrlStore, type ExtractPFXOptions, type ExtractPFXResult, Filename, KeySize, OpenSslCaBackend, Params, PrivateKeyPassphrase, PrivateKeyProvider, ProcessAltNamesParam, VerificationStatus, type VerifyCertificateOptions, coerceCertificateChain, convertPFXtoPEM, createPFX, dumpPFX, extractAllFromPFX, extractCACertificatesFromPFX, extractCertificateFromPFX, extractPrivateKeyFromPFX, findIssuerCertificateInChain, install_prerequisite, isIntermediateIssuer, isIssuer, isRootIssuer, makeFingerprint };
1003
+ export { type AddCertificateValidationOptions, CaBackend, CertificateAuthority, CertificateAuthorityCore, CertificateAuthorityCoreOptions, type CertificateAuthorityOptions, CertificateManager, type CertificateManagerEvents, type CertificateManagerOptions, CertificateManagerState, type CertificateStore, type ChainCompletionResult, ChainCompletionStatus, type CreatePFXOptions, CreateSelfSignCertificateParam, type CreateSelfSignCertificateParam1, type CrlStore, type ExtractPFXOptions, type ExtractPFXResult, Filename, KeySize, OpenSslCaBackend, Params, PrivateKeyPassphrase, PrivateKeyProvider, ProcessAltNamesParam, VerificationStatus, type VerifyCertificateOptions, coerceCertificateChain, convertPFXtoPEM, createPFX, dumpPFX, extractAllFromPFX, extractCACertificatesFromPFX, extractCertificateFromPFX, extractPrivateKeyFromPFX, findIssuerCertificateInChain, install_prerequisite, isIntermediateIssuer, isIssuer, isRootIssuer, makeFingerprint, safeStoreJoin, toFilenameLabel };
package/dist/index.js CHANGED
@@ -38,6 +38,7 @@ __export(lib_exports, {
38
38
  NativeCaBackend: () => NativeCaBackend,
39
39
  OpenSslCaBackend: () => OpenSslCaBackend,
40
40
  PrivateKeyPassphraseRequiredError: () => import_node_opcua_crypto10.PrivateKeyPassphraseRequiredError,
41
+ PrivateKeyUnavailableError: () => import_node_opcua_crypto10.PrivateKeyUnavailableError,
41
42
  Subject: () => import_node_opcua_crypto3.Subject,
42
43
  VerificationStatus: () => VerificationStatus,
43
44
  adjustApplicationUri: () => adjustApplicationUri,
@@ -54,10 +55,13 @@ __export(lib_exports, {
54
55
  install_prerequisite: () => install_prerequisite,
55
56
  isIntermediateIssuer: () => isIntermediateIssuer,
56
57
  isIssuer: () => isIssuer,
58
+ isOpaqueSigner: () => isOpaqueSigner,
57
59
  isRootIssuer: () => isRootIssuer,
58
60
  makeFingerprint: () => makeFingerprint,
59
61
  quote: () => quote,
60
- resolvePrivateKeyPassphrase: () => resolvePrivateKeyPassphrase
62
+ resolvePrivateKeyPassphrase: () => resolvePrivateKeyPassphrase,
63
+ safeStoreJoin: () => safeStoreJoin,
64
+ toFilenameLabel: () => toFilenameLabel
61
65
  });
62
66
  module.exports = __toCommonJS(lib_exports);
63
67
  var import_node_opcua_crypto10 = require("node-opcua-crypto");
@@ -72,6 +76,9 @@ var import_node_assert = __toESM(require("assert"));
72
76
  function quote(str) {
73
77
  return `"${str || ""}"`;
74
78
  }
79
+ function isOpaqueSigner(privateKey) {
80
+ return typeof privateKey !== "string" && typeof privateKey.sign === "function";
81
+ }
75
82
  async function resolvePrivateKeyPassphrase(passphrase) {
76
83
  if (passphrase === void 0) {
77
84
  return void 0;
@@ -1060,8 +1067,32 @@ function passinArg(passphrase = "") {
1060
1067
  function renderForDisplay(file, args) {
1061
1068
  return [file, ...args].map((a) => a === "" || /[\s"'`$\\]/.test(a) ? JSON.stringify(a) : a).join(" ");
1062
1069
  }
1070
+ function hasUnsafeArgChar(value) {
1071
+ for (const ch of value) {
1072
+ const code = ch.codePointAt(0);
1073
+ if (code < 32 || code === 127) {
1074
+ return true;
1075
+ }
1076
+ if (code < 128) {
1077
+ continue;
1078
+ }
1079
+ if (/[\p{L}\p{N}\p{M}]/u.test(ch)) {
1080
+ continue;
1081
+ }
1082
+ return true;
1083
+ }
1084
+ return false;
1085
+ }
1086
+ function checkArgs(file, args) {
1087
+ for (const value of [file, ...args]) {
1088
+ if (typeof value === "string" && hasUnsafeArgChar(value)) {
1089
+ throw new Error(`execute_openssl: refusing argument with an unexpected character: ${JSON.stringify(value)}`);
1090
+ }
1091
+ }
1092
+ }
1063
1093
  async function execute2(file, args, options) {
1064
1094
  const from = new Error();
1095
+ checkArgs(file, args);
1065
1096
  options.cwd = options.cwd || process.cwd();
1066
1097
  if (!g_config.silent) {
1067
1098
  warningLog(import_chalk4.default.cyan(" CWD "), options.cwd);
@@ -2887,8 +2918,9 @@ async function createCertificateSigningRequestAsync(certificateSigningRequestFil
2887
2918
  (0, import_node_assert6.default)(typeof certificateSigningRequestFilename === "string");
2888
2919
  const subject = params.subject ? new import_node_opcua_crypto6.Subject(params.subject).toString() : void 0;
2889
2920
  displaySubtitle("- Creating a Certificate Signing Request with subtile");
2890
- const privateKeyPem = typeof params.privateKey === "string" ? await import_node_fs9.default.promises.readFile(params.privateKey, "utf-8") : (0, import_node_opcua_crypto6.coercePrivateKeyPem)(params.privateKey);
2891
- const privateKey = await (0, import_node_opcua_crypto6.pemToPrivateKey)(privateKeyPem);
2921
+ const privateKey = isOpaqueSigner(params.privateKey) ? params.privateKey : await (0, import_node_opcua_crypto6.pemToPrivateKey)(
2922
+ typeof params.privateKey === "string" ? await import_node_fs9.default.promises.readFile(params.privateKey, "utf-8") : (0, import_node_opcua_crypto6.coercePrivateKeyPem)(params.privateKey)
2923
+ );
2892
2924
  const { csr } = await (0, import_node_opcua_crypto6.createCertificateSigningRequest)({
2893
2925
  privateKey,
2894
2926
  dns: params.dns,
@@ -2898,7 +2930,9 @@ async function createCertificateSigningRequestAsync(certificateSigningRequestFil
2898
2930
  purpose: params.purpose
2899
2931
  });
2900
2932
  await import_node_fs9.default.promises.writeFile(certificateSigningRequestFilename, csr, "utf-8");
2901
- display(`- privateKey ${typeof params.privateKey === "string" ? params.privateKey : "<in-memory>"}`);
2933
+ display(
2934
+ `- privateKey ${typeof params.privateKey === "string" ? params.privateKey : isOpaqueSigner(params.privateKey) ? "<opaque-signer>" : "<in-memory>"}`
2935
+ );
2902
2936
  display(`- certificateSigningRequestFilename ${certificateSigningRequestFilename}`);
2903
2937
  }
2904
2938
 
@@ -2925,8 +2959,9 @@ async function createSelfSignedCertificateAsync(certificate, params) {
2925
2959
  subject = subject.toString();
2926
2960
  const purpose = params.purpose;
2927
2961
  displayTitle("Generate a certificate request");
2928
- const privateKeyPem = typeof params.privateKey === "string" ? await import_node_fs10.default.promises.readFile(params.privateKey, "utf-8") : (0, import_node_opcua_crypto7.coercePrivateKeyPem)(params.privateKey);
2929
- const privateKey = await (0, import_node_opcua_crypto7.pemToPrivateKey)(privateKeyPem);
2962
+ const privateKey = isOpaqueSigner(params.privateKey) ? params.privateKey : await (0, import_node_opcua_crypto7.pemToPrivateKey)(
2963
+ typeof params.privateKey === "string" ? await import_node_fs10.default.promises.readFile(params.privateKey, "utf-8") : (0, import_node_opcua_crypto7.coercePrivateKeyPem)(params.privateKey)
2964
+ );
2930
2965
  const { cert } = await (0, import_node_opcua_crypto7.createSelfSignedCertificate)({
2931
2966
  privateKey,
2932
2967
  notBefore: params.startDate,
@@ -2992,18 +3027,31 @@ function makeFingerprint(certificate) {
2992
3027
  function short(stringToShorten) {
2993
3028
  return stringToShorten.substring(0, 10);
2994
3029
  }
2995
- var forbiddenChars = /[\x00-\x1F<>:"/\\|?*]/g;
3030
+ var disallowedInLabel = /[^A-Za-z0-9._ -]/g;
3031
+ function toFilenameLabel(commonName) {
3032
+ const label = (commonName || "").normalize("NFKC").replace(disallowedInLabel, "_").replace(/[^\x20-\x7E]/g, "").slice(0, 64).trim();
3033
+ return label || "certificate";
3034
+ }
2996
3035
  function buildIdealCertificateName(certificate) {
2997
3036
  const chain = coerceCertificateChain(certificate);
2998
3037
  const fingerprint = makeFingerprint(chain);
2999
3038
  try {
3000
3039
  const commonName = (0, import_node_opcua_crypto8.exploreCertificate)(chain[0]).tbsCertificate.subject.commonName || "";
3001
- const sanitizedCommonName = commonName.replace(forbiddenChars, "_");
3040
+ const sanitizedCommonName = toFilenameLabel(commonName);
3002
3041
  return `${sanitizedCommonName}[${fingerprint}]`;
3003
3042
  } catch (_err) {
3004
3043
  return `invalid_certificate_[${fingerprint}]`;
3005
3044
  }
3006
3045
  }
3046
+ function safeStoreJoin(folder, filename) {
3047
+ const full = import_node_path8.default.resolve(folder, filename);
3048
+ const base = import_node_path8.default.resolve(folder);
3049
+ if (full !== base && !full.startsWith(base + import_node_path8.default.sep)) {
3050
+ warningLog("refusing to write certificate outside its store folder:", filename);
3051
+ throw new Error(`certificate filename escapes its store folder: ${filename}`);
3052
+ }
3053
+ return full;
3054
+ }
3007
3055
  function findMatchingIssuerKey(entries, wantedIssuerKey) {
3008
3056
  return entries.filter((entry) => {
3009
3057
  const info = getOrComputeInfo(entry);
@@ -3231,6 +3279,9 @@ var CertificateManager = class _CertificateManager extends import_node_events.Ev
3231
3279
  #disableFileWatchers;
3232
3280
  #privateKeyPassphrase;
3233
3281
  #privateKeyProvider;
3282
+ #keyOperations;
3283
+ /** The stable lazy wrap handed out by {@link getKeyOperations} for non-opaque configurations. */
3284
+ #lazyLocalKeyOperations;
3234
3285
  /**
3235
3286
  * The on-disk key, decrypted once and kept for the instance's lifetime,
3236
3287
  * so the passphrase (or its resolver function) is consulted at most
@@ -3274,6 +3325,17 @@ var CertificateManager = class _CertificateManager extends import_node_events.Ev
3274
3325
  this.#disableFileWatchers = options.disableFileWatchers ?? process.env.OPCUA_PKI_DISABLE_FILE_WATCHERS === "true";
3275
3326
  this.#privateKeyPassphrase = options.privateKeyPassphrase;
3276
3327
  this.#privateKeyProvider = options.privateKeyProvider;
3328
+ this.#keyOperations = options.keyOperations;
3329
+ if (this.#keyOperations && this.#privateKeyProvider) {
3330
+ throw new Error(
3331
+ "CertificateManager: 'keyOperations' and 'privateKeyProvider' are mutually exclusive: one hides the key, the other sources its material"
3332
+ );
3333
+ }
3334
+ if (this.#keyOperations && this.#privateKeyPassphrase) {
3335
+ throw new Error(
3336
+ "CertificateManager: 'privateKeyPassphrase' is meaningless with 'keyOperations': there is no key material for a passphrase to protect"
3337
+ );
3338
+ }
3277
3339
  mkdirRecursiveSync(options.location);
3278
3340
  if (!import_node_fs11.default.existsSync(this.#location)) {
3279
3341
  throw new Error(`CertificateManager cannot access location ${this.#location}`);
@@ -3315,6 +3377,11 @@ var CertificateManager = class _CertificateManager extends import_node_events.Ev
3315
3377
  * authority on what the current key is.
3316
3378
  */
3317
3379
  async getPrivateKey() {
3380
+ if (this.#keyOperations) {
3381
+ throw new import_node_opcua_crypto8.PrivateKeyUnavailableError(
3382
+ "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"
3383
+ );
3384
+ }
3318
3385
  if (this.#privateKeyProvider) {
3319
3386
  return await this.#privateKeyProvider.getPrivateKey();
3320
3387
  }
@@ -3340,6 +3407,75 @@ var CertificateManager = class _CertificateManager extends import_node_events.Ev
3340
3407
  }
3341
3408
  /** In-flight first read of the on-disk key, so concurrent callers share one passphrase resolution. */
3342
3409
  #privateKeyPromise;
3410
+ /**
3411
+ * True when this manager's key is opaque — configured through
3412
+ * `keyOperations`, held by an HSM/KMS, never obtainable as material.
3413
+ * When true, {@link getPrivateKey} throws `PrivateKeyUnavailableError`
3414
+ * and {@link getKeyOperations} is the only way to use the key.
3415
+ */
3416
+ isPrivateKeyOpaque() {
3417
+ return !!this.#keyOperations;
3418
+ }
3419
+ /**
3420
+ * The key as an opaque {@link IKeyOperations} — the recommended way to
3421
+ * *use* the private key regardless of where it lives.
3422
+ *
3423
+ * Returns the configured `keyOperations` object when the key is opaque.
3424
+ * Otherwise returns a stable lazy wrap over {@link getPrivateKey}: its
3425
+ * methods resolve the key on first use (disk read, passphrase,
3426
+ * `privateKeyProvider` — all async), so the wrap offers no synchronous
3427
+ * fast path; callers that need one resolve the key themselves and build
3428
+ * a `LocalKeyOperations` over it. The wrap follows key rotation: a
3429
+ * `privateKeyProvider` that starts returning a different key gets a
3430
+ * fresh underlying `LocalKeyOperations`.
3431
+ */
3432
+ getKeyOperations() {
3433
+ if (this.#keyOperations) {
3434
+ return this.#keyOperations;
3435
+ }
3436
+ if (!this.#lazyLocalKeyOperations) {
3437
+ let cached;
3438
+ const resolve = async () => {
3439
+ const key = await this.getPrivateKey();
3440
+ if (!cached || cached.key !== key) {
3441
+ cached = { key, ops: (0, import_node_opcua_crypto8.keyOperationsFromPrivateKey)(key) };
3442
+ }
3443
+ return cached.ops;
3444
+ };
3445
+ this.#lazyLocalKeyOperations = {
3446
+ sign: async (data, params) => (await resolve()).sign(data, params),
3447
+ decryptBlock: async (block, params) => (await resolve()).decryptBlock(block, params),
3448
+ getKeyMetadata: async () => (await resolve()).getKeyMetadata(),
3449
+ getPublicKey: async () => (await resolve()).getPublicKey()
3450
+ };
3451
+ }
3452
+ return this.#lazyLocalKeyOperations;
3453
+ }
3454
+ /**
3455
+ * Fail closed at `initialize()` time, not on the first certificate
3456
+ * operation, if the key cannot actually be used: wrong/missing
3457
+ * passphrase on an encrypted key, a broken `privateKeyProvider`, or an
3458
+ * unreachable `keyOperations` provider (probed via `getKeyMetadata`).
3459
+ */
3460
+ async #probePrivateKey() {
3461
+ if (this.#keyOperations) {
3462
+ await this.#keyOperations.getKeyMetadata();
3463
+ return;
3464
+ }
3465
+ await this.getPrivateKey();
3466
+ }
3467
+ /**
3468
+ * The key to hand to a certificate-issuance primitive: the raw
3469
+ * {@link PrivateKey} for a local configuration, or a {@link CaSigner}
3470
+ * adapted from `keyOperations` when the key is opaque — which requires
3471
+ * the provider to implement `getPublicKey` (the adapter says so if not).
3472
+ */
3473
+ async #resolveSigningKey() {
3474
+ if (this.#keyOperations) {
3475
+ return (0, import_node_opcua_crypto8.caSignerFromKeyOperations)(this.#keyOperations);
3476
+ }
3477
+ return await this.getPrivateKey();
3478
+ }
3343
3479
  /**
3344
3480
  * Enable, disable, or rotate the passphrase protecting the on-disk
3345
3481
  * private key: decrypt with `oldPassphrase` (omit if the key is
@@ -3361,6 +3497,11 @@ var CertificateManager = class _CertificateManager extends import_node_events.Ev
3361
3497
  * disk file for this method to rewrite).
3362
3498
  */
3363
3499
  async reencryptPrivateKey(oldPassphrase, newPassphrase) {
3500
+ if (this.#keyOperations) {
3501
+ throw new import_node_opcua_crypto8.PrivateKeyUnavailableError(
3502
+ "reencryptPrivateKey: not supported when keyOperations is configured \u2014 there is no key material to rewrite"
3503
+ );
3504
+ }
3364
3505
  if (this.#privateKeyProvider) {
3365
3506
  throw new Error("reencryptPrivateKey: not supported when a privateKeyProvider is configured");
3366
3507
  }
@@ -3486,7 +3627,7 @@ var CertificateManager = class _CertificateManager extends import_node_events.Ev
3486
3627
  } catch (_err) {
3487
3628
  return "BadCertificateInvalid";
3488
3629
  }
3489
- const filename = import_node_path8.default.join(this.rejectedFolder, `${buildIdealCertificateName(leafCertificate)}.pem`);
3630
+ const filename = safeStoreJoin(this.rejectedFolder, `${buildIdealCertificateName(leafCertificate)}.pem`);
3490
3631
  debugLog("certificate has never been seen before and is now rejected (untrusted) ", filename);
3491
3632
  await fsWriteFile(filename, (0, import_node_opcua_crypto8.toPem)(chain, "CERTIFICATE"));
3492
3633
  this.#thumbs.rejected.set(fingerprint, { certificate: leafCertificate, filename });
@@ -3709,7 +3850,7 @@ var CertificateManager = class _CertificateManager extends import_node_events.Ev
3709
3850
  mkdirRecursiveSync(import_node_path8.default.join(pkiDir, "issuers"));
3710
3851
  mkdirRecursiveSync(import_node_path8.default.join(pkiDir, "issuers/certs"));
3711
3852
  mkdirRecursiveSync(import_node_path8.default.join(pkiDir, "issuers/crl"));
3712
- const ownsDiskKey = !this.#privateKeyProvider;
3853
+ const ownsDiskKey = !this.#privateKeyProvider && !this.#keyOperations;
3713
3854
  const needsKeyGeneration = ownsDiskKey && !import_node_fs11.default.existsSync(this.privateKey);
3714
3855
  const needsKeyEncryption = ownsDiskKey && !needsKeyGeneration && this.#privateKeyPassphrase !== void 0 && !isEncryptedPrivateKeyFile(this.privateKey);
3715
3856
  if (!import_node_fs11.default.existsSync(this.configFile) || needsKeyGeneration || needsKeyEncryption) {
@@ -3735,14 +3876,14 @@ var CertificateManager = class _CertificateManager extends import_node_events.Ev
3735
3876
  if (ownsDiskKey) {
3736
3877
  restrictPrivateFilePermissions(this.privateKey, 384);
3737
3878
  }
3738
- await this.getPrivateKey();
3879
+ await this.#probePrivateKey();
3739
3880
  await this.#readCertificates();
3740
3881
  });
3741
3882
  } else {
3742
3883
  if (ownsDiskKey) {
3743
3884
  restrictPrivateFilePermissions(this.privateKey, 384);
3744
3885
  }
3745
- await this.getPrivateKey();
3886
+ await this.#probePrivateKey();
3746
3887
  await this.#readCertificates();
3747
3888
  }
3748
3889
  }
@@ -3827,7 +3968,7 @@ var CertificateManager = class _CertificateManager extends import_node_events.Ev
3827
3968
  if (typeof params.applicationUri !== "string") {
3828
3969
  throw new Error("createSelfSignedCertificate: expecting applicationUri to be a string");
3829
3970
  }
3830
- if (!this.#privateKeyProvider && !import_node_fs11.default.existsSync(this.privateKey)) {
3971
+ if (!this.#privateKeyProvider && !this.#keyOperations && !import_node_fs11.default.existsSync(this.privateKey)) {
3831
3972
  throw new Error(`Cannot find private key ${this.privateKey}`);
3832
3973
  }
3833
3974
  let certificateFilename = import_node_path8.default.join(this.rootDir, "own/certs/self_signed_certificate.pem");
@@ -3836,7 +3977,7 @@ var CertificateManager = class _CertificateManager extends import_node_events.Ev
3836
3977
  ...params,
3837
3978
  rootDir: this.rootDir,
3838
3979
  configFile: this.configFile,
3839
- privateKey: await this.getPrivateKey(),
3980
+ privateKey: await this.#resolveSigningKey(),
3840
3981
  subject: params.subject || "CN=FIXME"
3841
3982
  };
3842
3983
  await this.withLock2(async () => {
@@ -3864,7 +4005,7 @@ var CertificateManager = class _CertificateManager extends import_node_events.Ev
3864
4005
  ...params,
3865
4006
  rootDir: import_node_path8.default.resolve(this.rootDir),
3866
4007
  configFile: import_node_path8.default.resolve(this.configFile),
3867
- privateKey: await this.getPrivateKey()
4008
+ privateKey: await this.#resolveSigningKey()
3868
4009
  };
3869
4010
  return await this.withLock2(async () => {
3870
4011
  const now = /* @__PURE__ */ new Date();
@@ -3894,7 +4035,7 @@ var CertificateManager = class _CertificateManager extends import_node_events.Ev
3894
4035
  if (this.#thumbs.issuers.certs.has(fingerprint)) {
3895
4036
  return "Good" /* Good */;
3896
4037
  }
3897
- const filename = import_node_path8.default.join(this.issuersCertFolder, `issuer_${buildIdealCertificateName(certificate)}.pem`);
4038
+ const filename = safeStoreJoin(this.issuersCertFolder, `issuer_${buildIdealCertificateName(certificate)}.pem`);
3898
4039
  await import_node_fs11.default.promises.writeFile(filename, pemCertificate, "ascii");
3899
4040
  this.#thumbs.issuers.certs.set(fingerprint, { certificate, filename });
3900
4041
  if (addInTrustList) {
@@ -4353,7 +4494,7 @@ var CertificateManager = class _CertificateManager extends import_node_events.Ev
4353
4494
  let status = await this.#checkRejectedOrTrusted(certificate);
4354
4495
  if (status === "unknown") {
4355
4496
  const pem = (0, import_node_opcua_crypto8.toPem)(chain, "CERTIFICATE");
4356
- const filename = import_node_path8.default.join(this.rejectedFolder, `${buildIdealCertificateName(certificate)}.pem`);
4497
+ const filename = safeStoreJoin(this.rejectedFolder, `${buildIdealCertificateName(certificate)}.pem`);
4357
4498
  await import_node_fs11.default.promises.writeFile(filename, pem);
4358
4499
  this.#thumbs.rejected.set(fingerprint, { certificate, filename });
4359
4500
  status = "rejected";
@@ -4370,7 +4511,7 @@ var CertificateManager = class _CertificateManager extends import_node_events.Ev
4370
4511
  throw new Error(`#moveCertificate: certificate ${fingerprint.substring(0, 10)} not found in ${status} index`);
4371
4512
  }
4372
4513
  const destFolder = newStatus === "trusted" ? this.trustedFolder : this.rejectedFolder;
4373
- const certificateDest = import_node_path8.default.join(destFolder, import_node_path8.default.basename(srcEntry.filename));
4514
+ const certificateDest = safeStoreJoin(destFolder, import_node_path8.default.basename(srcEntry.filename));
4374
4515
  debugLog("#moveCertificate", fingerprint.substring(0, 10), "old name", srcEntry.filename);
4375
4516
  debugLog("#moveCertificate", fingerprint.substring(0, 10), "new name", certificateDest);
4376
4517
  await import_node_fs11.default.promises.rename(srcEntry.filename, certificateDest);
@@ -4840,6 +4981,7 @@ async function dumpPFX(pfxFile, passphrase = "") {
4840
4981
  NativeCaBackend,
4841
4982
  OpenSslCaBackend,
4842
4983
  PrivateKeyPassphraseRequiredError,
4984
+ PrivateKeyUnavailableError,
4843
4985
  Subject,
4844
4986
  VerificationStatus,
4845
4987
  adjustApplicationUri,
@@ -4856,9 +4998,12 @@ async function dumpPFX(pfxFile, passphrase = "") {
4856
4998
  install_prerequisite,
4857
4999
  isIntermediateIssuer,
4858
5000
  isIssuer,
5001
+ isOpaqueSigner,
4859
5002
  isRootIssuer,
4860
5003
  makeFingerprint,
4861
5004
  quote,
4862
- resolvePrivateKeyPassphrase
5005
+ resolvePrivateKeyPassphrase,
5006
+ safeStoreJoin,
5007
+ toFilenameLabel
4863
5008
  });
4864
5009
  //# sourceMappingURL=index.js.map