node-opcua-pki 6.19.0 → 6.20.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.d.mts CHANGED
@@ -1,5 +1,5 @@
1
- import { SubjectOptions, CertificatePurpose, Subject, PrivateKey, Certificate, DER, CertificateRevocationList } from 'node-opcua-crypto';
2
- export { Subject, SubjectOptions } from 'node-opcua-crypto';
1
+ import { SubjectOptions, PrivateKey, CertificatePurpose, Subject, Certificate, DER, CertificateRevocationList } from 'node-opcua-crypto';
2
+ export { PrivateKeyPassphraseRequiredError, Subject, SubjectOptions } from 'node-opcua-crypto';
3
3
  import { EventEmitter } from 'node:events';
4
4
 
5
5
  /** RSA key size in bits. */
@@ -44,8 +44,12 @@ interface CreateCertificateSigningRequestWithConfigOptions extends CreateCertifi
44
44
  rootDir: Filename;
45
45
  /** Path to the OpenSSL configuration file. */
46
46
  configFile: Filename;
47
- /** Path to the private key file. */
48
- privateKey: Filename;
47
+ /**
48
+ * The private key: either a filesystem path to a PEM file (unencrypted;
49
+ * the historical behavior), or an already-resolved in-memory
50
+ * {@link PrivateKey} — see {@link CreateSelfSignCertificateWithConfigParam.privateKey}.
51
+ */
52
+ privateKey: Filename | PrivateKey;
49
53
  /** Intended purpose of the certificate. */
50
54
  purpose: CertificatePurpose;
51
55
  }
@@ -87,11 +91,36 @@ interface CreateSelfSignCertificateWithConfigParam extends CreateSelfSignCertifi
87
91
  rootDir: Filename;
88
92
  /** Path to the OpenSSL configuration file. */
89
93
  configFile: Filename;
90
- /** Path to the private key file. */
91
- privateKey: Filename;
94
+ /**
95
+ * The private key: either a filesystem path to a PEM file (unencrypted;
96
+ * the historical behavior), or an already-resolved in-memory
97
+ * {@link PrivateKey} — used when the key is passphrase-protected on
98
+ * disk, so the decrypted key material is never written back out in
99
+ * cleartext.
100
+ */
101
+ privateKey: Filename | PrivateKey;
92
102
  /** Intended purpose of the certificate. */
93
103
  purpose: CertificatePurpose;
94
104
  }
105
+ /**
106
+ * A passphrase, or a function resolving one — called lazily, and at most
107
+ * once per `CertificateManager` / `CertificateAuthority` instance (the
108
+ * result is cached in memory). Never logged.
109
+ */
110
+ type PrivateKeyPassphrase = string | (() => Promise<string>);
111
+ /**
112
+ * Sources a {@link PrivateKey} from somewhere other than the local
113
+ * filesystem (an HSM, a KMS, ...). When configured on `CertificateManager`,
114
+ * it overrides disk entirely — the on-disk `own/private/private_key.pem` is
115
+ * neither generated nor read. `CertificateAuthority` does not support a
116
+ * provider (its signing paths are openssl reading a key file); it does
117
+ * support `privateKeyPassphrase`.
118
+ */
119
+ interface PrivateKeyProvider {
120
+ getPrivateKey(): Promise<PrivateKey>;
121
+ }
122
+ /** Resolve a {@link PrivateKeyPassphrase}, if configured. */
123
+ declare function resolvePrivateKeyPassphrase(passphrase: PrivateKeyPassphrase | undefined): Promise<string | undefined>;
95
124
  /**
96
125
  * General-purpose parameters passed to CA operations such as
97
126
  * {@link CertificateAuthority.signCertificateRequest} and
@@ -114,6 +143,18 @@ interface Params extends ProcessAltNamesParam, StartDateEndDateParam {
114
143
  declare function adjustDate(params: StartDateEndDateParam): void;
115
144
  declare function adjustApplicationUri(params: Params): void;
116
145
 
146
+ /** openssl argv entries plus the env they need; merge `env` into `ExecuteOptions.env`. */
147
+ interface OpensslPassArg {
148
+ args: string[];
149
+ env: NodeJS.ProcessEnv;
150
+ }
151
+
152
+ /**
153
+ *
154
+ * return path to the openssl executable
155
+ */
156
+ declare function install_prerequisite(): Promise<string>;
157
+
117
158
  /**
118
159
  * Result of {@link CertificateAuthority.initializeCSR}.
119
160
  *
@@ -194,6 +235,24 @@ interface CertificateAuthorityOptions {
194
235
  * repair). Leave undefined to omit (US-202).
195
236
  */
