From b7caff88cf84654caccd07db5d7710c0936ae906 Mon Sep 17 00:00:00 2001 From: Sheen Capadngan Date: Thu, 22 Aug 2024 15:39:53 +0800 Subject: [PATCH] feat: finished up EST cacerts --- .../routes/est/certificate-est-router.ts | 34 ++++++++++++- .../certificate-est/certificate-est-fns.ts | 26 ++-------- .../certificate-est-service.ts | 50 ++++++++++++++++--- .../services/certificate/certificate-fns.ts | 23 +++++++++ 4 files changed, 100 insertions(+), 33 deletions(-) diff --git a/backend/src/server/routes/est/certificate-est-router.ts b/backend/src/server/routes/est/certificate-est-router.ts index 50846f4e5..f1f242138 100644 --- a/backend/src/server/routes/est/certificate-est-router.ts +++ b/backend/src/server/routes/est/certificate-est-router.ts @@ -2,7 +2,7 @@ import bcrypt from "bcrypt"; import { z } from "zod"; import { BadRequestError, UnauthorizedError } from "@app/lib/errors"; -import { writeLimit } from "@app/server/config/rateLimiter"; +import { readLimit, writeLimit } from "@app/server/config/rateLimiter"; export const registerCertificateEstRouter = async (server: FastifyZodProvider) => { // add support for CSR bodies @@ -18,6 +18,13 @@ export const registerCertificateEstRouter = async (server: FastifyZodProvider) = // Authenticate EST client using Passphrase server.addHook("onRequest", async (req, res) => { const { authorization } = req.headers; + const urlFragments = req.url.split("/"); + + // cacerts endpoint should not have any authentication + if (urlFragments[urlFragments.length - 1] === "cacerts") { + return; + } + if (!authorization) { const wwwAuthenticateHeader = "WWW-Authenticate"; const errAuthRequired = "Authentication required"; @@ -36,7 +43,6 @@ export const registerCertificateEstRouter = async (server: FastifyZodProvider) = return; } - const urlFragments = req.url.split("/"); const certificateTemplateId = urlFragments.slice(-2)[0]; const estConfig = await server.services.certificateTemplate.getEstConfiguration({ isInternal: true, @@ -124,4 +130,28 @@ export const registerCertificateEstRouter = async (server: FastifyZodProvider) = }); } }); + + server.route({ + method: "GET", + url: "/:certificateTemplateId/cacerts", + config: { + rateLimit: readLimit + }, + schema: { + params: z.object({ + certificateTemplateId: z.string().min(1) + }), + response: { + 200: z.string() + } + }, + handler: async (req, res) => { + void res.header("Content-Type", "application/pkcs7-mime; smime-type=certs-only"); + void res.header("Content-Transfer-Encoding", "base64"); + + return server.services.certificateEst.getCaCerts({ + certificateTemplateId: req.params.certificateTemplateId + }); + } + }); }; diff --git a/backend/src/services/certificate-est/certificate-est-fns.ts b/backend/src/services/certificate-est/certificate-est-fns.ts index 2444e055f..91e72e840 100644 --- a/backend/src/services/certificate-est/certificate-est-fns.ts +++ b/backend/src/services/certificate-est/certificate-est-fns.ts @@ -1,13 +1,12 @@ -import { X509Certificate } from "@peculiar/x509"; import { Certificate, ContentInfo, EncapsulatedContentInfo, SignedData } from "pkijs"; -export const convertRawCertToPkcs7 = (rawCertificate: ArrayBuffer) => { - const cert = Certificate.fromBER(rawCertificate); +export const convertRawCertsToPkcs7 = (rawCertificate: ArrayBuffer[]) => { + const certs = rawCertificate.map((rawCert) => Certificate.fromBER(rawCert)); const cmsSigned = new SignedData({ encapContentInfo: new EncapsulatedContentInfo({ eContentType: "1.2.840.113549.1.7.1" // not encrypted and not compressed data }), - certificates: [cert] + certificates: certs }); const cmsContent = new ContentInfo({ @@ -21,22 +20,3 @@ export const convertRawCertToPkcs7 = (rawCertificate: ArrayBuffer) => { return base64Pkcs7; }; - -export const checkCertValidityAgainstChain = async (cert: X509Certificate, chainCerts: X509Certificate[]) => { - let isSslClientCertValid = true; - let certToVerify = cert; - - for await (const issuerCert of chainCerts) { - if ( - await certToVerify.verify({ - publicKey: issuerCert.publicKey - }) - ) { - certToVerify = issuerCert; // Move to the next certificate in the chain - } else { - isSslClientCertValid = false; - } - } - - return isSslClientCertValid; -}; diff --git a/backend/src/services/certificate-est/certificate-est-service.ts b/backend/src/services/certificate-est/certificate-est-service.ts index d2c3f6d3f..1197a0a2c 100644 --- a/backend/src/services/certificate-est/certificate-est-service.ts +++ b/backend/src/services/certificate-est/certificate-est-service.ts @@ -1,23 +1,24 @@ import * as x509 from "@peculiar/x509"; -import { BadRequestError, UnauthorizedError } from "@app/lib/errors"; +import { BadRequestError, NotFoundError, UnauthorizedError } from "@app/lib/errors"; +import { checkCertValidityAgainstChain, convertCertPemToRaw } from "../certificate/certificate-fns"; import { TCertificateAuthorityCertDALFactory } from "../certificate-authority/certificate-authority-cert-dal"; import { TCertificateAuthorityDALFactory } from "../certificate-authority/certificate-authority-dal"; -import { getCaCertChains } from "../certificate-authority/certificate-authority-fns"; +import { getCaCertChain, getCaCertChains } from "../certificate-authority/certificate-authority-fns"; import { TCertificateAuthorityServiceFactory } from "../certificate-authority/certificate-authority-service"; import { TCertificateTemplateDALFactory } from "../certificate-template/certificate-template-dal"; import { TCertificateTemplateServiceFactory } from "../certificate-template/certificate-template-service"; import { TKmsServiceFactory } from "../kms/kms-service"; import { TProjectDALFactory } from "../project/project-dal"; -import { checkCertValidityAgainstChain, convertRawCertToPkcs7 } from "./certificate-est-fns"; +import { convertRawCertsToPkcs7 } from "./certificate-est-fns"; type TCertificateEstServiceFactoryDep = { certificateAuthorityService: Pick; - certificateTemplateService: Pick; + certificateTemplateService: Pick; certificateTemplateDAL: Pick; certificateAuthorityDAL: Pick; - certificateAuthorityCertDAL: Pick; + certificateAuthorityCertDAL: Pick; projectDAL: Pick; kmsService: Pick; }; @@ -155,7 +156,7 @@ export const certificateEstServiceFactory = ({ csr }); - return convertRawCertToPkcs7(rawCertificate); + return convertRawCertsToPkcs7([rawCertificate]); }; const simpleEnroll = async ({ @@ -228,10 +229,43 @@ export const certificateEstServiceFactory = ({ csr }); - return convertRawCertToPkcs7(rawCertificate); + return convertRawCertsToPkcs7([rawCertificate]); }; + + const getCaCerts = async ({ certificateTemplateId }: { certificateTemplateId: string }) => { + const certTemplate = await certificateTemplateDAL.findById(certificateTemplateId); + if (!certTemplate) { + throw new NotFoundError({ + message: "Certificate template not found" + }); + } + + const ca = await certificateAuthorityDAL.findById(certTemplate.caId); + if (!ca) { + throw new NotFoundError({ + message: "Certificate Authority not found" + }); + } + + const { caCert, caCertChain } = await getCaCertChain({ + caCertId: ca.activeCaCertId as string, + certificateAuthorityDAL, + certificateAuthorityCertDAL, + projectDAL, + kmsService + }); + + const caCertRaw = convertCertPemToRaw(caCert); + const caParentsRaw = caCertChain + .match(/-----BEGIN CERTIFICATE-----[\s\S]+?-----END CERTIFICATE-----/g) + ?.map(convertCertPemToRaw); + + return convertRawCertsToPkcs7([caCertRaw, ...(caParentsRaw ?? [])]); + }; + return { simpleEnroll, - simpleReenroll + simpleReenroll, + getCaCerts }; }; diff --git a/backend/src/services/certificate/certificate-fns.ts b/backend/src/services/certificate/certificate-fns.ts index dfbd50551..c880fae86 100644 --- a/backend/src/services/certificate/certificate-fns.ts +++ b/backend/src/services/certificate/certificate-fns.ts @@ -24,3 +24,26 @@ export const revocationReasonToCrlCode = (crlReason: CrlReason) => { return x509.X509CrlReason.unspecified; } }; + +export const convertCertPemToRaw = (certPem: string) => { + return new x509.X509Certificate(certPem).rawData; +}; + +export const checkCertValidityAgainstChain = async (cert: x509.X509Certificate, chainCerts: x509.X509Certificate[]) => { + let isSslClientCertValid = true; + let certToVerify = cert; + + for await (const issuerCert of chainCerts) { + if ( + await certToVerify.verify({ + publicKey: issuerCert.publicKey + }) + ) { + certToVerify = issuerCert; // Move to the next certificate in the chain + } else { + isSslClientCertValid = false; + } + } + + return isSslClientCertValid; +};