2026-02-15 20:20:46 +00:00
|
|
|
import { AcmeCrypto } from './acme.classes.crypto.js';
|
|
|
|
|
import { ACME_DIRECTORY_URLS } from './acme.classes.directory.js';
|
|
|
|
|
import { AcmeHttpClient, type TAcmeLogger } from './acme.classes.http-client.js';
|
|
|
|
|
import { AcmeAccount } from './acme.classes.account.js';
|
|
|
|
|
import { AcmeOrderManager } from './acme.classes.order.js';
|
|
|
|
|
import { AcmeChallengeManager } from './acme.classes.challenge.js';
|
|
|
|
|
import type {
|
|
|
|
|
IAcmeAccount,
|
|
|
|
|
IAcmeAccountCreateRequest,
|
|
|
|
|
IAcmeAuthorization,
|
|
|
|
|
IAcmeChallenge,
|
|
|
|
|
IAcmeIdentifier,
|
|
|
|
|
IAcmeOrder,
|
|
|
|
|
} from './acme.interfaces.js';
|
|
|
|
|
|
|
|
|
|
export interface IAcmeClientOptions {
|
|
|
|
|
directoryUrl: string;
|
|
|
|
|
accountKeyPem: string;
|
|
|
|
|
logger?: TAcmeLogger;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Top-level ACME client facade.
|
|
|
|
|
* Composes HTTP transport, account management, order lifecycle, and challenge handling.
|
|
|
|
|
*/
|
|
|
|
|
export class AcmeClient {
|
|
|
|
|
private httpClient: AcmeHttpClient;
|
|
|
|
|
private account: AcmeAccount;
|
|
|
|
|
private orderManager: AcmeOrderManager;
|
|
|
|
|
private challengeManager: AcmeChallengeManager;
|
|
|
|
|
|
|
|
|
|
/** Well-known CA directory URLs */
|
|
|
|
|
static directory = ACME_DIRECTORY_URLS;
|
|
|
|
|
/** Crypto utilities */
|
|
|
|
|
static crypto = AcmeCrypto;
|
|
|
|
|
|
|
|
|
|
constructor(options: IAcmeClientOptions) {
|
|
|
|
|
this.httpClient = new AcmeHttpClient(options.directoryUrl, options.accountKeyPem, options.logger);
|
|
|
|
|
this.account = new AcmeAccount(this.httpClient);
|
|
|
|
|
this.orderManager = new AcmeOrderManager(this.httpClient);
|
|
|
|
|
this.challengeManager = new AcmeChallengeManager(this.httpClient, options.accountKeyPem);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Register or retrieve an ACME account
|
|
|
|
|
*/
|
|
|
|
|
async createAccount(request: IAcmeAccountCreateRequest): Promise<IAcmeAccount> {
|
|
|
|
|
return this.account.create(request);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Create a new certificate order
|
|
|
|
|
*/
|
|
|
|
|
async createOrder(opts: { identifiers: IAcmeIdentifier[] }): Promise<IAcmeOrder> {
|
|
|
|
|
return this.orderManager.create(opts);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Get all authorizations for an order
|
|
|
|
|
*/
|
|
|
|
|
async getAuthorizations(order: IAcmeOrder): Promise<IAcmeAuthorization[]> {
|
|
|
|
|
return this.orderManager.getAuthorizations(order);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Compute the key authorization string for a challenge (sync)
|
|
|
|
|
*/
|
|
|
|
|
getChallengeKeyAuthorization(challenge: IAcmeChallenge): string {
|
|
|
|
|
return this.challengeManager.getKeyAuthorization(challenge);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Notify the ACME server to validate a challenge
|
|
|
|
|
*/
|
|
|
|
|
async completeChallenge(challenge: IAcmeChallenge): Promise<void> {
|
|
|
|
|
return this.challengeManager.complete(challenge);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Poll an ACME resource until it reaches valid/ready status
|
|
|
|
|
*/
|
|
|
|
|
async waitForValidStatus(item: { url: string }): Promise<any> {
|
|
|
|
|
return this.orderManager.waitForValidStatus(item);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Finalize an order by submitting the CSR
|
|
|
|
|
*/
|
|
|
|
|
async finalizeOrder(order: IAcmeOrder, csrPem: string): Promise<void> {
|
|
|
|
|
return this.orderManager.finalize(order, csrPem);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Download the certificate chain (PEM)
|
|
|
|
|
*/
|
|
|
|
|
async getCertificate(order: IAcmeOrder): Promise<string> {
|
|
|
|
|
return this.orderManager.getCertificate(order);
|
|
|
|
|
}
|
2026-02-15 20:43:06 +00:00
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Destroy HTTP transport to release sockets and allow process exit.
|
|
|
|
|
*/
|
|
|
|
|
destroy(): void {
|
|
|
|
|
this.httpClient.destroy();
|
|
|
|
|
}
|
2026-02-15 20:20:46 +00:00
|
|
|
}
|