import type { DB, DBTransaction, LevelPath, KeyPath } from '@matrixai/db'; import type { PublicKey, PrivateKey, Certificate, CertificatePEM, CertificatePEMChain } from './types.js'; import type KeyRing from './KeyRing.js'; import type TaskManager from '../tasks/TaskManager.js'; import type { CertId, TaskHandlerId, TaskId } from '../ids/types.js'; import type { TaskHandler } from '../tasks/types.js'; import type { PolykeyWorkerManager } from '../workers/types.js'; import Logger from '@matrixai/logger'; import { createDestroyStartStop } from '@matrixai/async-init'; import { Lock } from '@matrixai/async-locks'; interface CertManager extends createDestroyStartStop.CreateDestroyStartStop { } declare class CertManager { /** * The password is needed in case there needs to be an automatic renewal. * When the certificate is automatically renewed, a new key pair is generated. * * The `certDuration` is in seconds. This is the default duration of generated * certificates. Certificates will be valid from now until inclusive of * `now + certDuration`. Certificates only have second resolution, so `now` is * always interpreted as truncated to the second. * * The `certRenewLeadTime` is in seconds. This is the amount time set before the * last valid second timestamp, in which to automatically run the certificate * renewal with the current key pair. This must ensure enough time for change * propagation across the gestalt graph. */ static createCertManager({ db, keyRing, taskManager, certDuration, certRenewLeadTime, workerManager, logger, subjectAttrsExtra, now, lazy, fresh, }: { db: DB; keyRing: KeyRing; taskManager: TaskManager; certDuration?: number; certRenewLeadTime?: number; workerManager?: PolykeyWorkerManager; logger?: Logger; subjectAttrsExtra?: Array<{ [key: string]: Array; }>; issuerAttrsExtra?: Array<{ [key: string]: Array; }>; now?: Date; lazy?: boolean; fresh?: boolean; }): Promise; /** * Certificate duration in seconds */ readonly certDuration: number; readonly certRenewLeadTime: number; protected logger: Logger; protected db: DB; protected keyRing: KeyRing; protected taskManager: TaskManager; protected workerManager?: PolykeyWorkerManager; protected generateCertId: () => CertId; protected dbPath: LevelPath; /** * Certificate collection * `CertManager/certs/{CertId} -> {raw(CertificateASN1)}` */ protected dbCertsPath: LevelPath; /** * Maintain last `CertID` to preserve monotonicity across process restarts * `CertManager/lastCertId -> {raw(CertId)}` */ protected dbLastCertIdPath: KeyPath; protected renewResetLock: Lock; protected renewCurrentCertHandler: TaskHandler; protected renewCurrentCertHandlerId: TaskHandlerId; protected renewCurrentCertTaskId?: TaskId; protected subjectAttrsExtra?: Array<{ [key: string]: Array; }>; protected tasksRunning: boolean; constructor({ db, keyRing, taskManager, certDuration, certRenewLeadTime, workerManager, logger, }: { db: DB; keyRing: KeyRing; taskManager: TaskManager; certDuration: number; certRenewLeadTime: number; workerManager?: PolykeyWorkerManager; logger: Logger; }); setWorkerManager(workerManager: PolykeyWorkerManager): void; unsetWorkerManager(): void; start({ subjectAttrsExtra, now, lazy, fresh, }?: { subjectAttrsExtra?: Array<{ [key: string]: Array; }>; now?: Date; lazy?: boolean; fresh?: boolean; }): Promise; stop(): Promise; destroy(): Promise; /** * Start background tasks. * This is idempotent. */ startTasks(now?: Date): Promise; /** * Stop background tasks. * This is idempotent. */ stopTasks(): Promise; getLastCertId(tran?: DBTransaction): Promise; /** * Get a certificate according to the `CertID` */ getCert(certId: CertId, tran?: DBTransaction): Promise; /** * Get `Certificate` from leaf to root */ getCerts(tran?: DBTransaction): AsyncGenerator; /** * Gets an array of `Certificate` in order of leaf to root */ getCertsChain(tran?: DBTransaction): Promise>; /** * Get `CertificatePEM` from leaf to root */ getCertPEMs(tran?: DBTransaction): AsyncGenerator; /** * Gets an array of `CertificatePEM` in order of leaf to root */ getCertPEMsChain(tran?: DBTransaction): Promise>; /** * Gets a concatenated `CertificatePEM` ordered from leaf to root */ getCertPEMsChainPEM(tran?: DBTransaction): Promise; /** * Get the current (leaf) certificate */ getCurrentCert(tran?: DBTransaction): Promise; /** * Get the current (leaf) certificate in PEM */ getCurrentCertPEM(tran?: DBTransaction): Promise; /** * Generates a new leaf certificate with a new key pair. * This new certificate is chained to the previous certificate. * * It is self-signed and also signed by the previous certificate * * The self-signed signature is encoded within the `NodeSignatureExtension`. * The certificate signature is produced by the previous certificate's * public key. * * Garbage collection of old certificates is executed immediately afterwards. * The garbage collection will be checking if the certificates are expired. * It will reference the same timestamp that was used to generate the new * certificate. */ renewCertWithNewKeyPair(password: string, duration?: number, now?: Date): Promise; /** * Generates a new leaf certificate with the current key pair. * This new certificate is chained to the previous certificate. * It is self-signed and also signed by the previous certificate * * The self-signed signature is encoded within the `NodeSignatureExtension`. * The certificate signature is produced by the previous certificate's * public key. * * This maintains a certificate chain that provides zero-downtime migration. * The `NodeId` does not change. * * Garbage collection of old certificates is executed immediately afterwards. * The garbage collection will be checking if the certificates are expired. * It will reference the same timestamp that was used to generate the new * certificate. */ renewCertWithCurrentKeyPair(duration?: number, now?: Date): Promise; /** * Generates a new certificate chain starting with a new key pair. * The new certificate is self-signed, and has no previous certificate. * The results in a new `NodeId`. * * The self-signed signature is encoded within the `NodeSignatureExtension`. * * Garbage collection of old certificates is executed immediately afterwards. * The garbage collection will be checking if the certificates are expired. * It will reference the same timestamp that was used to generate the new * certificate. */ resetCertWithNewKeyPair(password: string, duration?: number, now?: Date): Promise; /** * Generates a new certificate chain starting with the current key pair. * The new certificate is self-signed, and has no previous certificate. * The `NodeId` does not change. * * The self-signed signature is encoded within the `NodeSignatureExtension`. * * Garbage collection of old certificates is executed immediately afterwards. * The garbage collection will be checking if the certificates are expired. * It will reference the same timestamp that was used to generate the new * certificate. */ resetCertWithCurrentKeyPair(duration?: number, now?: Date): Promise; protected putCert(cert: Certificate, tran?: DBTransaction): Promise; protected delCert(certId: CertId, tran?: DBTransaction): Promise; protected setupCurrentCert(now?: Date): Promise; /** * Sets up the renew current certificate task. * This will set the renewal of the certificate to the remaining duration * minus the lead time, with the minimum delaying being 0. * This task is a singleton. It must be updated when the current certificate * is renewed. */ protected setupRenewCurrentCertTask(now?: Date): Promise; protected generateCertificate({ subjectKeyPair, issuerPrivateKey, duration, subjectAttrsExtra, issuerAttrsExtra, now, }: { subjectKeyPair: { publicKey: PublicKey; privateKey: PrivateKey; }; issuerPrivateKey: PrivateKey; duration: number; subjectAttrsExtra?: Array<{ [key: string]: Array; }>; issuerAttrsExtra?: Array<{ [key: string]: Array; }>; now?: Date; }): Promise; /** * Garbage collect invalid or expired certificates. * Invalid certificates can happen if key rotation does not succeed. * It could mean that the leaf certificate does not match the current key pair. * * Expired certificates are no longer valid and should be deleted. * This will always ensure that the current certificate is kept. * It will also keep the first expired certificate in the chain. * All subsequent certificates will be deleted. * * Note if generating a new certificate, it may be generated with a duration * of 0. When this occurs, the certificate is considered still valid for the * `now` timestamp at generation. Therefore upon finding the current * certificate we don't bother checking whether it is not expired. We can * assume it is not expired. However to be absolutely robust, pass the same * `now` between generation and `this.gcCerts` to prevent accidental garbage * collection of a 0-duration current certificate. */ protected gcCerts(force?: boolean, now?: Date): Promise; } export default CertManager;