Replace custom pkcs7 fns with module

This commit is contained in:
Tuan Dang
2024-08-25 20:21:53 -07:00
parent 1317266415
commit f560534493
9 changed files with 73 additions and 118 deletions

View File

@@ -692,7 +692,7 @@ export const registerCaRouter = async (server: FastifyZodProvider) => {
}); });
return { return {
certificate, certificate: certificate.toString("pem"),
certificateChain, certificateChain,
issuingCaCertificate, issuingCaCertificate,
serialNumber serialNumber

View File

@@ -232,7 +232,7 @@ export const registerCertRouter = async (server: FastifyZodProvider) => {
}); });
return { return {
certificate, certificate: certificate.toString("pem"),
certificateChain, certificateChain,
issuingCaCertificate, issuingCaCertificate,
serialNumber serialNumber

View File

@@ -1553,8 +1553,10 @@ export const certificateAuthorityServiceFactory = ({
}); });
return { return {
certificate: leafCert.toString("pem"), certificate: leafCert,
rawCertificate: leafCert.rawData, // certificate: leafCert.toString("pem"),
// certificateObj: leafCert,
// rawCertificate: leafCert.rawData,
certificateChain: `${issuingCaCertificate}\n${caCertChain}`.trim(), certificateChain: `${issuingCaCertificate}\n${caCertChain}`.trim(),
issuingCaCertificate, issuingCaCertificate,
serialNumber, serialNumber,

View File

@@ -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;
};

View File

@@ -2,7 +2,6 @@ import * as x509 from "@peculiar/x509";
import { BadRequestError, NotFoundError, 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 { getCaCertChain, getCaCertChains } from "../certificate-authority/certificate-authority-fns"; 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 { 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 { convertRawCertsToPkcs7 } from "./certificate-est-fns";
type TCertificateEstServiceFactoryDep = { type TCertificateEstServiceFactoryDep = {
certificateAuthorityService: Pick<TCertificateAuthorityServiceFactory, "signCertFromCa">; certificateAuthorityService: Pick<TCertificateAuthorityServiceFactory, "signCertFromCa">;
@@ -60,7 +58,7 @@ export const certificateEstServiceFactory = ({
/-----BEGIN CERTIFICATE-----[\s\S]+?-----END CERTIFICATE-----/g /-----BEGIN CERTIFICATE-----[\s\S]+?-----END CERTIFICATE-----/g
)?.[0]; )?.[0];
if (!sslClientCert || !leafCertificate) { if (!leafCertificate) {
throw new UnauthorizedError({ message: "Missing client certificate" }); throw new UnauthorizedError({ message: "Missing client certificate" });
} }
@@ -82,43 +80,27 @@ export const certificateEstServiceFactory = ({
kmsService kmsService
}); });
const parsedChains = caCertChains const caChainBuilders = caCertChains.map((chain) => {
// we need the full chain from the CA certificate to the root const caCert = new x509.X509Certificate(chain.certificate);
.map((chain) => chain.certificate + chain.certificateChain) const caChain =
.map( chain.certificateChain
(certificateChain) => .match(/-----BEGIN CERTIFICATE-----[\s\S]+?-----END CERTIFICATE-----/g)
certificateChain.match(/-----BEGIN CERTIFICATE-----[\s\S]+?-----END CERTIFICATE-----/g)?.map((certEntry) => { ?.map((c) => new x509.X509Certificate(c)) || [];
const processedBody = certEntry return new x509.X509ChainBuilder({
.replace("-----BEGIN CERTIFICATE-----", "") certificates: [caCert, ...caChain]
.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 certValidityAgainstChains = await Promise.all( const verifiedChains = await Promise.all(
parsedChains.map(async (chain) => { caChainBuilders.map(async (caChainBuilder) => {
if (!chain) { const chainItems = await caChainBuilder.build(cert);
return false; return chainItems.length === caChainBuilder.certificates.length + 1;
}
return checkCertValidityAgainstChain(cert, chain);
}) })
); );
if (certValidityAgainstChains.every((isCertValid) => !isCertValid)) { if (!verifiedChains.some(Boolean)) {
throw new BadRequestError({ 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, isInternal: true,
certificateTemplateId, certificateTemplateId,
csr csr
}); });
return convertRawCertsToPkcs7([rawCertificate]); const certs = new x509.X509Certificates([certificate]);
return certs.export("base64");
}; };
const simpleEnroll = async ({ 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( const leafCertificate = decodeURIComponent(sslClientCert).match(
/-----BEGIN CERTIFICATE-----[\s\S]+?-----END CERTIFICATE-----/g /-----BEGIN CERTIFICATE-----[\s\S]+?-----END CERTIFICATE-----/g
)?.[0]; )?.[0];
if (!sslClientCert || !leafCertificate) { if (!leafCertificate) {
throw new UnauthorizedError({ message: "Missing client certificate" }); throw new BadRequestError({ message: "Missing client certificate" });
} }
const clientCertBody = leafCertificate const clientCertBody = leafCertificate
@@ -197,41 +192,25 @@ export const certificateEstServiceFactory = ({
.replace(/ /g, "") .replace(/ /g, "")
.trim(); .trim();
const chainCerts = estConfig.caChain const certObj = new x509.X509Certificate(clientCertBody);
.match(/-----BEGIN CERTIFICATE-----[\s\S]+?-----END CERTIFICATE-----/g) const chainItems = await caChain.build(certObj);
?.map((cert) => {
const processedBody = cert
.replace("-----BEGIN CERTIFICATE-----", "")
.replace("-----END CERTIFICATE-----", "")
.replace(/\n/g, "")
.replace(/ /g, "")
.trim();
const certificateBuffer = Buffer.from(processedBody, "base64"); if (chainItems.length !== caCerts.length + 1) throw new BadRequestError({ message: "Invalid certificate chain" });
return new x509.X509Certificate(certificateBuffer);
});
if (!chainCerts) { const { certificate } = await certificateAuthorityService.signCertFromCa({
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({
isInternal: true, isInternal: true,
certificateTemplateId, certificateTemplateId,
csr 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 getCaCerts = async ({ certificateTemplateId }: { certificateTemplateId: string }) => {
const certTemplate = await certificateTemplateDAL.findById(certificateTemplateId); const certTemplate = await certificateTemplateDAL.findById(certificateTemplateId);
if (!certTemplate) { if (!certTemplate) {
@@ -255,12 +234,22 @@ export const certificateEstServiceFactory = ({
kmsService kmsService
}); });
const caCertRaw = convertCertPemToRaw(caCert); const certificates = caCertChain
const caParentsRaw = caCertChain
.match(/-----BEGIN CERTIFICATE-----[\s\S]+?-----END CERTIFICATE-----/g) .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 { return {

View File

@@ -24,26 +24,3 @@ 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;
};

View File

@@ -7,6 +7,7 @@ services:
restart: always restart: always
ports: ports:
- 8080:80 - 8080:80
- 8443:443
volumes: volumes:
- ./nginx/default.dev.conf:/etc/nginx/conf.d/default.conf:ro - ./nginx/default.dev.conf:/etc/nginx/conf.d/default.conf:ro
depends_on: depends_on:

View File

@@ -3,6 +3,8 @@ import { Controller, useForm } from "react-hook-form";
import { zodResolver } from "@hookform/resolvers/zod"; import { zodResolver } from "@hookform/resolvers/zod";
import z from "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 { createNotification } from "@app/components/notifications";
import { import {
Button, Button,
@@ -178,6 +180,7 @@ export const CertificateTemplateEnrollmentModal = ({ popUp, handlePopUpToggle }:
type={isPassphraseFocused ? "text" : "password"} type={isPassphraseFocused ? "text" : "password"}
onFocus={() => setIsPassphraseFocused.on()} onFocus={() => setIsPassphraseFocused.on()}
onBlur={() => setIsPassphraseFocused.off()} onBlur={() => setIsPassphraseFocused.off()}
// rightIcon={<FontAwesomeIcon icon={faEyeSlash} />}
/> />
</FormControl> </FormControl>
)} )}
@@ -193,7 +196,7 @@ export const CertificateTemplateEnrollmentModal = ({ popUp, handlePopUpToggle }:
onCheckedChange={(value) => field.onChange(value)} onCheckedChange={(value) => field.onChange(value)}
isChecked={field.value} isChecked={field.value}
> >
<p className="ml-1 w-full">Enabled</p> <p className="ml-1 w-full">EST Enabled</p>
</Switch> </Switch>
</FormControl> </FormControl>
); );

View File

@@ -1,5 +1,8 @@
server { server {
listen 80; listen 80;
large_client_header_buffers 8 128k;
client_header_buffer_size 128k;
location /api { location /api {
proxy_set_header X-Real-RIP $remote_addr; proxy_set_header X-Real-RIP $remote_addr;
@@ -23,6 +26,8 @@ server {
proxy_set_header X-NginX-Proxy true; 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 $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_pass http://backend:4000;
proxy_redirect off; proxy_redirect off;