196
237
  caIssuersUrl?: string;
238
+ /**
239
+ * Encrypt the CA private key (`private/cakey.pem`) at rest with this
240
+ * passphrase (opt-in, default off). When set:
241
+ * - a freshly generated CA key is written as encrypted PKCS#8;
242
+ * - an existing *plaintext* CA key is encrypted in place by
243
+ * {@link CertificateAuthority.initialize} / `initializeCSR()`;
244
+ * - every `openssl` invocation that loads the CA key (CSR, self-sign,
245
+ * sign, revoke, CRL generation) receives it via `-passin env:`, never
246
+ * in argv; a missing or wrong passphrase fails `initialize()` closed.
247
+ *
248
+ * A function is called at most once per instance; the resolved
249
+ * passphrase is kept in memory for the instance's lifetime because the
250
+ * CA hands it to each openssl child. Never logged.
251
+ *
252
+ * A subordinate CA (`issuerCA` set) uses the *issuer's* passphrase for
253
+ * the issuer's key: construct the parent with its own option.
254
+ */
255
+ privateKeyPassphrase?: PrivateKeyPassphrase;
197
256
  }
198
257
  /**
199
258
  * An OpenSSL-based Certificate Authority (CA) that can create,
@@ -337,6 +396,7 @@ interface GenerateKeyPairAndSignPFXOptions extends GenerateKeyPairAndSignOptions
337
396
  passphrase?: string;
338
397
  }
339
398
  declare class CertificateAuthority {
399
+ #private;
340
400
  /** RSA key size used when generating the CA private key. */
341
401
  readonly keySize: KeySize;
342
402
  /** Root filesystem path of the CA directory structure. */
@@ -404,6 +464,33 @@ declare class CertificateAuthority {
404
464
  get rootDir(): string;
405
465
  /** Path to the OpenSSL configuration file (`conf/caconfig.cnf`). */
406
466
  get configFile(): string;
467
+ /** Path to the CA private key (`private/cakey.pem`); may be passphrase-encrypted, see {@link getPrivateKey}. */
468
+ get privateKey(): string;
469
+ /**
470
+ * The CA private key, decrypted with the configured `privateKeyPassphrase`
471
+ * if it is encrypted. Fails closed (`PrivateKeyPassphraseRequiredError`)
472
+ * on an encrypted key with no or the wrong passphrase.
473
+ */
474
+ getPrivateKey(): Promise<PrivateKey>;
475
+ /**
476
+ * Enable, disable, or rotate the passphrase protecting `private/cakey.pem`
477
+ * (temp file + atomic rename, temp file removed on failure). Only
478
+ * rewrites the file: construct a new `CertificateAuthority` with the new
479
+ * passphrase to continue using it.
480
+ */
481
+ reencryptPrivateKey(oldPassphrase?: PrivateKeyPassphrase, newPassphrase?: PrivateKeyPassphrase): Promise<void>;
482
+ /** @internal resolve the configured passphrase, at most once per instance */
483
+ _privateKeyPassphrase(): Promise<string | undefined>;
484
+ /** @internal `-passin env:` argv + env for every openssl call that loads this CA's key (always emitted, empty when none) */
485
+ _opensslPassin(): Promise<OpensslPassArg>;
486
+ /**
487
+ * @internal On an existing key: encrypt it in place if a passphrase is
488
+ * configured and it is still plaintext (secure by default: the option
489
+ * means "protect this key", not "ignore me"), then read it back so a
490
+ * wrong or missing passphrase fails initialize() closed rather than the
491
+ * first signing operation.
492
+ */
493
+ _ensurePrivateKeyProtection(): Promise<void>;
407
494
  /** Path to the CA certificate in PEM format (`public/cacert.pem`). */
408
495
  get caCertificate(): string;
409
496
  /**
@@ -836,6 +923,37 @@ interface CertificateManagerOptions {
836
923
  * @defaultValue false
837
924
  */
838
925
  disableFileWatchers?: boolean;
926
+ /**
927
+ * Encrypt the private key at rest with this passphrase (opt-in,
928
+ * default off). When set:
929
+ * - a freshly generated key is written as encrypted PKCS#8;
930
+ * - an existing *plaintext* key is re-encrypted in place by
931
+ * {@link CertificateManager.initialize} (atomic rename, same as
932
+ * {@link CertificateManager.reencryptPrivateKey}), so enabling the
933
+ * option on an existing install never leaves the key in cleartext;
934
+ * - an existing encrypted key requires the same passphrase — a
935
+ * mismatch, or an encrypted key with no passphrase configured, fails
936
+ * `initialize()` closed with `PrivateKeyPassphraseRequiredError`.
937
+ *
938
+ * A function is called at most once per `CertificateManager` instance
939
+ * (the decrypted key is cached in memory for the instance's lifetime,
940
+ * see {@link CertificateManager.getPrivateKey}). Never logged.
941
+ *
942
+ * Existing consumers that read `own/private/private_key.pem` directly
943
+ * (e.g. node-opcua's `OPCUAServer`, or `createPFX` without its
944
+ * `privateKeyPassphrase` option) still expect a plaintext key — do not
945
+ * enable this unless every reader of that file goes through
946
+ * {@link CertificateManager.getPrivateKey} or is given the passphrase.
947
+ */
948
+ privateKeyPassphrase?: PrivateKeyPassphrase;
949
+ /**
950
+ * Source the private key from somewhere other than
951
+ * `own/private/private_key.pem` (an HSM, a KMS, ...). When set, it
952
+ * overrides disk entirely for every operation that needs the private
953
+ * key — the on-disk file is not read, and `privateKeyPassphrase` is
954
+ * ignored.
955
+ */
956
+ privateKeyProvider?: PrivateKeyProvider;
839
957
  }
