From 27e88d186d5841741ab9482e14ec7ef3419d6c85 Mon Sep 17 00:00:00 2001 From: Fang-Pen Lin Date: Fri, 31 Oct 2025 10:04:00 -0700 Subject: [PATCH] Use thumbprint instead --- .../services/pki-acme/pki-acme-account-dal.ts | 14 +++++++---- .../ee/services/pki-acme/pki-acme-service.ts | 23 +++++++++++++++---- 2 files changed, 28 insertions(+), 9 deletions(-) diff --git a/backend/src/ee/services/pki-acme/pki-acme-account-dal.ts b/backend/src/ee/services/pki-acme/pki-acme-account-dal.ts index bbfe042ee..685d07f2b 100644 --- a/backend/src/ee/services/pki-acme/pki-acme-account-dal.ts +++ b/backend/src/ee/services/pki-acme/pki-acme-account-dal.ts @@ -20,19 +20,23 @@ export const pkiAcmeAccountDALFactory = (db: TDbClient) => { } }; - const findByPublicKey = async (profileId: string, alg: string, publicKey: unknown, tx?: Knex) => { + const findByProfileIdAndPublicKeyThumbprintAndAlg = async ( + profileId: string, + alg: string, + publicKeyThumbprint: string, + tx?: Knex + ) => { try { - const account = await (tx || db)(TableName.PkiAcmeAccount).where({ profileId, alg, publicKey }).first(); - + const account = await (tx || db)(TableName.PkiAcmeAccount).where({ profileId, alg, publicKeyThumbprint }).first(); return account || null; } catch (error) { - throw new DatabaseError({ error, name: "Find PKI ACME account by public key and alg" }); + throw new DatabaseError({ error, name: "Find PKI ACME account by profile id, public key thumbprint and alg" }); } }; return { ...pkiAcmeAccountOrm, findByProjectIdAndAccountId, - findByPublicKey + findByProfileIdAndPublicKeyThumbprintAndAlg }; }; 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 d47d480f0..b165df639 100644 --- a/backend/src/ee/services/pki-acme/pki-acme-service.ts +++ b/backend/src/ee/services/pki-acme/pki-acme-service.ts @@ -11,7 +11,14 @@ import { EnrollmentType, TCertificateProfileWithConfigs } from "@app/services/certificate-profile/certificate-profile-types"; -import { errors, flattenedVerify, FlattenedVerifyResult, importJWK, JWSHeaderParameters } from "jose"; +import { + calculateJwkThumbprint, + errors, + flattenedVerify, + FlattenedVerifyResult, + importJWK, + JWSHeaderParameters +} from "jose"; import { z, ZodError } from "zod"; import { TPkiAcmeAccountDALFactory } from "./pki-acme-account-dal"; import { TPkiAcmeAuthDALFactory } from "./pki-acme-auth-dal"; @@ -57,7 +64,10 @@ import { type TPkiAcmeServiceFactoryDep = { certificateProfileDAL: Pick; - acmeAccountDAL: Pick; + acmeAccountDAL: Pick< + TPkiAcmeAccountDALFactory, + "findByProjectIdAndAccountId" | "findByProfileIdAndPublicKeyThumbprintAndAlg" | "create" + >; acmeOrderDAL: Pick; acmeAuthDAL: Pick; acmeOrderAuthDAL: Pick; @@ -286,8 +296,12 @@ export const pkiAcmeServiceFactory = ({ payload: TCreateAcmeAccountPayload; }): Promise> => { const profile = await validateAcmeProfile(profileId); - // TODO: ensure unique account per public key - const existingAccount: TPkiAcmeAccounts | null = await acmeAccountDAL.findByPublicKey(profileId, alg, jwk); + const publicKeyThumbprint = await calculateJwkThumbprint(jwk, "sha256"); + const existingAccount: TPkiAcmeAccounts | null = await acmeAccountDAL.findByProfileIdAndPublicKeyThumbprintAndAlg( + profileId, + alg, + publicKeyThumbprint + ); if (onlyReturnExisting && !existingAccount) { throw new AcmeAccountDoesNotExistError({ message: "ACME account not found" }); } @@ -311,6 +325,7 @@ export const pkiAcmeServiceFactory = ({ profileId: profile.id, alg, publicKey: jwk, + publicKeyThumbprint, emails: contact ?? [] }); // TODO: create audit log here