From b2297a3ecd71a2ee3a039383f1046ce9c2bc285f Mon Sep 17 00:00:00 2001 From: Fang-Pen Lin Date: Tue, 11 Nov 2025 11:43:14 -0800 Subject: [PATCH] Check more for the CSR --- .../ee/services/pki-acme/pki-acme-service.ts | 58 +++++++++++++++---- .../certificate-profile-dal.ts | 15 ++--- 2 files changed, 54 insertions(+), 19 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 feb8ad782..e2566f57c 100644 --- a/backend/src/ee/services/pki-acme/pki-acme-service.ts +++ b/backend/src/ee/services/pki-acme/pki-acme-service.ts @@ -31,6 +31,12 @@ import { getProjectKmsCertificateKeyId } from "@app/services/project/project-fns import { getConfig } from "@app/lib/config/env"; import { TCertificateAuthorityDALFactory } from "@app/services/certificate-authority/certificate-authority-dal"; import { CaType } from "@app/services/certificate-authority/certificate-authority-enums"; +import { extractCertificateRequestFromCSR } from "@app/services/certificate-common/certificate-csr-utils"; +import { + CertExtendedKeyUsage, + CertKeyUsage, + CertSubjectAlternativeNameType +} from "@app/services/certificate/certificate-types"; import { TPkiAcmeAccountDALFactory } from "./pki-acme-account-dal"; import { TPkiAcmeAuthDALFactory } from "./pki-acme-auth-dal"; import { TPkiAcmeChallengeDALFactory } from "./pki-acme-challenge-dal"; @@ -641,7 +647,40 @@ export const pkiAcmeServiceFactory = ({ if (finalizingOrder.expiresAt < new Date()) { throw new AcmeOrderNotReadyError({ message: "ACME order has expired" }); } + const { csr } = payload; + + // Check and validate the CSR + const certificateRequest = extractCertificateRequestFromCSR(csr); + if (!certificateRequest.commonName) { + throw new AcmeBadCSRError({ detail: "Invalid CSR: Common name is required" }); + } + if ( + certificateRequest.subjectAlternativeNames?.some( + (san) => san.type !== CertSubjectAlternativeNameType.DNS_NAME + ) + ) { + throw new AcmeBadCSRError({ detail: "Invalid CSR: Only DNS subject alternative names are supported" }); + } + const orderWithAuthorizations = (await acmeOrderDAL.findByAccountAndOrderIdWithAuthorizations( + accountId, + orderId, + tx + ))!; + const csrIdentifierValues = new Set( + orderWithAuthorizations.authorizations + .map((auth) => auth.identifierValue.toLowerCase()) + .concat([certificateRequest.commonName!.toLowerCase()]) + ); + if ( + csrIdentifierValues.size !== orderWithAuthorizations.authorizations.length || + !orderWithAuthorizations.authorizations.every((auth) => + csrIdentifierValues.has(auth.identifierValue.toLowerCase()) + ) + ) { + throw new AcmeBadCSRError({ detail: "Invalid CSR: Common name + SANs mismatch with order identifiers" }); + } + const ca = await certificateAuthorityDAL.findByIdWithAssociatedCa(profile.caId); if (!ca) { throw new NotFoundError({ message: "Certificate Authority not found" }); @@ -672,16 +711,15 @@ export const pkiAcmeServiceFactory = ({ }); return { certificateId: result.certificateId }; } else { - const orderWithAuthorizations = (await acmeOrderDAL.findByAccountAndOrderIdWithAuthorizations( - accountId, - orderId, - tx - ))!; - const result = await orderCertificateForAcmeProfile( - profileId, - orderWithAuthorizations.authorizations[0].identifierValue - ); - return { certificateId: result }; + const { certificateAuthority } = (await certificateProfileDAL.findByIdWithConfigs(profileId, tx))!; + const cert = await acmeCertificateAuthorityFns.orderCertificate({ + caId: certificateAuthority.id, + commonName: certificateRequest.commonName, + altNames: certificateRequest.subjectAlternativeNames?.map((san) => san.value), + keyUsages: [CertKeyUsage.DIGITAL_SIGNATURE, CertKeyUsage.KEY_ENCIPHERMENT, CertKeyUsage.KEY_AGREEMENT], + extendedKeyUsages: [CertExtendedKeyUsage.SERVER_AUTH] + }); + return { certificateId: cert.id }; } })(); await acmeOrderDAL.updateById( diff --git a/backend/src/services/certificate-profile/certificate-profile-dal.ts b/backend/src/services/certificate-profile/certificate-profile-dal.ts index 53172b744..d2f468248 100644 --- a/backend/src/services/certificate-profile/certificate-profile-dal.ts +++ b/backend/src/services/certificate-profile/certificate-profile-dal.ts @@ -168,15 +168,12 @@ export const certificateProfileDALFactory = (db: TDbClient) => { } as TCertificateProfileWithConfigs["acmeConfig"]) : undefined; - const certificateAuthority = - result.caId && result.caProjectId && result.caStatus && result.caName - ? ({ - id: result.caId, - projectId: result.caProjectId, - status: result.caStatus, - name: result.caName - } as TCertificateProfileWithConfigs["certificateAuthority"]) - : undefined; + const certificateAuthority = { + id: result.caId, + projectId: result.caProjectId, + status: result.caStatus, + name: result.caName + } as TCertificateProfileWithConfigs["certificateAuthority"]; const certificateTemplate = result.templateId && result.templateProjectId && result.templateName