diff --git a/backend/src/db/migrations/20251029234547_add-pki-acme.ts b/backend/src/db/migrations/20251029234547_add-pki-acme.ts index fdc481a04..aa334cfba 100644 --- a/backend/src/db/migrations/20251029234547_add-pki-acme.ts +++ b/backend/src/db/migrations/20251029234547_add-pki-acme.ts @@ -104,6 +104,9 @@ export async function up(knex: Knex): Promise { // Authorization status t.string("status").notNullable(); // pending, valid, invalid, deactivated, expired, revoked + // Token used to validate the authorization through ACME challenge + t.timestamp("token").nullable(); + // Identifier type and value t.string("identifierType").notNullable(); // dns t.string("identifierValue").notNullable(); // domain name diff --git a/backend/src/db/schemas/index.ts b/backend/src/db/schemas/index.ts index e3db789ac..3513b95a7 100644 --- a/backend/src/db/schemas/index.ts +++ b/backend/src/db/schemas/index.ts @@ -96,8 +96,8 @@ export * from "./pki-acme-accounts"; export * from "./pki-acme-auths"; export * from "./pki-acme-challenges"; export * from "./pki-acme-enrollment-configs"; -export * from "./pki-acme-orders"; export * from "./pki-acme-order-auths"; +export * from "./pki-acme-orders"; export * from "./pki-alerts"; export * from "./pki-api-enrollment-configs"; export * from "./pki-certificate-profiles"; diff --git a/backend/src/db/schemas/pki-acme-auths.ts b/backend/src/db/schemas/pki-acme-auths.ts index de883356d..15c7a7c55 100644 --- a/backend/src/db/schemas/pki-acme-auths.ts +++ b/backend/src/db/schemas/pki-acme-auths.ts @@ -16,7 +16,8 @@ export const PkiAcmeAuthsSchema = z.object({ expiresAt: z.date(), certificateId: z.string().uuid().nullable().optional(), createdAt: z.date(), - updatedAt: z.date() + updatedAt: z.date(), + token: z.string().nullable().optional() }); export type TPkiAcmeAuths = z.infer; diff --git a/backend/src/ee/services/pki-acme/pki-acme-service.ts b/backend/src/ee/services/pki-acme/pki-acme-service.ts index e5336b69c..35f668a8d 100644 --- a/backend/src/ee/services/pki-acme/pki-acme-service.ts +++ b/backend/src/ee/services/pki-acme/pki-acme-service.ts @@ -1,19 +1,11 @@ -import { getConfig } from "@app/lib/config/env"; -import { NotFoundError } from "@app/lib/errors"; - -import { TCertificateProfileDALFactory } from "@app/services/certificate-profile/certificate-profile-dal"; - -import { - AcmeAccountDoesNotExistError, - AcmeBadPublicKeyError, - AcmeMalformedError, - AcmeServerInternalError, - AcmeUnsupportedIdentifierError -} from "./pki-acme-errors"; - import { TPkiAcmeAccounts } from "@app/db/schemas/pki-acme-accounts"; import { TPkiAcmeAuths } from "@app/db/schemas/pki-acme-auths"; +import { getConfig } from "@app/lib/config/env"; +import { crypto } from "@app/lib/crypto/cryptography"; +import { NotFoundError } from "@app/lib/errors"; import { logger } from "@app/lib/logger"; +import { TCertificateProfileDALFactory } from "@app/services/certificate-profile/certificate-profile-dal"; + import { EnrollmentType, TCertificateProfileWithConfigs @@ -22,6 +14,13 @@ import { errors, flattenedVerify, FlattenedVerifyResult, importJWK, JWSHeaderPar import { z, ZodError } from "zod"; import { TPkiAcmeAccountDALFactory } from "./pki-acme-account-dal"; import { TPkiAcmeAuthDALFactory } from "./pki-acme-auth-dal"; +import { + AcmeAccountDoesNotExistError, + AcmeBadPublicKeyError, + AcmeMalformedError, + AcmeServerInternalError, + AcmeUnsupportedIdentifierError +} from "./pki-acme-errors"; import { TPkiAcmeOrderAuthDALFactory } from "./pki-acme-order-auth-dal"; import { TPkiAcmeOrderDALFactory } from "./pki-acme-order-dal"; import { @@ -32,6 +31,7 @@ import { ProtectedHeaderSchema } from "./pki-acme-schemas"; import { + TAcmeOrderResource, TAcmeResponse, TAuthenciatedJwsPayload, TCreateAcmeAccountPayload, @@ -44,7 +44,6 @@ import { TGetAcmeDirectoryResponse, TJwsPayload, TListAcmeOrdersResponse, - TAcmeOrderResource, TPkiAcmeServiceFactory, TRawJwsPayload, TRespondToAcmeChallengeResponse @@ -357,6 +356,10 @@ export const pkiAcmeServiceFactory = ({ status: AcmeAuthStatus.Pending, identifierType: identifier.type, identifierValue: identifier.value, + // RFC 8555 suggests a token with at least 128 bits of entropy + // We are using 256 bits of entropy here, should be enough for now + // ref: https://datatracker.ietf.org/doc/html/rfc8555#section-11.3 + token: crypto.randomBytes(32).toString("base64"), // TODO: read config from the profile to get the expiration time instead expiresAt: new Date(Date.now() + 24 * 60 * 60 * 1000) },