840
958
  /**
841
959
  * Parameters for {@link createSelfSignedCertificate}.
@@ -1104,8 +1222,53 @@ declare class CertificateManager extends EventEmitter {
1104
1222
  get configFile(): string;
1105
1223
  /** Root directory of the PKI store. */
1106
1224
  get rootDir(): string;
1107
- /** Path to the private key file (`own/private/private_key.pem`). */
1225
+ /**
1226
+ * Path to the private key file (`own/private/private_key.pem`).
1227
+ *
1228
+ * Kept for backward compatibility with code that reads the key
1229
+ * directly from disk. When a passphrase or a `privateKeyProvider` is
1230
+ * configured, prefer {@link getPrivateKey} instead — this getter still
1231
+ * returns the on-disk path even if a provider is configured (there may
1232
+ * be no meaningful file in that case).
1233
+ */
1108
1234
  get privateKey(): string;
1235
+ /**
1236
+ * Resolve the private key: from `privateKeyProvider` if configured,
1237
+ * otherwise from disk (decrypting with `privateKeyPassphrase` if the
1238
+ * key is encrypted). Fails closed — throws
1239
+ * `PrivateKeyPassphraseRequiredError` — if the on-disk key is encrypted
1240
+ * and no passphrase is configured, or if the wrong passphrase is
1241
+ * configured.
1242
+ *
1243
+ * The on-disk key is read and decrypted once and then cached for the
1244
+ * lifetime of this instance, so a `privateKeyPassphrase` function is
1245
+ * called at most once (concurrent first calls share the same read). A
1246
+ * failed read is not cached, so a caller can fix the passphrase and
1247
+ * retry. A `privateKeyProvider` is consulted on every call: it is the
1248
+ * authority on what the current key is.
1249
+ */
1250
+ getPrivateKey(): Promise<PrivateKey>;
1251
+ /**
1252
+ * Enable, disable, or rotate the passphrase protecting the on-disk
1253
+ * private key: decrypt with `oldPassphrase` (omit if the key is
1254
+ * currently unencrypted), then write back encrypted with
1255
+ * `newPassphrase` (omit to leave it unencrypted). The write goes to a
1256
+ * temporary file in the same directory and is atomically renamed into
1257
+ * place, so a crash mid-rotation cannot leave a partially-written key;
1258
+ * the temporary file is removed if anything fails, so a rotation *to*
1259
+ * plaintext can never leave a stray cleartext copy behind. Runs under
1260
+ * the same lock as `initialize()`.
1261
+ *
1262
+ * This only rewrites the on-disk file — it does not update this
1263
+ * instance's own `privateKeyPassphrase` (set at construction), and it
1264
+ * drops this instance's cached key so that disk stays the source of
1265
+ * truth. Construct a new `CertificateManager` with the new passphrase to
1266
+ * continue using it afterward.
1267
+ *
1268
+ * Not supported when a `privateKeyProvider` is configured (there is no
1269
+ * disk file for this method to rewrite).
1270
+ */
1271
+ reencryptPrivateKey(oldPassphrase?: PrivateKeyPassphrase, newPassphrase?: PrivateKeyPassphrase): Promise<void>;
1109
1272
  /** Path to the OpenSSL random seed file. */
