import * as crypto from 'node:crypto'; import { AcmeCrypto } from './acme.classes.crypto.js'; import type { AcmeHttpClient } from './acme.classes.http-client.js'; import type { IAcmeChallenge } from './acme.interfaces.js'; /** * ACME challenge operations - key authorization computation and challenge completion */ export class AcmeChallengeManager { private httpClient: AcmeHttpClient; private accountKeyPem: string; constructor(httpClient: AcmeHttpClient, accountKeyPem: string) { this.httpClient = httpClient; this.accountKeyPem = accountKeyPem; } /** * Compute the key authorization for a challenge. * For http-01: returns `token.thumbprint` * For dns-01: returns `base64url(sha256(token.thumbprint))` * * This is a synchronous, pure-crypto computation. */ getKeyAuthorization(challenge: IAcmeChallenge): string { const jwk = AcmeCrypto.getJwk(this.accountKeyPem); const thumbprint = AcmeCrypto.getJwkThumbprint(jwk); const keyAuth = `${challenge.token}.${thumbprint}`; if (challenge.type === 'dns-01') { // DNS-01 uses base64url(SHA-256(keyAuthorization)) return crypto.createHash('sha256').update(keyAuth).digest().toString('base64url'); } // HTTP-01 and others use the raw key authorization return keyAuth; } /** * Notify the ACME server to validate a challenge (POST {} to challenge URL) */ async complete(challenge: IAcmeChallenge): Promise { await this.httpClient.signedRequest(challenge.url, {}); } }