feat: finished up EST cacerts

This commit is contained in:
Sheen Capadngan
2024-08-22 15:39:53 +08:00
parent 760a1e917a
commit b7caff88cf
4 changed files with 100 additions and 33 deletions
@@ -2,7 +2,7 @@ import bcrypt from "bcrypt";
import { z } from "zod"; import { z } from "zod";
import { BadRequestError, UnauthorizedError } from "@app/lib/errors"; 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) => { export const registerCertificateEstRouter = async (server: FastifyZodProvider) => {
// add support for CSR bodies // add support for CSR bodies
@@ -18,6 +18,13 @@ export const registerCertificateEstRouter = async (server: FastifyZodProvider) =
// Authenticate EST client using Passphrase // Authenticate EST client using Passphrase
server.addHook("onRequest", async (req, res) => { server.addHook("onRequest", async (req, res) => {
const { authorization } = req.headers; 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) { if (!authorization) {
const wwwAuthenticateHeader = "WWW-Authenticate"; const wwwAuthenticateHeader = "WWW-Authenticate";
const errAuthRequired = "Authentication required"; const errAuthRequired = "Authentication required";
@@ -36,7 +43,6 @@ export const registerCertificateEstRouter = async (server: FastifyZodProvider) =
return; return;
} }
const urlFragments = req.url.split("/");
const certificateTemplateId = urlFragments.slice(-2)[0]; const certificateTemplateId = urlFragments.slice(-2)[0];
const estConfig = await server.services.certificateTemplate.getEstConfiguration({ const estConfig = await server.services.certificateTemplate.getEstConfiguration({
isInternal: true, 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
});
}
});
}; };
@@ -1,13 +1,12 @@
import { X509Certificate } from "@peculiar/x509";
import { Certificate, ContentInfo, EncapsulatedContentInfo, SignedData } from "pkijs"; import { Certificate, ContentInfo, EncapsulatedContentInfo, SignedData } from "pkijs";
export const convertRawCertToPkcs7 = (rawCertificate: ArrayBuffer) => { export const convertRawCertsToPkcs7 = (rawCertificate: ArrayBuffer[]) => {
const cert = Certificate.fromBER(rawCertificate); const certs = rawCertificate.map((rawCert) => Certificate.fromBER(rawCert));
const cmsSigned = new SignedData({ const cmsSigned = new SignedData({
encapContentInfo: new EncapsulatedContentInfo({ encapContentInfo: new EncapsulatedContentInfo({
eContentType: "1.2.840.113549.1.7.1" // not encrypted and not compressed data eContentType: "1.2.840.113549.1.7.1" // not encrypted and not compressed data
}), }),
certificates: [cert] certificates: certs
}); });
const cmsContent = new ContentInfo({ const cmsContent = new ContentInfo({
@@ -21,22 +20,3 @@ export const convertRawCertToPkcs7 = (rawCertificate: ArrayBuffer) => {
return base64Pkcs7; 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;
};
@@ -1,23 +1,24 @@
import * as x509 from "@peculiar/x509"; 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 { TCertificateAuthorityCertDALFactory } from "../certificate-authority/certificate-authority-cert-dal";
import { TCertificateAuthorityDALFactory } from "../certificate-authority/certificate-authority-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 { TCertificateAuthorityServiceFactory } from "../certificate-authority/certificate-authority-service";
import { TCertificateTemplateDALFactory } from "../certificate-template/certificate-template-dal"; import { TCertificateTemplateDALFactory } from "../certificate-template/certificate-template-dal";
import { TCertificateTemplateServiceFactory } from "../certificate-template/certificate-template-service"; import { TCertificateTemplateServiceFactory } from "../certificate-template/certificate-template-service";
import { TKmsServiceFactory } from "../kms/kms-service"; import { TKmsServiceFactory } from "../kms/kms-service";
import { TProjectDALFactory } from "../project/project-dal"; import { TProjectDALFactory } from "../project/project-dal";
import { checkCertValidityAgainstChain, convertRawCertToPkcs7 } from "./certificate-est-fns"; import { convertRawCertsToPkcs7 } from "./certificate-est-fns";
type TCertificateEstServiceFactoryDep = { type TCertificateEstServiceFactoryDep = {
certificateAuthorityService: Pick<TCertificateAuthorityServiceFactory, "signCertFromCa">; certificateAuthorityService: Pick<TCertificateAuthorityServiceFactory, "signCertFromCa">;
certificateTemplateService: Pick<TCertificateTemplateServiceFactory, "getEstConfiguration" | "getCertTemplate">; certificateTemplateService: Pick<TCertificateTemplateServiceFactory, "getEstConfiguration">;
certificateTemplateDAL: Pick<TCertificateTemplateDALFactory, "findById">; certificateTemplateDAL: Pick<TCertificateTemplateDALFactory, "findById">;
certificateAuthorityDAL: Pick<TCertificateAuthorityDALFactory, "findById">; certificateAuthorityDAL: Pick<TCertificateAuthorityDALFactory, "findById">;
certificateAuthorityCertDAL: Pick<TCertificateAuthorityCertDALFactory, "find">; certificateAuthorityCertDAL: Pick<TCertificateAuthorityCertDALFactory, "find" | "findById">;
projectDAL: Pick<TProjectDALFactory, "findOne" | "updateById" | "transaction">; projectDAL: Pick<TProjectDALFactory, "findOne" | "updateById" | "transaction">;
kmsService: Pick<TKmsServiceFactory, "decryptWithKmsKey" | "generateKmsKey">; kmsService: Pick<TKmsServiceFactory, "decryptWithKmsKey" | "generateKmsKey">;
}; };
@@ -155,7 +156,7 @@ export const certificateEstServiceFactory = ({
csr csr
}); });
return convertRawCertToPkcs7(rawCertificate); return convertRawCertsToPkcs7([rawCertificate]);
}; };
const simpleEnroll = async ({ const simpleEnroll = async ({
@@ -228,10 +229,43 @@ export const certificateEstServiceFactory = ({
csr 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 { return {
simpleEnroll, simpleEnroll,
simpleReenroll simpleReenroll,
getCaCerts
}; };
}; };
@@ -24,3 +24,26 @@ export const revocationReasonToCrlCode = (crlReason: CrlReason) => {
return x509.X509CrlReason.unspecified; 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;
};