1110
1273
  get randomFile(): string;
1111
1274
  /**
@@ -1385,8 +1548,15 @@ declare class CertificateManager extends EventEmitter {
1385
1548
  interface CreatePFXOptions {
1386
1549
  /** Path to the certificate file (PEM or DER). */
1387
1550
  certificateFile: Filename;
1388
- /** Path to the private key file (PEM). */
1551
+ /** Path to the private key file (PEM, plaintext or encrypted PKCS#8). */
1389
1552
  privateKeyFile: Filename;
1553
+ /**
1554
+ * Passphrase that decrypts `privateKeyFile` if it is an encrypted
1555
+ * PKCS#8 key (e.g. a `CertificateManager` created with
1556
+ * `privateKeyPassphrase`). Omit for a plaintext key. This is distinct
1557
+ * from `passphrase`, which protects the *output* PFX bundle.
1558
+ */
1559
+ privateKeyPassphrase?: string;
1390
1560
  /** Output path for the generated PFX file. */
1391
1561
  outputFile: Filename;
1392
1562
  /**
@@ -1435,8 +1605,14 @@ interface ExtractPFXResult {
1435
1605
  * -in <cert> -inkey <key>
1436
1606
  * [-certfile <ca>]
1437
1607
  * -out <pfx>
1438
- * -passout pass:<passphrase>
1608
+ * -passin env:NODE_OPCUA_PKI_OPENSSL_PASSIN
1609
+ * -passout env:NODE_OPCUA_PKI_OPENSSL_PASSOUT
1439
1610
  * ```
1611
+ * Both passphrases are passed via per-invocation environment variables,
1612
+ * never placed in argv — see {@link ExecuteOptions.env}.
1613
+ * `-passin` is always present (empty when the key is plaintext) so that an
1614
+ * encrypted key without a `privateKeyPassphrase` fails fast rather than
1615
+ * leaving openssl waiting on a terminal prompt that never comes.
1440
1616
  *
1441
1617
  * @param options — see {@link CreatePFXOptions}
1442
1618
  */
@@ -1447,8 +1623,10 @@ declare function createPFX(options: CreatePFXOptions): Promise<void>;
1447
1623
  * Wraps:
1448
1624
  * ```
1449
1625
  * openssl pkcs12 -in <pfx> -clcerts -nokeys
1450
- * -passin pass:<passphrase>
1626
+ * -passin env:NODE_OPCUA_PKI_OPENSSL_PASSIN
1451
1627
  * ```
1628
+ * The passphrase is passed via a per-invocation environment variable, never
1629
+ * placed in argv — see {@link ExecuteOptions.env}.
1452
1630
  *
1453
1631
  * @returns the certificate in PEM format.
1454
1632
  */
@@ -1459,8 +1637,10 @@ declare function extractCertificateFromPFX(options: ExtractPFXOptions): Promise<
1459
1637
  * Wraps:
1460
1638
  * ```
1461
1639
  * openssl pkcs12 -in <pfx> -nocerts -nodes
1462
- * -passin pass:<passphrase>
1640
+ * -passin env:NODE_OPCUA_PKI_OPENSSL_PASSIN
1463
1641
  * ```
1642
+ * The passphrase is passed via a per-invocation environment variable, never
1643
+ * placed in argv — see {@link ExecuteOptions.env}.
1464
1644
  *
1465
1645
  * @returns the private key in PEM format.
1466
1646
  */
@@ -1471,8 +1651,10 @@ declare function extractPrivateKeyFromPFX(options: ExtractPFXOptions): Promise<s
1471
1651
  * Wraps:
1472
1652
  * ```
1473
1653
  * openssl pkcs12 -in <pfx> -cacerts -nokeys -nodes
1474
- * -passin pass:<passphrase>
1654
+ * -passin env:NODE_OPCUA_PKI_OPENSSL_PASSIN
1475
1655
  * ```
1656
+ * The passphrase is passed via a per-invocation environment variable, never
1657
+ * placed in argv — see {@link ExecuteOptions.env}.
1476
1658
  *
1477
1659
  * @returns the CA certificates in PEM format
1478
1660
  * (empty string if none are present).
@@ -1492,8 +1674,10 @@ declare function extractAllFromPFX(options: ExtractPFXOptions): Promise<ExtractP
1492
1674
  * Wraps:
