From e7f098e63ebc61ae44cf2802e5f76737e839c5f7 Mon Sep 17 00:00:00 2001 From: Fang-Pen Lin Date: Mon, 3 Nov 2025 16:07:19 -0800 Subject: [PATCH] Implement more checks --- .../ee/services/pki-acme/pki-acme-service.ts | 30 +++++++++++++++---- .../certificate-profile-service.ts | 2 +- 2 files changed, 25 insertions(+), 7 deletions(-) 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 16d917fae..930e22872 100644 --- a/backend/src/ee/services/pki-acme/pki-acme-service.ts +++ b/backend/src/ee/services/pki-acme/pki-acme-service.ts @@ -324,26 +324,45 @@ export const pkiAcmeServiceFactory = ({ throw new AcmeExternalAccountRequiredError({ detail: "External account binding is required" }); } + const publicKeyThumbprint = await calculateJwkThumbprint(jwk, "sha256"); const certificateManagerKmsId = await getProjectKmsCertificateKeyId({ projectId: profile.projectId, projectDAL, kmsService }); - const kmsDecryptor = await kmsService.decryptWithKmsKey({ kmsId: certificateManagerKmsId }); const eabSecret = await kmsDecryptor({ cipherTextBlob: profile.acmeConfig!.encryptedEabSecret }); - const encodedSecret = new TextEncoder().encode(eabSecret.toString()); try { const { payload: eabPayload, protectedHeader: eabProtectedHeader } = await flattenedVerify( externalAccountBinding, - encodedSecret + eabSecret ); - const alg = eabProtectedHeader!.alg!; - if (!["HS256", "HS384", "HS512"].includes(alg)) { + const eabAlg = eabProtectedHeader!.alg!; + if (!["HS256", "HS384", "HS512"].includes(eabAlg)) { throw new AcmeMalformedError({ detail: "Invalid algorithm for external account binding JWS payload" }); } + // Make sure the URL matches the expected URL + const url = eabProtectedHeader!.url!; + if (url !== buildUrl(profile.id, "/new-account")) { + throw new UnauthorizedError({ message: "External account binding URL mismatch" }); + } + + // Make sure the JWK in the EAB payload matches the one provided in the outer JWS payload + const decoder = new TextDecoder(); + const decodedEabPayload = decoder.decode(eabPayload); + const eabPayloadJson = JSON.parse(decodedEabPayload); + const eabPayloadJwkThumbprint = await calculateJwkThumbprint( + eabPayloadJson.jwk as JsonWebKey, + alg as "sha256" | "sha384" | "sha512" + ); + if (eabPayloadJwkThumbprint !== publicKeyThumbprint || eabAlg !== alg) { + throw new AcmeBadPublicKeyError({ + message: "External account binding public key thumbprint or algorithm mismatch" + }); + } + // Make sure the KID in the EAB payload matches the profile ID if ((eabPayload as unknown as { kid: string }).kid !== profile.id) { throw new UnauthorizedError({ message: "External account binding KID mismatch" }); } @@ -358,7 +377,6 @@ export const pkiAcmeServiceFactory = ({ throw new AcmeServerInternalError({ detail: "Failed to verify EAB JWS payload" }); } - const publicKeyThumbprint = await calculateJwkThumbprint(jwk, "sha256"); const existingAccount: TPkiAcmeAccounts | null = await acmeAccountDAL.findByProfileIdAndPublicKeyThumbprintAndAlg( profileId, alg, diff --git a/backend/src/services/certificate-profile/certificate-profile-service.ts b/backend/src/services/certificate-profile/certificate-profile-service.ts index 2e92c4b02..be7a0a253 100644 --- a/backend/src/services/certificate-profile/certificate-profile-service.ts +++ b/backend/src/services/certificate-profile/certificate-profile-service.ts @@ -832,7 +832,7 @@ export const certificateProfileServiceFactory = ({ kmsId: certificateManagerKmsId }); const eabSecret = await kmsDecryptor({ cipherTextBlob: profile.acmeConfig.encryptedEabSecret }); - return { eabKid: profile.id, eabSecret: eabSecret.toString() }; + return { eabKid: profile.id, eabSecret: eabSecret.toString("base64url") }; }; return {