diff --git a/backend/src/server/routes/v1/certificate-authority-routers/internal-certificate-authority-router.ts b/backend/src/server/routes/v1/certificate-authority-routers/internal-certificate-authority-router.ts index 648a869bbd..be3f53d0aa 100644 --- a/backend/src/server/routes/v1/certificate-authority-routers/internal-certificate-authority-router.ts +++ b/backend/src/server/routes/v1/certificate-authority-routers/internal-certificate-authority-router.ts @@ -93,12 +93,13 @@ export const registerInternalCertificateAuthorityRouter = async (server: Fastify 200: z.object({ certificate: z.string().trim().describe(CERTIFICATE_AUTHORITIES.RENEW_CA_CERT.certificate), certificateChain: z.string().trim().describe(CERTIFICATE_AUTHORITIES.RENEW_CA_CERT.certificateChain), - serialNumber: z.string().trim().describe(CERTIFICATE_AUTHORITIES.RENEW_CA_CERT.serialNumber) + serialNumber: z.string().trim().describe(CERTIFICATE_AUTHORITIES.RENEW_CA_CERT.serialNumber), + certId: z.string().describe("Certificate ID") }) } }, handler: async (req) => { - const { certificate, certificateChain, serialNumber, ca } = + const { certificate, certificateChain, serialNumber, certId, ca } = await server.services.internalCertificateAuthority.renewCaCert({ caId: req.params.caId, actor: req.permission.type, @@ -123,7 +124,8 @@ export const registerInternalCertificateAuthorityRouter = async (server: Fastify return { certificate, certificateChain, - serialNumber + serialNumber, + certId }; } }); @@ -159,7 +161,8 @@ export const registerInternalCertificateAuthorityRouter = async (server: Fastify .string() .trim() .describe(CERTIFICATE_AUTHORITIES.GENERATE_CA_CERTIFICATE.certificateChain), - serialNumber: z.string().trim().describe(CERTIFICATE_AUTHORITIES.GENERATE_CA_CERTIFICATE.serialNumber) + serialNumber: z.string().trim().describe(CERTIFICATE_AUTHORITIES.GENERATE_CA_CERTIFICATE.serialNumber), + certId: z.string().describe("Certificate ID") }) } }, @@ -169,6 +172,7 @@ export const registerInternalCertificateAuthorityRouter = async (server: Fastify let certificate: string; let certificateChain: string; let serialNumber: string; + let certId: string; let ca: { id: string; dn: string; projectId: string }; if (parentCaId) { @@ -187,6 +191,7 @@ export const registerInternalCertificateAuthorityRouter = async (server: Fastify certificate = intermediateCert.certificate; certificateChain = intermediateCert.certificateChain; serialNumber = intermediateCert.serialNumber; + certId = intermediateCert.certId; ca = await server.services.internalCertificateAuthority.getCaById({ caId: req.params.caId, @@ -210,6 +215,7 @@ export const registerInternalCertificateAuthorityRouter = async (server: Fastify certificate = rootCert.certificate; certificateChain = rootCert.certificateChain; serialNumber = rootCert.serialNumber; + certId = rootCert.certId; ca = rootCert.ca; } @@ -230,7 +236,8 @@ export const registerInternalCertificateAuthorityRouter = async (server: Fastify return { certificate, certificateChain, - serialNumber + serialNumber, + certId }; } }); @@ -255,6 +262,7 @@ export const registerInternalCertificateAuthorityRouter = async (server: Fastify certificate: z.string().describe(CERTIFICATE_AUTHORITIES.GET_CA_CERTS.certificate), certificateChain: z.string().describe(CERTIFICATE_AUTHORITIES.GET_CA_CERTS.certificateChain), serialNumber: z.string().describe(CERTIFICATE_AUTHORITIES.GET_CA_CERTS.serialNumber), + certId: z.string().describe("Certificate ID"), version: z.number().describe(CERTIFICATE_AUTHORITIES.GET_CA_CERTS.version) }) ) @@ -303,12 +311,13 @@ export const registerInternalCertificateAuthorityRouter = async (server: Fastify 200: z.object({ certificate: z.string().describe(CERTIFICATE_AUTHORITIES.GET_CERT.certificate), certificateChain: z.string().describe(CERTIFICATE_AUTHORITIES.GET_CERT.certificateChain), - serialNumber: z.string().describe(CERTIFICATE_AUTHORITIES.GET_CERT.serialNumber) + serialNumber: z.string().describe(CERTIFICATE_AUTHORITIES.GET_CERT.serialNumber), + certId: z.string().describe("Certificate ID") }) } }, handler: async (req) => { - const { certificate, certificateChain, serialNumber, ca } = + const { certificate, certificateChain, serialNumber, certId, ca } = await server.services.internalCertificateAuthority.getCaCert({ caId: req.params.caId, actor: req.permission.type, @@ -332,7 +341,64 @@ export const registerInternalCertificateAuthorityRouter = async (server: Fastify return { certificate, certificateChain, - serialNumber + serialNumber, + certId + }; + } + }); + + server.route({ + method: "GET", + url: "/:caId/certificate/:certId", + config: { + rateLimit: readLimit + }, + onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]), + schema: { + hide: false, + tags: [ApiDocsTags.PkiCertificateAuthorities], + description: "Get a specific CA certificate by ID", + params: z.object({ + caId: z.string().trim().describe(CERTIFICATE_AUTHORITIES.GET_CERT.caId), + certId: z.string().trim().describe("Certificate ID to retrieve") + }), + response: { + 200: z.object({ + certificate: z.string().describe(CERTIFICATE_AUTHORITIES.GET_CERT.certificate), + certificateChain: z.string().describe(CERTIFICATE_AUTHORITIES.GET_CERT.certificateChain), + serialNumber: z.string().describe(CERTIFICATE_AUTHORITIES.GET_CERT.serialNumber), + certId: z.string().describe("Certificate ID") + }) + } + }, + handler: async (req) => { + const { certificate, certificateChain, serialNumber, certId, ca } = + await server.services.internalCertificateAuthority.getCaCertByIdWithAuth({ + caId: req.params.caId, + certId: req.params.certId, + actor: req.permission.type, + actorId: req.permission.id, + actorAuthMethod: req.permission.authMethod, + actorOrgId: req.permission.orgId + }); + + await server.services.auditLog.createAuditLog({ + ...req.auditLogInfo, + projectId: ca.projectId, + event: { + type: EventType.GET_CA_CERT, + metadata: { + caId: ca.id, + dn: ca.dn + } + } + }); + + return { + certificate, + certificateChain, + serialNumber, + certId }; } }); diff --git a/backend/src/services/certificate-authority/certificate-authority-fns.ts b/backend/src/services/certificate-authority/certificate-authority-fns.ts index aff81dcc53..32c22f0e64 100644 --- a/backend/src/services/certificate-authority/certificate-authority-fns.ts +++ b/backend/src/services/certificate-authority/certificate-authority-fns.ts @@ -273,6 +273,7 @@ export const getCaCertChains = async ({ certificate: caCertObj.toString("pem"), certificateChain: decryptedChain.toString("utf-8"), serialNumber: caCertObj.serialNumber, + certId: caCert.id, version: caCert.version }; }) diff --git a/backend/src/services/certificate-authority/internal/internal-certificate-authority-service.ts b/backend/src/services/certificate-authority/internal/internal-certificate-authority-service.ts index 1ef0c105b3..1ef9da04a3 100644 --- a/backend/src/services/certificate-authority/internal/internal-certificate-authority-service.ts +++ b/backend/src/services/certificate-authority/internal/internal-certificate-authority-service.ts @@ -62,6 +62,7 @@ import { TCreateCaDTO, TDeleteCaDTO, TGenerateRootCaCertificateDTO, + TGetCaCertByIdDTO, TGetCaCertDTO, TGetCaCertificateTemplatesDTO, TGetCaCertsDTO, @@ -556,6 +557,7 @@ export const internalCertificateAuthorityServiceFactory = ({ let certificate = ""; let certificateChain = ""; + let newCertId = ""; switch (ca.internalCa.type) { case InternalCaType.ROOT: { @@ -611,6 +613,8 @@ export const internalCertificateAuthorityServiceFactory = ({ tx ); + newCertId = newCaCert.id; + await internalCertificateAuthorityDAL.update( { caId: ca.id @@ -753,6 +757,8 @@ export const internalCertificateAuthorityServiceFactory = ({ tx ); + newCertId = newCaCert.id; + await internalCertificateAuthorityDAL.update( { caId: ca.id @@ -780,6 +786,7 @@ export const internalCertificateAuthorityServiceFactory = ({ certificate, certificateChain, serialNumber, + certId: newCertId, ca: { ...ca, ...ca.internalCa @@ -854,6 +861,7 @@ export const internalCertificateAuthorityServiceFactory = ({ certificate: caCert, certificateChain: caCertChain, serialNumber, + certId: ca.internalCa.activeCaCertId, ca: expandInternalCa(ca) }; }; @@ -891,6 +899,61 @@ export const internalCertificateAuthorityServiceFactory = ({ return caCertObj; }; + const getCaCertByIdWithAuth = async ({ + caId, + certId, + actorId, + actorAuthMethod, + actor, + actorOrgId + }: TGetCaCertByIdDTO) => { + const ca = await certificateAuthorityDAL.findByIdWithAssociatedCa(caId); + if (!ca.internalCa) throw new NotFoundError({ message: `CA with ID '${caId}' not found` }); + + const { permission } = await permissionService.getProjectPermission({ + actor, + actorId, + projectId: ca.projectId, + actorAuthMethod, + actorOrgId, + actionProjectType: ActionProjectType.CertificateManager + }); + + ForbiddenError.from(permission).throwUnlessCan( + ProjectPermissionCertificateAuthorityActions.Read, + subject(ProjectPermissionSub.CertificateAuthorities, { name: ca.name }) + ); + + const caCert = await certificateAuthorityCertDAL.findOne({ + caId, + id: certId + }); + + if (!caCert) { + throw new NotFoundError({ message: `Certificate with ID '${certId}' not found for CA with ID '${caId}'` }); + } + + const { + caCert: certificate, + caCertChain: certificateChain, + serialNumber + } = await getCaCertChain({ + caCertId: certId, + certificateAuthorityDAL, + certificateAuthorityCertDAL, + projectDAL, + kmsService + }); + + return { + certificate, + certificateChain, + serialNumber, + certId, + ca: expandInternalCa(ca) + }; + }; + /** * Issue certificate to be imported back in for intermediate CA */ @@ -1296,10 +1359,16 @@ export const internalCertificateAuthorityServiceFactory = ({ parentCaId }); + const updatedCa = await certificateAuthorityDAL.findByIdWithAssociatedCa(caId, tx); + if (!updatedCa.internalCa?.activeCaCertId) { + throw new Error("Failed to get certificate ID after import"); + } + return { certificate: signedResult.certificate, certificateChain: signedResult.certificateChain, - serialNumber: signedResult.serialNumber + serialNumber: signedResult.serialNumber, + certId: updatedCa.internalCa.activeCaCertId }; }; @@ -2275,6 +2344,7 @@ export const internalCertificateAuthorityServiceFactory = ({ certificate: cert.toString("pem"), certificateChain: "", serialNumber, + certId: caCert.id, ca: expandInternalCa(updatedCa) }; }); @@ -2290,6 +2360,7 @@ export const internalCertificateAuthorityServiceFactory = ({ getCaCerts, getCaCert, getCaCertById, + getCaCertByIdWithAuth, signIntermediate, importCertToCa, generateIntermediateCaCertificate, diff --git a/backend/src/services/certificate-authority/internal/internal-certificate-authority-types.ts b/backend/src/services/certificate-authority/internal/internal-certificate-authority-types.ts index d8084e5477..4adb16a063 100644 --- a/backend/src/services/certificate-authority/internal/internal-certificate-authority-types.ts +++ b/backend/src/services/certificate-authority/internal/internal-certificate-authority-types.ts @@ -114,6 +114,11 @@ export type TGetCaCertDTO = { caId: string; } & Omit; +export type TGetCaCertByIdDTO = { + caId: string; + certId: string; +} & Omit; + export type TSignIntermediateDTO = { isInternal?: boolean; } & (