1493
1675
  * ```
1494
1676
  * openssl pkcs12 -in <pfx> -out <pem> -nodes
1495
- * -passin pass:<passphrase>
1677
+ * -passin env:NODE_OPCUA_PKI_OPENSSL_PASSIN
1496
1678
  * ```
1679
+ * The passphrase is passed via a per-invocation environment variable, never
1680
+ * placed in argv — see {@link ExecuteOptions.env}.
1497
1681
  */
1498
1682
  declare function convertPFXtoPEM(pfxFile: Filename, pemFile: Filename, passphrase?: string): Promise<void>;
1499
1683
  /**
@@ -1502,17 +1686,13 @@ declare function convertPFXtoPEM(pfxFile: Filename, pemFile: Filename, passphras
1502
1686
  * Wraps:
1503
1687
  * ```
1504
1688
  * openssl pkcs12 -in <pfx> -info -noout
1505
- * -passin pass:<passphrase>
1689
+ * -passin env:NODE_OPCUA_PKI_OPENSSL_PASSIN
1506
1690
  * ```
1691
+ * The passphrase is passed via a per-invocation environment variable, never
1692
+ * placed in argv — see {@link ExecuteOptions.env}.
1507
1693
  *
1508
1694
  * @returns the human-readable dump as a string.
1509
1695
  */
1510
1696
  declare function dumpPFX(pfxFile: Filename, passphrase?: string): Promise<string>;
1511
1697
 
1512
- /**
1513
- *
1514
- * return path to the openssl executable
1515
- */
1516
- declare function install_prerequisite(): Promise<string>;
1517
-
1518
- export { type AddCertificateValidationOptions, CertificateAuthority, type CertificateAuthorityOptions, CertificateManager, type CertificateManagerEvents, type CertificateManagerOptions, CertificateManagerState, type CertificateStatus, type CertificateStore, type ChainCompletionResult, ChainCompletionStatus, type CreateCertificateSigningRequestOptions, type CreateCertificateSigningRequestWithConfigOptions, type CreatePFXOptions, type CreateSelfSignCertificateParam, type CreateSelfSignCertificateParam1, type CreateSelfSignCertificateWithConfigParam, type CrlStore, type ExtractPFXOptions, type ExtractPFXResult, type Filename, type GenerateKeyPairAndSignOptions, type GenerateKeyPairAndSignPFXOptions, type InitializeCSRResult, type InstallCACertificateResult, type KeyLength, type KeySize, type Params, type PkiBackendCapabilities, type ProcessAltNamesParam, type SignCertificateOptions, type StartDateEndDateParam, type Thumbprint, VerificationStatus, type VerifyCertificateOptions, adjustApplicationUri, adjustDate, coerceCertificateChain, convertPFXtoPEM, createPFX, dumpPFX, extractAllFromPFX, extractCACertificatesFromPFX, extractCertificateFromPFX, extractPrivateKeyFromPFX, findIssuerCertificateInChain, install_prerequisite, isIntermediateIssuer, isIssuer, isRootIssuer, makeFingerprint, quote };
1698
+ export { type AddCertificateValidationOptions, CertificateAuthority, type CertificateAuthorityOptions, CertificateManager, type CertificateManagerEvents, type CertificateManagerOptions, CertificateManagerState, type CertificateStatus, type CertificateStore, type ChainCompletionResult, ChainCompletionStatus, type CreateCertificateSigningRequestOptions, type CreateCertificateSigningRequestWithConfigOptions, type CreatePFXOptions, type CreateSelfSignCertificateParam, type CreateSelfSignCertificateParam1, type CreateSelfSignCertificateWithConfigParam, type CrlStore, type ExtractPFXOptions, type ExtractPFXResult, type Filename, type GenerateKeyPairAndSignOptions, type GenerateKeyPairAndSignPFXOptions, type InitializeCSRResult, type InstallCACertificateResult, type KeyLength, type KeySize, type Params, type PkiBackendCapabilities, type PrivateKeyPassphrase, type PrivateKeyProvider, type ProcessAltNamesParam, type SignCertificateOptions, type StartDateEndDateParam, type Thumbprint, VerificationStatus, type VerifyCertificateOptions, adjustApplicationUri, adjustDate, coerceCertificateChain, convertPFXtoPEM, createPFX, dumpPFX, extractAllFromPFX, extractCACertificatesFromPFX, extractCertificateFromPFX, extractPrivateKeyFromPFX, findIssuerCertificateInChain, install_prerequisite, isIntermediateIssuer, isIssuer, isRootIssuer, makeFingerprint, quote, resolvePrivateKeyPassphrase };