UNPKG

node-opcua-pki

Version:
1,284 lines (1,280 loc) 59.9 kB
import { SubjectOptions, CertificatePurpose, Subject, PrivateKey, Certificate, DER, CertificateRevocationList } from 'node-opcua-crypto'; export { Subject, SubjectOptions } from 'node-opcua-crypto'; import { EventEmitter } from 'node:events'; /** RSA key size in bits. */ type KeySize = 1024 | 2048 | 3072 | 4096; /** Hex-encoded SHA-1 certificate thumbprint. */ type Thumbprint = string; /** A filesystem path to a file. */ type Filename = string; /** Status of a certificate in the trust store. */ type CertificateStatus = "unknown" | "trusted" | "rejected"; /** * @deprecated Use {@link KeySize} instead. */ type KeyLength = 1024 | 2048 | 3072 | 4096; declare function quote(str?: string): string; /** * Subject Alternative Name (SAN) parameters for certificate * generation. */ interface ProcessAltNamesParam { /** DNS host names to include in the SAN extension. */ dns?: string[]; /** IP addresses to include in the SAN extension. */ ip?: string[]; /** OPC UA application URI for the SAN extension. */ applicationUri?: string; } /** * Options for creating a Certificate Signing Request (CSR). */ interface CreateCertificateSigningRequestOptions extends ProcessAltNamesParam { /** X.500 subject for the certificate. */ subject?: SubjectOptions | string; } /** * Extended CSR options that include filesystem paths and * certificate purpose — used internally by the OpenSSL toolbox. */ interface CreateCertificateSigningRequestWithConfigOptions extends CreateCertificateSigningRequestOptions { /** Root directory of the PKI store. */ rootDir: Filename; /** Path to the OpenSSL configuration file. */ configFile: Filename; /** Path to the private key file. */ privateKey: Filename; /** Intended purpose of the certificate. */ purpose: CertificatePurpose; } /** * Validity period parameters for certificate generation. */ interface StartDateEndDateParam { /** Certificate "Not Before" date. Defaults to now. */ startDate?: Date; /** Certificate "Not After" date (computed from validity). */ endDate?: Date; /** Number of days the certificate is valid. @defaultValue 365 */ validity?: number; /** * Certificate validity in milliseconds. * * When provided, takes precedence over {@link validity} and enables * sub-day validity (X.509 supports second precision per RFC 5280 * §4.1.2.5; OpenSSL is invoked with `-startdate`/`-enddate` already). * * Typical use is short-lived certificates for demos or for renewal * cycle testing. Existing day-based callers are unaffected. */ validityMs?: number; } /** * Parameters for creating a self-signed certificate. */ interface CreateSelfSignCertificateParam extends ProcessAltNamesParam, StartDateEndDateParam { /** X.500 subject for the certificate. */ subject?: SubjectOptions | string; } /** * Extended self-signed certificate options that include * filesystem paths and purpose — used internally. */ interface CreateSelfSignCertificateWithConfigParam extends CreateSelfSignCertificateParam { /** Root directory of the PKI store. */ rootDir: Filename; /** Path to the OpenSSL configuration file. */ configFile: Filename; /** Path to the private key file. */ privateKey: Filename; /** Intended purpose of the certificate. */ purpose: CertificatePurpose; } /** * General-purpose parameters passed to CA operations such as * {@link CertificateAuthority.signCertificateRequest} and * {@link CertificateAuthority.revokeCertificate}. */ interface Params extends ProcessAltNamesParam, StartDateEndDateParam { /** X.500 subject for the certificate. */ subject?: SubjectOptions | string; /** Path to the private key file. */ privateKey?: string; /** Path to the OpenSSL configuration file. */ configFile?: string; /** Root directory of the PKI store. */ rootDir?: string; /** Output filename for the generated certificate. */ outputFile?: string; /** CRL revocation reason (e.g. `"keyCompromise"`). */ reason?: string; } declare function adjustDate(params: StartDateEndDateParam): void; declare function adjustApplicationUri(params: Params): void; /** * Result of {@link CertificateAuthority.initializeCSR}. * * - `"ready"` — the CA certificate already exists and is valid. * - `"pending"` — key + CSR exist but no cert; waiting for external signing. * - `"created"` — a fresh key + CSR were just generated. * - `"expired"` — the CA certificate has expired (or will expire within * the configured threshold). A new CSR has been generated for renewal * while preserving the existing private key. */ type InitializeCSRResult = { status: "ready"; } | { status: "pending"; csrPath: string; } | { status: "created"; csrPath: string; } | { status: "expired"; csrPath: string; expiryDate: Date; }; /** * Result of {@link CertificateAuthority.installCACertificate}. * * - `"success"` — the certificate was installed and CRL generated. * - `"error"` — the certificate was rejected (see `reason`). */ type InstallCACertificateResult = { status: "success"; } | { status: "error"; reason: string; message: string; }; /** * Options for creating a {@link CertificateAuthority}. */ interface CertificateAuthorityOptions { /** RSA key size for the CA private key. */ keySize: KeySize; /** Filesystem path where the CA directory structure is stored. */ location: string; /** * X.500 subject for the CA certificate. * Accepts a slash-delimited string (e.g. `"/CN=My CA/O=Acme"`) or * a structured {@link SubjectOptions} object. * * @defaultValue {@link defaultSubject} */ subject?: string | SubjectOptions; /** * Parent CA that will sign this CA's certificate. * If omitted, the CA is self-signed (root CA). * The parent CA must be initialized before this CA. */ issuerCA?: CertificateAuthority; /** * Public URL (http/https) where the CRL produced by this CA is * reachable. When set, every issued certificate carries an * X.509v3 `crlDistributionPoints` extension pointing at this URL. * * Leave undefined to omit the extension entirely (opt-in — see * US-202). Validated synchronously at construction / setter call. */ crlDistributionUrl?: string; /** * Public URL of the OCSP responder. When set, every issued cert * carries an `authorityInfoAccess` extension with an `OCSP` leg * pointing at this URL. Leave undefined to omit (US-202). */ ocspResponderUrl?: string; /** * Public URL where the issuer's certificate can be fetched. * When set, the `authorityInfoAccess` extension on every issued * cert carries a `caIssuers` leg pointing at this URL (chain * repair). Leave undefined to omit (US-202). */ caIssuersUrl?: string; } /** * An OpenSSL-based Certificate Authority (CA) that can create, * sign, and revoke X.509 certificates. * * The CA maintains a standard OpenSSL directory layout under * {@link CertificateAuthority.rootDir | rootDir}: * * ``` * <location>/ * ├── conf/ OpenSSL configuration * ├── private/ CA private key (cakey.pem) * ├── public/ CA certificate (cacert.pem) * ├── certs/ Signed certificates * ├── crl/ Revocation lists * ├── serial Next serial number * ├── crlnumber Next CRL number * └── index.txt Certificate database * ``` * * @example * ```ts * const ca = new CertificateAuthority({ * keySize: 2048, * location: "/var/pki/CA" * }); * await ca.initialize(); * ``` */ /** * A record from the OpenSSL CA certificate database * (`index.txt`). */ interface IssuedCertificateRecord { /** Hex-encoded serial number (e.g. `"1000"`). */ serial: string; /** Certificate status. */ status: "valid" | "revoked" | "expired"; /** X.500 subject string (slash-delimited). */ subject: string; /** Certificate expiry date as ISO-8601 string. */ expiryDate: string; /** * Revocation date as ISO-8601 string. * Only present when `status === "revoked"`. */ revocationDate?: string; } /** * Options for {@link CertificateAuthority.signCertificateRequestFromDER}. * * All fields are optional. When provided, they override the * corresponding values from the CSR. */ interface SignCertificateOptions { /** Certificate validity in days (default: 365). */ validity?: number; /** * Certificate validity in milliseconds. * * When provided, takes precedence over {@link validity} and enables * sub-day validity (e.g. 10-minute certificates for renewal demos). */ validityMs?: number; /** Override the certificate start date. */ startDate?: Date; /** Override DNS SANs. */ dns?: string[]; /** Override IP SANs. */ ip?: string[]; /** Override the application URI SAN. */ applicationUri?: string; /** Override the X.500 subject. */ subject?: SubjectOptions | string; } /** * Capabilities advertised by a PKI backend (or by this * {@link CertificateAuthority}) so consumers can clamp requested * validity to the limits the backend can actually honor. * * Useful for the GDS Pull / Push management flows, where the CA may * be supplied by an external service (step-ca, EJBCA, …) with its * own minimum / maximum / granularity constraints. * * @see CertificateAuthority.getCapabilities */ interface PkiBackendCapabilities { /** Smallest validity this backend can issue, in milliseconds. */ minValidityMs: number; /** Largest validity this backend will issue, in milliseconds. */ maxValidityMs: number; /** * Validity is rounded up to the nearest multiple of this many * milliseconds. For `node-opcua-pki`'s OpenSSL-based CA this is * 1 000 ms (one second — the X.509 floor per RFC 5280 §4.1.2.5). */ validityGranularityMs: number; /** * Native unit the backend works in. Diagnostic only — callers * always pass `validityMs` (US-208 / US-210). */ nativeUnit: "second" | "minute" | "hour" | "day"; } /** * Options for {@link CertificateAuthority.generateKeyPairAndSignDER}. */ interface GenerateKeyPairAndSignOptions { /** OPC UA application URI (required). */ applicationUri: string; /** X.500 subject for the certificate (e.g. "CN=MyApp"). */ subject?: SubjectOptions | string; /** DNS host names for the SAN extension. */ dns?: string[]; /** IP addresses for the SAN extension. */ ip?: string[]; /** Certificate validity in days (default: 365). */ validity?: number; /** * Certificate validity in milliseconds. * * When provided, takes precedence over {@link validity} and enables * sub-day validity (e.g. 10-minute certificates for renewal demos). */ validityMs?: number; /** Certificate start date (default: now). */ startDate?: Date; /** RSA key size in bits (default: 2048). */ keySize?: KeySize; } /** * Options for {@link CertificateAuthority.generateKeyPairAndSignPFX}. * * Extends the DER options with an optional `passphrase` to protect * the PFX bundle. */ interface GenerateKeyPairAndSignPFXOptions extends GenerateKeyPairAndSignOptions { /** * Passphrase to protect the PFX file. * If omitted, the PFX is created without a password. */ passphrase?: string; } declare class CertificateAuthority { /** RSA key size used when generating the CA private key. */ readonly keySize: KeySize; /** Root filesystem path of the CA directory structure. */ readonly location: string; /** X.500 subject of the CA certificate. */ readonly subject: Subject; /** @internal Parent CA (undefined for root CAs). */ readonly _issuerCA?: CertificateAuthority; /** @internal Configured CDP / AIA URLs (US-202). */ private _crlDistributionUrl?; private _ocspResponderUrl?; private _caIssuersUrl?; constructor(options: CertificateAuthorityOptions); /** * Public URL where the CRL produced by this CA is reachable, or * `undefined` if no CDP extension should be emitted on issued certs. */ get crlDistributionUrl(): string | undefined; /** * Public URL of the OCSP responder, or `undefined` if no AIA OCSP * leg should be emitted on issued certs. */ get ocspResponderUrl(): string | undefined; /** * Public URL where the issuer's certificate can be fetched, or * `undefined` if no AIA caIssuers leg should be emitted. */ get caIssuersUrl(): string | undefined; /** * Configure the URL embedded as `crlDistributionPoints` in every * subsequently-issued certificate. Pass `undefined` to disable * the extension entirely. Validated synchronously — throws on * empty string, non-http(s) protocol, missing path. Warns (does * not throw) when the URL points at loopback. * * @see US-202 */ setCrlDistributionUrl(url: string | undefined): void; /** * Configure the OCSP responder URL embedded as the `OCSP` leg of * the `authorityInfoAccess` extension on every subsequently-issued * certificate. Pass `undefined` to disable. * * @see US-202 */ setOcspResponderUrl(url: string | undefined): void; /** * Configure the caIssuers URL embedded as the `caIssuers` leg of * the `authorityInfoAccess` extension on every subsequently-issued * certificate. Pass `undefined` to disable. * * @see US-202 */ setCaIssuersUrl(url: string | undefined): void; /** * @internal * Populate the OpenSSL config substitution env vars (`CDP_URL` and * `AIA_VALUE`) from the configured URLs, or unset them so the * matching `{{#KEY}}...{{/KEY}}` blocks in the templates are * stripped. MUST be called before every `generateStaticConfig` * invocation that signs a certificate. */ _wireRevocationEnvVars(): void; /** Absolute path to the CA root directory (alias for {@link location}). */ get rootDir(): string; /** Path to the OpenSSL configuration file (`conf/caconfig.cnf`). */ get configFile(): string; /** Path to the CA certificate in PEM format (`public/cacert.pem`). */ get caCertificate(): string; /** * Path to the issuer certificate chain (`public/issuer_chain.pem`). * * This file is created by {@link installCACertificate} when the * provided cert file contains additional issuer certificates * (e.g. intermediate + root). It is appended to signed certs * by {@link constructCertificateChain} to produce a full chain * per OPC UA Part 6 §6.2.6. */ get issuerCertificateChain(): string; /** * Path to the current Certificate Revocation List in DER format. * (`crl/revocation_list.der`) */ get revocationListDER(): string; /** * Path to the current Certificate Revocation List in PEM format. * (`crl/revocation_list.crl`) */ get revocationList(): string; /** * Path to the concatenated CA certificate + CRL file. * Used by OpenSSL for CRL-based verification. */ get caCertificateWithCrl(): string; /** * Return the CA certificate as a DER-encoded buffer. * * @throws if the CA certificate file does not exist * (call {@link initialize} first). */ getCACertificateDER(): Buffer; /** * Return the CA certificate as a PEM-encoded string. * * @throws if the CA certificate file does not exist * (call {@link initialize} first). */ getCACertificatePEM(): string; /** * Return the current Certificate Revocation List as a * DER-encoded buffer. * * Returns an empty buffer if no CRL has been generated yet. */ getCRLDER(): Buffer; /** * Return the current Certificate Revocation List as a * PEM-encoded string. * * Returns an empty string if no CRL has been generated yet. */ getCRLPEM(): string; /** * Return a list of all issued certificates recorded in the * OpenSSL `index.txt` database. * * Each entry includes the serial number, subject, status, * expiry date, and (for revoked certs) the revocation date. */ getIssuedCertificates(): IssuedCertificateRecord[]; /** * Return the total number of certificates recorded in * `index.txt`. */ getIssuedCertificateCount(): number; /** * Return the status of a certificate by its serial number. * * @param serial - hex-encoded serial number (e.g. `"1000"`) * @returns `"valid"`, `"revoked"`, `"expired"`, or * `undefined` if not found */ getCertificateStatus(serial: string): "valid" | "revoked" | "expired" | undefined; /** * Read a specific issued certificate by serial number and * return its content as a DER-encoded buffer. * * OpenSSL stores signed certificates in the `certs/` * directory using the naming convention `<SERIAL>.pem`. * * @param serial - hex-encoded serial number (e.g. `"1000"`) * @returns the DER buffer, or `undefined` if not found */ getCertificateBySerial(serial: string): Buffer | undefined; /** * Path to the OpenSSL certificate database file. */ get indexFile(): string; /** * Parse the OpenSSL `index.txt` certificate database. * * Each line has tab-separated fields: * ``` * status expiry [revocationDate] serial unknown subject * ``` * * - status: `V` (valid), `R` (revoked), `E` (expired) * - expiry: `YYMMDDHHmmssZ` * - revocationDate: present only for revoked certs * - serial: hex string * - unknown: always `"unknown"` * - subject: X.500 slash-delimited string */ private _parseIndexTxt; /** * Sign a DER-encoded Certificate Signing Request and return * the signed certificate as a DER buffer. * * This method handles temp-file creation and cleanup * internally so that callers can work with in-memory * buffers only. * * The CA can override fields from the CSR by passing * `options.dns`, `options.ip`, `options.applicationUri`, * `options.startDate`, or `options.subject`. * * @param csrDer - the CSR as a DER-encoded buffer * @param options - signing options and CA overrides * @returns the signed certificate as a DER-encoded buffer */ signCertificateRequestFromDER(csrDer: Buffer, options?: SignCertificateOptions): Promise<Buffer>; /** * Advertise the validity limits this CA can honor. * * Consumers (notably the GDS server in [`cert_auth.ts`](https://github.com/sterfive/node-opcua-gds)) * clamp a requested validity against these bounds before calling * {@link signCertificateRequestFromDER}, so a misconfigured * `defaultCertValidity` cannot ask the CA for something it cannot * produce. * * Defaults match the OpenSSL-backed implementation: * - `minValidityMs = 60_000` (1 minute) — practical floor; the * X.509 spec floor is 1 second but very short certs are rarely * useful and pathological for any real deployment. * - `maxValidityMs = 10 * 365 * 86_400_000` (≈ 10 years) — long * enough for root CAs. * - `validityGranularityMs = 1_000` (1 second) — RFC 5280 §4.1.2.5 * floor on `notBefore` / `notAfter`. * - `nativeUnit = "second"` — what `x509Date()` actually encodes. * * @see US-208 — the consumer-side capability story. */ getCapabilities(): PkiBackendCapabilities; /** * Generate a new RSA key pair, create an internal CSR, sign it * with this CA, and return both the certificate and private key * as DER-encoded buffers. * * The private key is **never stored** by the CA — it exists only * in a temporary directory that is cleaned up after the operation. * * This is used by `StartNewKeyPairRequest` (OPC UA Part 12) for * constrained devices that cannot generate their own keys. * * @param options - key generation and certificate parameters * @returns `{ certificateDer, privateKey }` — certificate as DER, * private key as a branded `PrivateKey` buffer */ generateKeyPairAndSignDER(options: GenerateKeyPairAndSignOptions): Promise<{ certificateDer: Buffer; privateKey: PrivateKey; }>; /** * Generate a new RSA key pair, create an internal CSR, sign it * with this CA, and return the result as a PKCS#12 (PFX) * buffer bundling the certificate, private key, and CA chain. * * The private key is **never stored** by the CA — it exists only * in a temporary directory that is cleaned up after the operation. * * @param options - key generation, certificate, and PFX options * @returns the PFX as a `Buffer` */ generateKeyPairAndSignPFX(options: GenerateKeyPairAndSignPFXOptions): Promise<Buffer>; /** * Revoke a DER-encoded certificate and regenerate the CRL. * * Extracts the serial number from the certificate, then * uses the stored cert file at `certs/<serial>.pem` for * revocation — avoiding temp-file PEM format mismatches. * * @param certDer - the certificate as a DER-encoded buffer * @param reason - CRL reason code * (default: `"keyCompromise"`) * @throws if the certificate's serial is not found in the CA */ revokeCertificateDER(certDer: Buffer, reason?: string): Promise<void>; /** * Initialize the CA directory structure, generate the CA * private key and self-signed certificate if they do not * already exist. */ initialize(): Promise<void>; /** * Initialize the CA directory structure and generate the * private key + CSR **without signing**. * * Use this when the CA certificate will be signed by an * external (third-party) root CA. After receiving the signed * certificate, call {@link installCACertificate} to complete * the setup. * * **Idempotent / restart-safe:** * - If the CA certificate exists and is valid → `{ status: "ready" }` * - If the CA certificate has expired → `{ status: "expired", csrPath, expiryDate }` * (a new CSR is generated, preserving the existing private key) * - If key + CSR exist but no cert (restart before install) → * `{ status: "pending", csrPath }` without regenerating * - Otherwise → generates key + CSR → `{ status: "created", csrPath }` * * @returns an {@link InitializeCSRResult} describing the CA state */ initializeCSR(): Promise<InitializeCSRResult>; /** * Check whether the CA certificate needs renewal and, if so, * generate a new CSR for re-signing by the external root CA. * * Use this while the CA is running to detect upcoming expiry * **before** it actually expires. The existing private key is * preserved so previously issued certs remain valid. * * @param thresholdDays - number of days before expiry at which * to trigger renewal (default: 30) * @returns an {@link InitializeCSRResult} — `"expired"` if * renewal is needed, `"ready"` if the cert is still valid */ renewCSR(thresholdDays?: number): Promise<InitializeCSRResult>; /** * Generate a CSR using the existing private key. * @internal */ private _generateCSR; /** * Install an externally-signed CA certificate and generate * the initial CRL. * * Call this after {@link initializeCSR} once the external * root CA has signed the CSR. * * **Safety checks:** * - Verifies that the certificate's public key matches the * CA private key before installing. * * @param signedCertFile - path to the PEM-encoded signed * CA certificate (issued by the external root CA) * @returns an {@link InstallCACertificateResult} with * `status: "success"` or `status: "error"` and a `reason` */ installCACertificate(signedCertFile: string): Promise<InstallCACertificateResult>; /** * Sign a CSR with CA extensions (`v3_ca`), producing a * subordinate CA certificate. * * Unlike {@link signCertificateRequest} which signs with * end-entity extensions (SANs, etc.), this method signs * with `basicConstraints = CA:TRUE` and `keyUsage = * keyCertSign, cRLSign`. * * @param certFile - output path for the signed CA cert (PEM) * @param csrFile - path to the subordinate CA's CSR * @param params - signing parameters */ signCACertificateRequest(certFile: string, csrFile: string, params: { validity?: number; }): Promise<void>; /** * Rebuild the combined CA certificate + CRL file. * * This concatenates the CA certificate with the current * revocation list so that OpenSSL can verify certificates * with CRL checking enabled. */ constructCACertificateWithCRL(): Promise<void>; /** * Append the CA certificate to a signed certificate file, * creating a PEM certificate chain. * * @param certificate - path to the certificate file to extend */ constructCertificateChain(certificate: Filename): Promise<void>; /** * Create a self-signed certificate using OpenSSL. * * @param certificateFile - output path for the signed certificate * @param privateKey - path to the private key file * @param params - certificate parameters (subject, validity, SANs) */ createSelfSignedCertificate(certificateFile: Filename, privateKey: Filename, params: Params): Promise<void>; /** * Revoke a certificate and regenerate the CRL. * * @param certificate - path to the certificate file to revoke * @param params - revocation parameters * @param params.reason - CRL reason code * (default `"keyCompromise"`) */ revokeCertificate(certificate: Filename, params: Params): Promise<void>; /** * Sign a Certificate Signing Request (CSR) with this CA. * * The signed certificate is written to `certificate`, and the * CA certificate chain plus CRL are appended to form a * complete certificate chain. * * @param certificate - output path for the signed certificate * @param certificateSigningRequestFilename - path to the CSR * @param params1 - signing parameters (validity, dates, SANs) * @returns the path to the signed certificate */ signCertificateRequest(certificate: Filename, certificateSigningRequestFilename: Filename, params1: Params): Promise<Filename>; /** * Verify a certificate against this CA. * * @param certificate - path to the certificate file to verify */ verifyCertificate(certificate: Filename): Promise<void>; } /** * Identifies which PKI sub-store a certificate event originated from. */ type CertificateStore = "trusted" | "rejected" | "issuersCerts"; /** * Identifies which PKI sub-store a CRL event originated from. */ type CrlStore = "crl" | "issuersCrl"; /** * Events emitted by {@link CertificateManager} when the * file-system watchers detect certificate or CRL changes. */ interface CertificateManagerEvents { /** A certificate file was added to a store. */ certificateAdded: (event: { store: CertificateStore; certificate: Certificate; fingerprint: string; filename: string; }) => void; /** A certificate file was removed from a store. */ certificateRemoved: (event: { store: CertificateStore; fingerprint: string; filename: string; }) => void; /** A certificate file was modified in a store. */ certificateChange: (event: { store: CertificateStore; certificate: Certificate; fingerprint: string; filename: string; }) => void; /** A CRL file was added. */ crlAdded: (event: { store: CrlStore; filename: string; }) => void; /** A CRL file was removed. */ crlRemoved: (event: { store: CrlStore; filename: string; }) => void; } /** * Options controlling certificate validation in * {@link CertificateManager.addTrustedCertificateFromChain}. * * By default all checks are **strict** (secure). Set individual * flags to `true` only in test/development environments. */ interface AddCertificateValidationOptions { /** * Accept certificates whose validity period has expired * or is not yet active. * @defaultValue false */ acceptExpiredCertificate?: boolean; /** * Accept certificates that have been revoked by their * issuer's CRL. When `false` (the default), a revoked * certificate is rejected with `BadCertificateRevoked`. * @defaultValue false */ acceptRevokedCertificate?: boolean; /** * Do not fail when a CRL is missing for an issuer in the * chain. When `false` (the default), a missing CRL causes * `BadCertificateRevocationUnknown`. * @defaultValue false */ ignoreMissingRevocationList?: boolean; /** * Maximum depth of the certificate chain (leaf + issuers). * The leaf certificate counts as depth 1. * @defaultValue 5 */ maxChainLength?: number; } /** * Options for creating a {@link CertificateManager}. */ interface CertificateManagerOptions { /** * RSA key size for generated private keys. * @defaultValue 2048 */ keySize?: KeySize; /** Filesystem path where the PKI directory structure is stored. */ location: string; /** * Validation options applied by * {@link CertificateManager.addTrustedCertificateFromChain}. * * Defaults are secure — all checks enabled. */ addCertificateValidationOptions?: AddCertificateValidationOptions; /** * When `true`, the CertificateManager will **not** start * chokidar file-system watchers on the PKI folders. * * The initial file-system scan still runs so the in-memory * indexes are populated, but live change detection is * disabled. This is useful in test / CI environments where * many CertificateManager instances are created in parallel * and the accumulated `fs.watch` handles exhaust the libuv * thread-pool, causing event-loop starvation. * * @defaultValue false */ disableFileWatchers?: boolean; } /** * Parameters for {@link createSelfSignedCertificate}. * All fields from {@link CreateSelfSignCertificateParam} are required. */ interface CreateSelfSignCertificateParam1 extends CreateSelfSignCertificateParam { /** * Output path for the certificate. * @defaultValue `"own/certs/self_signed_certificate.pem"` */ outputFile?: Filename; /** X.500 subject for the certificate. */ subject: SubjectOptions | string; /** OPC UA application URI for the SAN extension. */ applicationUri: string; /** DNS host names to include in the SAN extension. */ dns: string[]; /** Certificate "Not Before" date. */ startDate: Date; /** Number of days the certificate is valid. */ validity: number; } /** * Options to fine-tune certificate verification behaviour. * Passed to {@link CertificateManager.verifyCertificate}. * * Without any options, `verifyCertificate` is **strict**: only * certificates that are explicitly present in the trusted store * will return {@link VerificationStatus.Good}. Unknown or * rejected certificates return * {@link VerificationStatus.BadCertificateUntrusted} even when * their issuer chain is valid. * * Set {@link acceptCertificateWithValidIssuerChain} to `true` * to accept certificates whose issuer chain validates against * a trusted CA — even if the leaf certificate itself is not * in the trusted store. */ interface VerifyCertificateOptions { /** Accept certificates whose "Not After" date has passed. */ acceptOutdatedCertificate?: boolean; /** Accept issuer certificates whose "Not After" date has passed. */ acceptOutDatedIssuerCertificate?: boolean; /** Do not fail when a CRL is missing for an issuer. */ ignoreMissingRevocationList?: boolean; /** Accept certificates whose "Not Before" date is in the future. */ acceptPendingCertificate?: boolean; /** * Accept a certificate that is not in the trusted store when * its issuer (CA) certificate is trusted, the signature is * valid, and the certificate does not appear in the CRL. * * When `false` (the default), only certificates explicitly * placed in the trusted store are accepted — this is the * same behaviour as {@link CertificateManager.isCertificateTrusted}. * * @defaultValue false */ acceptCertificateWithValidIssuerChain?: boolean; } /** * OPC UA certificate verification status codes. * * These mirror the OPC UA `StatusCode` values for certificate * validation results. */ declare enum VerificationStatus { /** The certificate provided as a parameter is not valid. */ BadCertificateInvalid = "BadCertificateInvalid", /** An error occurred verifying security. */ BadSecurityChecksFailed = "BadSecurityChecksFailed", /** The certificate does not meet the requirements of the security policy. */ BadCertificatePolicyCheckFailed = "BadCertificatePolicyCheckFailed", /** The certificate has expired or is not yet valid. */ BadCertificateTimeInvalid = "BadCertificateTimeInvalid", /** An issuer certificate has expired or is not yet valid. */ BadCertificateIssuerTimeInvalid = "BadCertificateIssuerTimeInvalid", /** The HostName used to connect to a server does not match a HostName in the certificate. */ BadCertificateHostNameInvalid = "BadCertificateHostNameInvalid", /** The URI specified in the ApplicationDescription does not match the URI in the certificate. */ BadCertificateUriInvalid = "BadCertificateUriInvalid", /** The certificate may not be used for the requested operation. */ BadCertificateUseNotAllowed = "BadCertificateUseNotAllowed", /** The issuer certificate may not be used for the requested operation. */ BadCertificateIssuerUseNotAllowed = "BadCertificateIssuerUseNotAllowed", /** The certificate is not trusted. */ BadCertificateUntrusted = "BadCertificateUntrusted", /** It was not possible to determine if the certificate has been revoked. */ BadCertificateRevocationUnknown = "BadCertificateRevocationUnknown", /** It was not possible to determine if the issuer certificate has been revoked. */ BadCertificateIssuerRevocationUnknown = "BadCertificateIssuerRevocationUnknown", /** The certificate has been revoked. */ BadCertificateRevoked = "BadCertificateRevoked", /** The issuer certificate has been revoked. */ BadCertificateIssuerRevoked = "BadCertificateIssuerRevoked", /** The certificate chain is incomplete. */ BadCertificateChainIncomplete = "BadCertificateChainIncomplete", /** Validation OK. */ Good = "Good" } declare function coerceCertificateChain(certificate: Certificate | Certificate[]): Certificate[]; declare function makeFingerprint(certificate: Certificate | Certificate[] | CertificateRevocationList): string; /** * Check if the provided certificate acts as an issuer (CA) * @param certificate - the DER-encoded certificate * @returns true if the certificate has CA basicConstraints or keyCertSign keyUsage */ declare function isIssuer(certificate: Certificate): boolean; /** * Check if the provided certificate acts as an intermediate issuer. * An intermediate issuer is a CA certificate that is not a root CA (not self-signed). * @param certificate - the DER-encoded certificate * @returns true if the certificate is a CA and is not self-signed */ declare function isIntermediateIssuer(certificate: Certificate): boolean; /** * Check if the provided certificate acts as a root issuer. * A root issuer is a CA certificate that is self-signed. * @param certificate - the DER-encoded certificate * @returns true if the certificate is a CA and is self-signed */ declare function isRootIssuer(certificate: Certificate): boolean; /** * Find the issuer certificate for a given certificate within * a provided certificate chain. * * @param certificate - the DER-encoded certificate whose issuer to find * @param chain - candidate issuer certificates to search * @returns the matching issuer certificate, or `null` if not found */ declare function findIssuerCertificateInChain(certificate: Certificate | Certificate[], chain: Certificate[]): Certificate | null; /** * Lifecycle state of a {@link CertificateManager} instance. */ declare enum CertificateManagerState { Uninitialized = 0, Initializing = 1, Initialized = 2, Disposing = 3, Disposed = 4 } /** * Manages a GDS-compliant PKI directory structure for an OPC UA * application. * * The PKI store layout follows the OPC UA specification: * * ``` * <location>/ * ├── own/ * │ ├── certs/ Own certificate(s) * │ └── private/ Own private key * ├── trusted/ * │ ├── certs/ Trusted peer certificates * │ └── crl/ CRLs for trusted certs * ├── rejected/ Untrusted / rejected certificates * └── issuers/ * ├── certs/ CA (issuer) certificates * └── crl/ CRLs for issuer certificates * ``` * * File-system watchers keep the in-memory indexes in sync with * on-disk changes. Call {@link dispose} when the instance is no * longer needed to release watchers and allow the process to * exit cleanly. * * ## Environment Variables * * - **`OPCUA_PKI_USE_POLLING`** — set to `"true"` to use * polling-based file watching instead of native OS events. * Useful for NFS, CIFS, Docker volumes, or other remote / * virtual file systems where native events are unreliable. * * - **`OPCUA_PKI_POLLING_INTERVAL`** — polling interval in * milliseconds (only effective when polling is enabled). * Clamped to the range [100, 600 000]. Defaults to * {@link folderPollingInterval} (5 000 ms). * * @example * ```ts * const cm = new CertificateManager({ location: "/var/pki" }); * await cm.initialize(); * const status = await cm.verifyCertificate(cert); * await cm.dispose(); * ``` */ /** * Status codes returned by {@link CertificateManager.completeCertificateChain}. */ declare enum ChainCompletionStatus { /** The chain already reached a self-signed root — no action was needed. */ AlreadyComplete = "AlreadyComplete", /** One or more issuer certificates were successfully appended. */ ChainCompleted = "ChainCompleted", /** The issuer for the last certificate in the chain could not be found * in the issuers or trusted stores. The chain is still partial. */ IssuerNotFound = "IssuerNotFound", /** The input chain was empty. */ EmptyChain = "EmptyChain", /** Chain completion was stopped because the maximum depth was reached. */ MaxDepthReached = "MaxDepthReached" } /** * Result of {@link CertificateManager.completeCertificateChain}. */ interface ChainCompletionResult { /** The (possibly completed) certificate chain, leaf first. */ chain: Certificate[]; /** Status code indicating whether completion succeeded and why/why not. */ status: ChainCompletionStatus; /** Human-readable diagnostic message. */ message: string; } declare class CertificateManager extends EventEmitter { #private; /** * Dispose **all** active CertificateManager instances, * closing their file watchers and freeing resources. * * This is mainly useful in test tear-down to ensure the * Node.js process can exit cleanly. */ static disposeAll(): Promise<void>; /** * Assert that all CertificateManager instances have been * properly disposed. Throws an Error listing the locations * of any leaked instances. * * Intended for use in test `afterAll()` / `afterEach()` * hooks to catch missing `dispose()` calls early. * * @example * ```ts * after(() => { * CertificateManager.checkAllDisposed(); * }); * ``` */ static checkAllDisposed(): void; /** * When `true` (the default), any certificate that is not * already in the trusted or rejected store is automatically * written to the rejected folder the first time it is seen. */ untrustUnknownCertificate: boolean; /** Current lifecycle state of this instance. */ state: CertificateManagerState; /** @deprecated Use {@link folderPollingInterval} instead (typo fix). */ folderPoolingInterval: number; /** Interval in milliseconds for file-system polling (when enabled). */ get folderPollingInterval(): number; set folderPollingInterval(value: number); /** RSA key size used when generating the private key. */ readonly keySize: KeySize; /** * Create a new CertificateManager. * * The constructor creates the root directory if it does not * exist but does **not** initialise the PKI store — call * {@link initialize} before using any other method. * * @param options - configuration options */ constructor(options: CertificateManagerOptions); /** Path to the OpenSSL configuration file. */ get configFile(): string; /** Root directory of the PKI store. */ get rootDir(): string; /** Path to the private key file (`own/private/private_key.pem`). */ get privateKey(): string; /** Path to the OpenSSL random seed file. */ get randomFile(): string; /** * Move a certificate to the rejected store. * If the certificate was previously trusted, it will be removed from the trusted folder. * @param certificateOrChain - the DER-encoded certificate or certificate chain */ rejectCertificate(certificateOrChain: Certificate | Certificate[]): Promise<void>; /** * Move a certificate to the trusted store. * If the certificate was previously rejected, it will be removed from the rejected folder. * @param certificateOrChain - the DER-encoded certificate or certificate chain */ trustCertificate(certificateOrChain: Certificate | Certificate[]): Promise<void>; /** * Check whether the trusted certificate store is empty. * * This inspects the in-memory index, which is kept in * sync with the `trusted/certs/` folder by file-system * watchers after {@link initialize} has been called. */ isTrustListEmpty(): boolean; /** * Return the number of certificates currently in the * trusted store. */ getTrustedCertificateCount(): number; /** Path to the rejected certificates folder. */ get rejectedFolder(): string; /** Path to the trusted certificates folder. */ get trustedFolder(): string; /** Path to the trusted CRL folder. */ get crlFolder(): string; /** Path to the issuer (CA) certificates folder. */ get issuersCertFolder(): string; /** Path to the issuer CRL folder. */ get issuersCrlFolder(): string; /** Path to the own certificate folder. */ get ownCertFolder(): string; get ownPrivateFolder(): string; /** * Check if a certificate is in the trusted store. * If the certificate is unknown and `untrustUnknownCertificate` is set, * it will be written to the rejected folder. * @param certificate - the DER-encoded certificate * @returns `"Good"` if trusted, `"BadCertificateUntrusted"` if rejected/unknown, * or `"BadCertificateInvalid"` if the certificate cannot be parsed. */ isCertificateTrusted(certificateOrCertificateChain: Certificate | Certificate[]): Promise<"Good" | "BadCertificateUntrusted" | "BadCertificateInvalid">; /** * Internal verification hook called by {@link verifyCertificate}. * * Subclasses can override this to inject additional validation * logic (e.g. application-level policy checks) while still * delegating to the default chain/CRL/trust verification. * * @param certificate - the DER-encoded certificate to verify * @param options - verification options forwarded from the * public API * @returns the verification status code */ protected verifyCertificateAsync(certificate: Certificate | Certificate[], options: VerifyCertificateOptions): Promise<VerificationStatus>; /** * Verify a certificate against the PKI trust store. * * This performs a full validation including trust status, * issuer chain, CRL revocation checks, and time validity. * * @param certificate - the DER-encoded certificate to verify * @param options - optional flags to relax validation rules * @returns the verification status code */ verifyCertificate(certificate: Certificate | Certificate[], options?: VerifyCertificateOptions): Promise<VerificationStatus>; /** * Initialize the PKI directory structure, generate the * private key (if missing), and start file-system watchers. * * This method is idempotent — subsequent calls are no-ops. * It must be called before any certificate operations. */ initialize(): Promise<void>; /** * Dispose of the CertificateManager, releasing file watchers * and other resources. The instance should not be used after * calling this method. */ dispose(): Promise<void>; /** * Force a full re-scan of all PKI folders, rebuilding * the in-memory `_thumbs` index from scratch. * * Call this after external processes have modified the * PKI folders (e.g. via `writeTrustList` or CLI tools) * to ensure the CertificateManager sees the latest * state without waiting for file-system events. */ reloadCertificates(): Promise<void>; protected withLock2<T>(action: () => Promise<T>): Promise<T>; /** * Create a self-signed certificate for this PKI's private key. * * The certificate is written to `params.outputFile` or * `own/certs/self_signed_certificate.pem` by default. * * @param params - certificate parameters (subject, SANs, * validity, etc.) */ createSelfSignedCertificate(params: CreateSelfSignCertificateParam1): Promise<void>; /** * Create a Certificate Signing Request (CSR) using this * PKI's private key and configuration. * * The CSR file is written to `own/certs/` with a timestamped * filename. * * @param params - CSR parameters (subject, SANs) * @returns the filesystem path to the generated CSR file */ createCertificateRequest(params: CreateSelfSignCertificateParam): Promise<Filename>; /** * Add a CA (issuer) certificate to the issuers store. * If the certificate is already present, this is a no-op. * @param certificate - the DER-encoded CA certificate * @param validate - if `true`, verify the certificate before adding * @param addInTrustList - if `true`, also add to the trusted store * @returns `VerificationStatus.Good` on success */ addIssuer(certificate: DER, validate?: boolean, addInTrustList?: boolean): Promise<VerificationStatus>; /** * Add multiple CA (issuer) certificates to the issuers store. * @param certificates - the DER-encoded CA certificates * @param validate - if `true`, verify each certificate before adding * @param addInTrustList - if `true`, also add each certificate to the trusted store * @returns `VerificationStatus.Good` on success */ addIssuers(certificates: Certificate[], validate?: boolean, addInTrustList?: boolean): Promise<VerificationStatus>; /** * Add a CRL to the certificate manager. * @param crl - the CRL to add * @param target - "issuers" (default) writes to issuers/crl, "trusted" writes to trusted/crl */ addRevocationList(crl: CertificateRevocationList, target?: "issuers" | "trusted"): Promise<VerificationStatus>; /** * Remove all CRL files from the specified folder(s) and clear the * corresponding in-memory index. * @param target - "issuers" clears issuers/crl, "trusted" clears * trusted/crl, "all" clears both. */ clearRevocationLists(target: "issuers" | "trusted" | "all"): Promise<void>; /** * Check whether an issuer certificate with the given thumbprint * is already registered. * @param thumbprint - hex-encoded SHA-1 thumbprint (lowercase) */ hasIssuer(thumbprint: string): Promise<boolean>; /** * Remove a trusted certificate identified by its SHA-1 thumbprint. * Deletes the file on disk and removes the entry from the * in-memory index. * @param thumbprint - hex-encoded SHA-1 thumbprint (lowercase) * @returns the removed certificate buffer, or `null` if not found */ removeTrustedCertificate(thumbprint: string): Promise<Certificate | null>; /** * Remove an issuer certificate identified by its SHA-1 thumbprint. * Deletes the file on disk and removes the entry from the * in-memory index. * @param thumbprint - hex-encoded SHA-1 thumbprint (lowercase) * @returns the removed certificate buffer, or `null` if not found */ removeIssuer(thumbprint: string): Promise<Certificate | null>; /** * Remove all CRL files that were issued by the given CA certificate * from the specified folder (or both). * @param issuerCertificate - the CA certificate whose CRLs to remove * @param target - "issuers",