diff --git a/backend/src/server/routes/v1/certificate-authority-router.ts b/backend/src/server/routes/v1/certificate-authority-router.ts index d009fc6248..1fd9cabbbf 100644 --- a/backend/src/server/routes/v1/certificate-authority-router.ts +++ b/backend/src/server/routes/v1/certificate-authority-router.ts @@ -692,7 +692,7 @@ export const registerCaRouter = async (server: FastifyZodProvider) => { }); return { - certificate, + certificate: certificate.toString("pem"), certificateChain, issuingCaCertificate, serialNumber diff --git a/backend/src/server/routes/v1/certificate-router.ts b/backend/src/server/routes/v1/certificate-router.ts index 1be345a8a5..91ae85982d 100644 --- a/backend/src/server/routes/v1/certificate-router.ts +++ b/backend/src/server/routes/v1/certificate-router.ts @@ -232,7 +232,7 @@ export const registerCertRouter = async (server: FastifyZodProvider) => { }); return { - certificate, + certificate: certificate.toString("pem"), certificateChain, issuingCaCertificate, serialNumber diff --git a/backend/src/services/certificate-authority/certificate-authority-service.ts b/backend/src/services/certificate-authority/certificate-authority-service.ts index 758ac3d652..4a72cefaa7 100644 --- a/backend/src/services/certificate-authority/certificate-authority-service.ts +++ b/backend/src/services/certificate-authority/certificate-authority-service.ts @@ -1553,8 +1553,10 @@ export const certificateAuthorityServiceFactory = ({ }); return { - certificate: leafCert.toString("pem"), - rawCertificate: leafCert.rawData, + certificate: leafCert, + // certificate: leafCert.toString("pem"), + // certificateObj: leafCert, + // rawCertificate: leafCert.rawData, certificateChain: `${issuingCaCertificate}\n${caCertChain}`.trim(), issuingCaCertificate, serialNumber, diff --git a/backend/src/services/certificate-est/certificate-est-fns.ts b/backend/src/services/certificate-est/certificate-est-fns.ts deleted file mode 100644 index 91e72e8405..0000000000 --- a/backend/src/services/certificate-est/certificate-est-fns.ts +++ /dev/null @@ -1,22 +0,0 @@ -import { Certificate, ContentInfo, EncapsulatedContentInfo, SignedData } from "pkijs"; - -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: certs - }); - - const cmsContent = new ContentInfo({ - contentType: "1.2.840.113549.1.7.2", // SignedData - // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment - content: cmsSigned.toSchema() - }); - - const derBuffer = cmsContent.toSchema().toBER(false); - const base64Pkcs7 = Buffer.from(derBuffer).toString("base64"); - - return base64Pkcs7; -}; diff --git a/backend/src/services/certificate-est/certificate-est-service.ts b/backend/src/services/certificate-est/certificate-est-service.ts index 1197a0a2cd..1634d09960 100644 --- a/backend/src/services/certificate-est/certificate-est-service.ts +++ b/backend/src/services/certificate-est/certificate-est-service.ts @@ -2,7 +2,6 @@ import * as x509 from "@peculiar/x509"; 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 { getCaCertChain, getCaCertChains } from "../certificate-authority/certificate-authority-fns"; @@ -11,7 +10,6 @@ import { TCertificateTemplateDALFactory } from "../certificate-template/certific import { TCertificateTemplateServiceFactory } from "../certificate-template/certificate-template-service"; import { TKmsServiceFactory } from "../kms/kms-service"; import { TProjectDALFactory } from "../project/project-dal"; -import { convertRawCertsToPkcs7 } from "./certificate-est-fns"; type TCertificateEstServiceFactoryDep = { certificateAuthorityService: Pick; @@ -60,7 +58,7 @@ export const certificateEstServiceFactory = ({ /-----BEGIN CERTIFICATE-----[\s\S]+?-----END CERTIFICATE-----/g )?.[0]; - if (!sslClientCert || !leafCertificate) { + if (!leafCertificate) { throw new UnauthorizedError({ message: "Missing client certificate" }); } @@ -82,43 +80,27 @@ export const certificateEstServiceFactory = ({ kmsService }); - const parsedChains = caCertChains - // we need the full chain from the CA certificate to the root - .map((chain) => chain.certificate + chain.certificateChain) - .map( - (certificateChain) => - certificateChain.match(/-----BEGIN CERTIFICATE-----[\s\S]+?-----END CERTIFICATE-----/g)?.map((certEntry) => { - const processedBody = certEntry - .replace("-----BEGIN CERTIFICATE-----", "") - .replace("-----END CERTIFICATE-----", "") - .replace(/\n/g, "") - .replace(/ /g, "") - .trim(); - - const certificateBuffer = Buffer.from(processedBody, "base64"); - return new x509.X509Certificate(certificateBuffer); - }) - ); - - if (!parsedChains || !parsedChains.length) { - throw new BadRequestError({ - message: "Error parsing CA chain" + const caChainBuilders = caCertChains.map((chain) => { + const caCert = new x509.X509Certificate(chain.certificate); + const caChain = + chain.certificateChain + .match(/-----BEGIN CERTIFICATE-----[\s\S]+?-----END CERTIFICATE-----/g) + ?.map((c) => new x509.X509Certificate(c)) || []; + return new x509.X509ChainBuilder({ + certificates: [caCert, ...caChain] }); - } + }); - const certValidityAgainstChains = await Promise.all( - parsedChains.map(async (chain) => { - if (!chain) { - return false; - } - - return checkCertValidityAgainstChain(cert, chain); + const verifiedChains = await Promise.all( + caChainBuilders.map(async (caChainBuilder) => { + const chainItems = await caChainBuilder.build(cert); + return chainItems.length === caChainBuilder.certificates.length + 1; }) ); - if (certValidityAgainstChains.every((isCertValid) => !isCertValid)) { + if (!verifiedChains.some(Boolean)) { throw new BadRequestError({ - message: "Invalid client certificate" + message: "Invalid client certificate: unable to build a valid certificate chain" }); } @@ -150,13 +132,14 @@ export const certificateEstServiceFactory = ({ }); } - const { rawCertificate } = await certificateAuthorityService.signCertFromCa({ + const { certificate } = await certificateAuthorityService.signCertFromCa({ isInternal: true, certificateTemplateId, csr }); - return convertRawCertsToPkcs7([rawCertificate]); + const certs = new x509.X509Certificates([certificate]); + return certs.export("base64"); }; const simpleEnroll = async ({ @@ -182,12 +165,24 @@ export const certificateEstServiceFactory = ({ }); } + const caCerts = estConfig.caChain + .match(/-----BEGIN CERTIFICATE-----[\s\S]+?-----END CERTIFICATE-----/g) + ?.map((cert) => { + return new x509.X509Certificate(cert); + }); + + if (!caCerts) throw new BadRequestError({ message: "Failed to parse certificate chain" }); + + const caChain = new x509.X509ChainBuilder({ + certificates: caCerts + }); + const leafCertificate = decodeURIComponent(sslClientCert).match( /-----BEGIN CERTIFICATE-----[\s\S]+?-----END CERTIFICATE-----/g )?.[0]; - if (!sslClientCert || !leafCertificate) { - throw new UnauthorizedError({ message: "Missing client certificate" }); + if (!leafCertificate) { + throw new BadRequestError({ message: "Missing client certificate" }); } const clientCertBody = leafCertificate @@ -197,41 +192,25 @@ export const certificateEstServiceFactory = ({ .replace(/ /g, "") .trim(); - const chainCerts = estConfig.caChain - .match(/-----BEGIN CERTIFICATE-----[\s\S]+?-----END CERTIFICATE-----/g) - ?.map((cert) => { - const processedBody = cert - .replace("-----BEGIN CERTIFICATE-----", "") - .replace("-----END CERTIFICATE-----", "") - .replace(/\n/g, "") - .replace(/ /g, "") - .trim(); + const certObj = new x509.X509Certificate(clientCertBody); + const chainItems = await caChain.build(certObj); - const certificateBuffer = Buffer.from(processedBody, "base64"); - return new x509.X509Certificate(certificateBuffer); - }); + if (chainItems.length !== caCerts.length + 1) throw new BadRequestError({ message: "Invalid certificate chain" }); - if (!chainCerts) { - throw new BadRequestError({ message: "Failed to parse certificate chain" }); - } - - const cert = new x509.X509Certificate(clientCertBody); - - if (!(await checkCertValidityAgainstChain(cert, chainCerts))) { - throw new UnauthorizedError({ - message: "Invalid client certificate" - }); - } - - const { rawCertificate } = await certificateAuthorityService.signCertFromCa({ + const { certificate } = await certificateAuthorityService.signCertFromCa({ isInternal: true, certificateTemplateId, csr }); - return convertRawCertsToPkcs7([rawCertificate]); + const certs = new x509.X509Certificates([certificate]); + return certs.export("base64"); }; + /** + * Return the CA certificate and CA certificate chain for the CA bound to + * the certificate template with id [certificateTemplateId] as part of EST protocol + */ const getCaCerts = async ({ certificateTemplateId }: { certificateTemplateId: string }) => { const certTemplate = await certificateTemplateDAL.findById(certificateTemplateId); if (!certTemplate) { @@ -255,12 +234,22 @@ export const certificateEstServiceFactory = ({ kmsService }); - const caCertRaw = convertCertPemToRaw(caCert); - const caParentsRaw = caCertChain + const certificates = caCertChain .match(/-----BEGIN CERTIFICATE-----[\s\S]+?-----END CERTIFICATE-----/g) - ?.map(convertCertPemToRaw); + ?.map((cert) => new x509.X509Certificate(cert)); - return convertRawCertsToPkcs7([caCertRaw, ...(caParentsRaw ?? [])]); + if (!certificates) throw new BadRequestError({ message: "Failed to parse certificate chain" }); + + const chain = new x509.X509ChainBuilder({ + certificates + }); + + const chainItems = await chain.build(new x509.X509Certificate(caCert)); + + if (chainItems.length !== certificates.length + 1) + throw new BadRequestError({ message: "Invalid certificate chain" }); + + return chainItems.export("base64"); }; return { diff --git a/backend/src/services/certificate/certificate-fns.ts b/backend/src/services/certificate/certificate-fns.ts index c880fae868..dfbd50551c 100644 --- a/backend/src/services/certificate/certificate-fns.ts +++ b/backend/src/services/certificate/certificate-fns.ts @@ -24,26 +24,3 @@ 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; -}; diff --git a/docker-compose.dev.yml b/docker-compose.dev.yml index 422fe43f3c..9e56ae5891 100644 --- a/docker-compose.dev.yml +++ b/docker-compose.dev.yml @@ -7,6 +7,7 @@ services: restart: always ports: - 8080:80 + - 8443:443 volumes: - ./nginx/default.dev.conf:/etc/nginx/conf.d/default.conf:ro depends_on: diff --git a/frontend/src/views/Project/CertificatesPage/components/CertificatesTab/components/CertificateTemplateEnrollmentModal.tsx b/frontend/src/views/Project/CertificatesPage/components/CertificatesTab/components/CertificateTemplateEnrollmentModal.tsx index 2f821ef222..54a5c5b150 100644 --- a/frontend/src/views/Project/CertificatesPage/components/CertificatesTab/components/CertificateTemplateEnrollmentModal.tsx +++ b/frontend/src/views/Project/CertificatesPage/components/CertificatesTab/components/CertificateTemplateEnrollmentModal.tsx @@ -3,6 +3,8 @@ import { Controller, useForm } from "react-hook-form"; import { zodResolver } from "@hookform/resolvers/zod"; import z from "zod"; +// import { faEye, faEyeSlash } from "@fortawesome/free-solid-svg-icons"; +// import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; import { createNotification } from "@app/components/notifications"; import { Button, @@ -178,6 +180,7 @@ export const CertificateTemplateEnrollmentModal = ({ popUp, handlePopUpToggle }: type={isPassphraseFocused ? "text" : "password"} onFocus={() => setIsPassphraseFocused.on()} onBlur={() => setIsPassphraseFocused.off()} + // rightIcon={} /> )} @@ -193,7 +196,7 @@ export const CertificateTemplateEnrollmentModal = ({ popUp, handlePopUpToggle }: onCheckedChange={(value) => field.onChange(value)} isChecked={field.value} > -

Enabled

+

EST Enabled

); diff --git a/nginx/default.dev.conf b/nginx/default.dev.conf index 4512b63110..ea637e2cc7 100644 --- a/nginx/default.dev.conf +++ b/nginx/default.dev.conf @@ -1,5 +1,8 @@ server { listen 80; + + large_client_header_buffers 8 128k; + client_header_buffer_size 128k; location /api { proxy_set_header X-Real-RIP $remote_addr; @@ -23,6 +26,8 @@ server { proxy_set_header X-NginX-Proxy true; proxy_set_header X-SSL-Client-Cert $ssl_client_escaped_cert; + # proxy_set_header X-SSL-Client-Cert $http_x_ssl_client_cert; + # proxy_pass_request_headers on; proxy_pass http://backend:4000; proxy_redirect off;