From ac26ae389310b610ae6a1d8f65960a5f6461cf19 Mon Sep 17 00:00:00 2001 From: Sheen Capadngan Date: Thu, 12 Sep 2024 23:16:49 +0800 Subject: [PATCH 1/3] misc: addressed minor cert lint issues --- backend/src/lib/api-docs/constants.ts | 4 ++ .../routes/v1/certificate-authority-router.ts | 28 ++++++++ .../certificate-authority-fns.ts | 2 +- .../certificate-authority-service.ts | 69 +++++++++++++++++-- 4 files changed, 97 insertions(+), 6 deletions(-) diff --git a/backend/src/lib/api-docs/constants.ts b/backend/src/lib/api-docs/constants.ts index 7d998f8ea..9735e3f19 100644 --- a/backend/src/lib/api-docs/constants.ts +++ b/backend/src/lib/api-docs/constants.ts @@ -1073,6 +1073,10 @@ export const CERTIFICATE_AUTHORITIES = { certificateChain: "The certificate chain of the CA", serialNumber: "The serial number of the CA certificate" }, + GET_CERT_BY_ID: { + caId: "The ID of the CA to get the certificate from", + caCertId: "The ID of the CA certificate to get" + }, GET_CA_CERTS: { caId: "The ID of the CA to get the CA certificates for", certificate: "The certificate body of the CA certificate", diff --git a/backend/src/server/routes/v1/certificate-authority-router.ts b/backend/src/server/routes/v1/certificate-authority-router.ts index 77ee70e57..94a203f97 100644 --- a/backend/src/server/routes/v1/certificate-authority-router.ts +++ b/backend/src/server/routes/v1/certificate-authority-router.ts @@ -1,3 +1,4 @@ +/* eslint-disable @typescript-eslint/no-floating-promises */ import ms from "ms"; import { z } from "zod"; @@ -139,6 +140,33 @@ export const registerCaRouter = async (server: FastifyZodProvider) => { } }); + // this endpoint will be used to serve the CA certificate when a client makes a request + // against the Authority Information Access CA Issuer URL + server.route({ + method: "GET", + url: "/:caId/certificates/:caCertId", + config: { + rateLimit: readLimit + }, + schema: { + description: "Public endpoint for fetching DER-encoded Certificate of CA", + params: z.object({ + caId: z.string().trim().describe(CERTIFICATE_AUTHORITIES.GET_CERT_BY_ID.caId), + caCertId: z.string().trim().describe(CERTIFICATE_AUTHORITIES.GET_CERT_BY_ID.caCertId) + }), + response: { + 200: z.instanceof(Buffer) + } + }, + handler: async (req, res) => { + const caCert = await server.services.certificateAuthority.getCaCertById(req.params); + + res.header("Content-Type", "application/pkix-cert"); + + return Buffer.from(caCert.rawData); + } + }); + server.route({ method: "PATCH", url: "/:caId", diff --git a/backend/src/services/certificate-authority/certificate-authority-fns.ts b/backend/src/services/certificate-authority/certificate-authority-fns.ts index 7330f029b..65ba57f07 100644 --- a/backend/src/services/certificate-authority/certificate-authority-fns.ts +++ b/backend/src/services/certificate-authority/certificate-authority-fns.ts @@ -15,7 +15,7 @@ import { /* eslint-disable no-bitwise */ export const createSerialNumber = () => { - const randomBytes = crypto.randomBytes(32); + const randomBytes = crypto.randomBytes(20); randomBytes[0] &= 0x7f; // ensure the first bit is 0 return randomBytes.toString("hex"); }; diff --git a/backend/src/services/certificate-authority/certificate-authority-service.ts b/backend/src/services/certificate-authority/certificate-authority-service.ts index 1c2a5a689..12371fd58 100644 --- a/backend/src/services/certificate-authority/certificate-authority-service.ts +++ b/backend/src/services/certificate-authority/certificate-authority-service.ts @@ -762,6 +762,39 @@ export const certificateAuthorityServiceFactory = ({ }; }; + /** + * Return CA certificate object by ID + */ + const getCaCertById = async ({ caId, caCertId }: { caId: string; caCertId: string }) => { + const caCert = await certificateAuthorityCertDAL.findOne({ + caId, + id: caCertId + }); + + if (!caCert) { + throw new NotFoundError({ message: "CA certificate not found" }); + } + + const ca = await certificateAuthorityDAL.findById(caId); + const keyId = await getProjectKmsCertificateKeyId({ + projectId: ca.projectId, + projectDAL, + kmsService + }); + + const kmsDecryptor = await kmsService.decryptWithKmsKey({ + kmsId: keyId + }); + + const decryptedCaCert = await kmsDecryptor({ + cipherTextBlob: caCert.encryptedCertificate + }); + + const caCertObj = new x509.X509Certificate(decryptedCaCert); + + return caCertObj; + }; + /** * Issue certificate to be imported back in for intermediate CA */ @@ -776,6 +809,7 @@ export const certificateAuthorityServiceFactory = ({ notAfter, maxPathLength }: TSignIntermediateDTO) => { + const appCfg = getConfig(); const ca = await certificateAuthorityDAL.findById(caId); if (!ca) throw new BadRequestError({ message: "CA not found" }); @@ -850,7 +884,7 @@ export const certificateAuthorityServiceFactory = ({ throw new BadRequestError({ message: "notAfter date is after CA certificate's notAfter date" }); } - const { caPrivateKey } = await getCaCredentials({ + const { caPrivateKey, caSecret } = await getCaCredentials({ caId: ca.id, certificateAuthorityDAL, certificateAuthoritySecretDAL, @@ -859,6 +893,11 @@ export const certificateAuthorityServiceFactory = ({ }); const serialNumber = createSerialNumber(); + + const caCrl = await certificateAuthorityCrlDAL.findOne({ caSecretId: caSecret.id }); + const distributionPointUrl = `${appCfg.SITE_URL}/api/v1/pki/crl/${caCrl.id}`; + + const caIssuerUrl = `${appCfg.SITE_URL}/api/v1/pki/ca/${ca.id}/certificates/${caCert.id}`; const intermediateCert = await x509.X509CertificateGenerator.create({ serialNumber, subject: csrObj.subject, @@ -878,7 +917,11 @@ export const certificateAuthorityServiceFactory = ({ ), new x509.BasicConstraintsExtension(true, maxPathLength === -1 ? undefined : maxPathLength, true), await x509.AuthorityKeyIdentifierExtension.create(caCertObj, false), - await x509.SubjectKeyIdentifierExtension.create(csrObj.publicKey) + await x509.SubjectKeyIdentifierExtension.create(csrObj.publicKey), + new x509.CRLDistributionPointsExtension([distributionPointUrl]), + new x509.AuthorityInfoAccessExtension({ + caIssuers: new x509.GeneralName("url", caIssuerUrl) + }) ] }); @@ -1169,13 +1212,18 @@ export const certificateAuthorityServiceFactory = ({ const appCfg = getConfig(); const distributionPointUrl = `${appCfg.SITE_URL}/api/v1/pki/crl/${caCrl.id}`; + const caIssuerUrl = `${appCfg.SITE_URL}/api/v1/pki/ca/${ca.id}/certificates/${caCert.id}`; const extensions: x509.Extension[] = [ new x509.KeyUsagesExtension(x509.KeyUsageFlags.digitalSignature | x509.KeyUsageFlags.keyEncipherment, true), new x509.BasicConstraintsExtension(false), new x509.CRLDistributionPointsExtension([distributionPointUrl]), await x509.AuthorityKeyIdentifierExtension.create(caCertObj, false), - await x509.SubjectKeyIdentifierExtension.create(csrObj.publicKey) + await x509.SubjectKeyIdentifierExtension.create(csrObj.publicKey), + new x509.AuthorityInfoAccessExtension({ + caIssuers: new x509.GeneralName("url", caIssuerUrl) + }), + new x509.CertificatePolicyExtension(["2.5.29.32.0"]) // anyPolicy ]; let altNamesArray: { @@ -1308,6 +1356,7 @@ export const certificateAuthorityServiceFactory = ({ * Note: CSR is generated externally and submitted to Infisical. */ const signCertFromCa = async (dto: TSignCertFromCaDTO) => { + const appCfg = getConfig(); let ca: TCertificateAuthorities | undefined; let certificateTemplate: TCertificateTemplates | undefined; @@ -1432,7 +1481,7 @@ export const certificateAuthorityServiceFactory = ({ message: "A common name (CN) is required in the CSR or as a parameter to this endpoint" }); - const { caPrivateKey } = await getCaCredentials({ + const { caPrivateKey, caSecret } = await getCaCredentials({ caId: ca.id, certificateAuthorityDAL, certificateAuthoritySecretDAL, @@ -1440,11 +1489,20 @@ export const certificateAuthorityServiceFactory = ({ kmsService }); + const caCrl = await certificateAuthorityCrlDAL.findOne({ caSecretId: caSecret.id }); + const distributionPointUrl = `${appCfg.SITE_URL}/api/v1/pki/crl/${caCrl.id}`; + + const caIssuerUrl = `${appCfg.SITE_URL}/api/v1/pki/ca/${ca.id}/certificates/${caCert.id}`; const extensions: x509.Extension[] = [ new x509.KeyUsagesExtension(x509.KeyUsageFlags.digitalSignature | x509.KeyUsageFlags.keyEncipherment, true), new x509.BasicConstraintsExtension(false), await x509.AuthorityKeyIdentifierExtension.create(caCertObj, false), - await x509.SubjectKeyIdentifierExtension.create(csrObj.publicKey) + await x509.SubjectKeyIdentifierExtension.create(csrObj.publicKey), + new x509.CRLDistributionPointsExtension([distributionPointUrl]), + new x509.AuthorityInfoAccessExtension({ + caIssuers: new x509.GeneralName("url", caIssuerUrl) + }), + new x509.CertificatePolicyExtension(["2.5.29.32.0"]) // anyPolicy ]; let altNamesFromCsr: string = ""; @@ -1628,6 +1686,7 @@ export const certificateAuthorityServiceFactory = ({ renewCaCert, getCaCerts, getCaCert, + getCaCertById, signIntermediate, importCertToCa, issueCertFromCa, From bb3da758704da2f5d505dc83e9316d9b7228a4c7 Mon Sep 17 00:00:00 2001 From: Tuan Dang Date: Thu, 12 Sep 2024 17:26:56 -0700 Subject: [PATCH 2/3] Minor text updates --- backend/src/lib/api-docs/constants.ts | 2 +- backend/src/server/routes/v1/certificate-authority-router.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/backend/src/lib/api-docs/constants.ts b/backend/src/lib/api-docs/constants.ts index d68380f61..5ed7ed8f2 100644 --- a/backend/src/lib/api-docs/constants.ts +++ b/backend/src/lib/api-docs/constants.ts @@ -1084,7 +1084,7 @@ export const CERTIFICATE_AUTHORITIES = { serialNumber: "The serial number of the CA certificate" }, GET_CERT_BY_ID: { - caId: "The ID of the CA to get the certificate from", + caId: "The ID of the CA to get the CA certificate from", caCertId: "The ID of the CA certificate to get" }, GET_CA_CERTS: { diff --git a/backend/src/server/routes/v1/certificate-authority-router.ts b/backend/src/server/routes/v1/certificate-authority-router.ts index c52fa8d06..3abd048a8 100644 --- a/backend/src/server/routes/v1/certificate-authority-router.ts +++ b/backend/src/server/routes/v1/certificate-authority-router.ts @@ -149,7 +149,7 @@ export const registerCaRouter = async (server: FastifyZodProvider) => { rateLimit: readLimit }, schema: { - description: "Public endpoint for fetching DER-encoded Certificate of CA", + description: "Get DER-encoded certificate of CA", params: z.object({ caId: z.string().trim().describe(CERTIFICATE_AUTHORITIES.GET_CERT_BY_ID.caId), caCertId: z.string().trim().describe(CERTIFICATE_AUTHORITIES.GET_CERT_BY_ID.caCertId) From c37e3ba635a3750b03e4b48cda5b910a17f13d08 Mon Sep 17 00:00:00 2001 From: Sheen Capadngan Date: Fri, 13 Sep 2024 12:44:12 +0800 Subject: [PATCH 3/3] misc: addressed comments --- .../v1/certificate-authority-crl-router.ts | 24 +++++++++++++++++++ .../routes/v1/certificate-authority-router.ts | 2 +- .../certificate-authority-service.ts | 12 +++++----- 3 files changed, 31 insertions(+), 7 deletions(-) diff --git a/backend/src/ee/routes/v1/certificate-authority-crl-router.ts b/backend/src/ee/routes/v1/certificate-authority-crl-router.ts index 468981c0e..f61706025 100644 --- a/backend/src/ee/routes/v1/certificate-authority-crl-router.ts +++ b/backend/src/ee/routes/v1/certificate-authority-crl-router.ts @@ -11,6 +11,30 @@ export const registerCaCrlRouter = async (server: FastifyZodProvider) => { config: { rateLimit: readLimit }, + schema: { + description: "Get CRL in DER format (deprecated)", + params: z.object({ + crlId: z.string().trim().describe(CA_CRLS.GET.crlId) + }), + response: { + 200: z.instanceof(Buffer) + } + }, + handler: async (req, res) => { + const { crl } = await server.services.certificateAuthorityCrl.getCrlById(req.params.crlId); + + res.header("Content-Type", "application/pkix-crl"); + + return Buffer.from(crl); + } + }); + + server.route({ + method: "GET", + url: "/:crlId/der", + config: { + rateLimit: readLimit + }, schema: { description: "Get CRL in DER format", params: z.object({ diff --git a/backend/src/server/routes/v1/certificate-authority-router.ts b/backend/src/server/routes/v1/certificate-authority-router.ts index 3abd048a8..88ec8500e 100644 --- a/backend/src/server/routes/v1/certificate-authority-router.ts +++ b/backend/src/server/routes/v1/certificate-authority-router.ts @@ -144,7 +144,7 @@ export const registerCaRouter = async (server: FastifyZodProvider) => { // against the Authority Information Access CA Issuer URL server.route({ method: "GET", - url: "/:caId/certificates/:caCertId", + url: "/:caId/certificates/:caCertId/der", config: { rateLimit: readLimit }, diff --git a/backend/src/services/certificate-authority/certificate-authority-service.ts b/backend/src/services/certificate-authority/certificate-authority-service.ts index dfde75b84..c7d32f1d3 100644 --- a/backend/src/services/certificate-authority/certificate-authority-service.ts +++ b/backend/src/services/certificate-authority/certificate-authority-service.ts @@ -901,9 +901,9 @@ export const certificateAuthorityServiceFactory = ({ const serialNumber = createSerialNumber(); const caCrl = await certificateAuthorityCrlDAL.findOne({ caSecretId: caSecret.id }); - const distributionPointUrl = `${appCfg.SITE_URL}/api/v1/pki/crl/${caCrl.id}`; + const distributionPointUrl = `${appCfg.SITE_URL}/api/v1/pki/crl/${caCrl.id}/der`; - const caIssuerUrl = `${appCfg.SITE_URL}/api/v1/pki/ca/${ca.id}/certificates/${caCert.id}`; + const caIssuerUrl = `${appCfg.SITE_URL}/api/v1/pki/ca/${ca.id}/certificates/${caCert.id}/der`; const intermediateCert = await x509.X509CertificateGenerator.create({ serialNumber, subject: csrObj.subject, @@ -1219,8 +1219,8 @@ export const certificateAuthorityServiceFactory = ({ const caCrl = await certificateAuthorityCrlDAL.findOne({ caSecretId: caSecret.id }); const appCfg = getConfig(); - const distributionPointUrl = `${appCfg.SITE_URL}/api/v1/pki/crl/${caCrl.id}`; - const caIssuerUrl = `${appCfg.SITE_URL}/api/v1/pki/ca/${ca.id}/certificates/${caCert.id}`; + const distributionPointUrl = `${appCfg.SITE_URL}/api/v1/pki/crl/${caCrl.id}/der`; + const caIssuerUrl = `${appCfg.SITE_URL}/api/v1/pki/ca/${ca.id}/certificates/${caCert.id}/der`; const extensions: x509.Extension[] = [ new x509.BasicConstraintsExtension(false), @@ -1551,9 +1551,9 @@ export const certificateAuthorityServiceFactory = ({ }); const caCrl = await certificateAuthorityCrlDAL.findOne({ caSecretId: caSecret.id }); - const distributionPointUrl = `${appCfg.SITE_URL}/api/v1/pki/crl/${caCrl.id}`; + const distributionPointUrl = `${appCfg.SITE_URL}/api/v1/pki/crl/${caCrl.id}/der`; - const caIssuerUrl = `${appCfg.SITE_URL}/api/v1/pki/ca/${ca.id}/certificates/${caCert.id}`; + const caIssuerUrl = `${appCfg.SITE_URL}/api/v1/pki/ca/${ca.id}/certificates/${caCert.id}/der`; const extensions: x509.Extension[] = [ new x509.BasicConstraintsExtension(false), await x509.AuthorityKeyIdentifierExtension.create(caCertObj, false),