From b48325b4bad8b7958fd2716e9186085bfcef0c86 Mon Sep 17 00:00:00 2001 From: Tuan Dang Date: Sun, 9 Jun 2024 23:23:01 -0400 Subject: [PATCH] Update certificate chain handling --- .../20240607032218_certificate-mgmt.ts | 1 - backend/src/db/schemas/certificate-certs.ts | 3 +- backend/src/server/routes/index.ts | 1 + .../certificate-authority-dal.ts | 1 + .../certificate-authority-queue.ts | 4 +- .../certificate-authority-service.ts | 89 +++++++------------ .../certificate/certificate-service.ts | 19 +++- .../platform/pki/certificates.mdx | 2 +- .../components/CertificateContent.tsx | 2 +- 9 files changed, 56 insertions(+), 66 deletions(-) diff --git a/backend/src/db/migrations/20240607032218_certificate-mgmt.ts b/backend/src/db/migrations/20240607032218_certificate-mgmt.ts index 92a9f1ae6a..d4eeb60c8a 100644 --- a/backend/src/db/migrations/20240607032218_certificate-mgmt.ts +++ b/backend/src/db/migrations/20240607032218_certificate-mgmt.ts @@ -98,7 +98,6 @@ export async function up(knex: Knex): Promise { t.uuid("certId").notNullable().unique(); t.foreign("certId").references("id").inTable(TableName.Certificate).onDelete("CASCADE"); t.binary("encryptedCertificate").notNullable(); - t.binary("encryptedCertificateChain").notNullable(); }); } diff --git a/backend/src/db/schemas/certificate-certs.ts b/backend/src/db/schemas/certificate-certs.ts index e30fb04ff6..b02ef8ab43 100644 --- a/backend/src/db/schemas/certificate-certs.ts +++ b/backend/src/db/schemas/certificate-certs.ts @@ -14,8 +14,7 @@ export const CertificateCertsSchema = z.object({ createdAt: z.date(), updatedAt: z.date(), certId: z.string().uuid(), - encryptedCertificate: zodBuffer, - encryptedCertificateChain: zodBuffer + encryptedCertificate: zodBuffer }); export type TCertificateCerts = z.infer; diff --git a/backend/src/server/routes/index.ts b/backend/src/server/routes/index.ts index 1b9b138b00..dd1de1385a 100644 --- a/backend/src/server/routes/index.ts +++ b/backend/src/server/routes/index.ts @@ -527,6 +527,7 @@ export const registerRoutes = async ( certificateDAL, certificateCertDAL, certificateAuthorityDAL, + certificateAuthorityCertDAL, projectDAL, kmsService, permissionService diff --git a/backend/src/services/certificate-authority/certificate-authority-dal.ts b/backend/src/services/certificate-authority/certificate-authority-dal.ts index fc3b63b83f..1b4b30e737 100644 --- a/backend/src/services/certificate-authority/certificate-authority-dal.ts +++ b/backend/src/services/certificate-authority/certificate-authority-dal.ts @@ -8,6 +8,7 @@ export type TCertificateAuthorityDALFactory = ReturnType { const caOrm = ormify(db, TableName.CertificateAuthority); + // note: not used const buildCertificateChain = async (caId: string) => { try { const result: { diff --git a/backend/src/services/certificate-authority/certificate-authority-queue.ts b/backend/src/services/certificate-authority/certificate-authority-queue.ts index 1331689e42..fed0a91bb8 100644 --- a/backend/src/services/certificate-authority/certificate-authority-queue.ts +++ b/backend/src/services/certificate-authority/certificate-authority-queue.ts @@ -78,7 +78,7 @@ export const certificateAuthorityQueueFactory = ({ const ca = await certificateAuthorityDAL.findById(caId); if (!ca) throw new BadRequestError({ message: "CA not found" }); - const caKeys = await certificateAuthoritySecretDAL.findOne({ caId: ca.id }); + const caSecret = await certificateAuthoritySecretDAL.findOne({ caId: ca.id }); const alg = keyAlgorithmToAlgCfg(ca.keyAlgorithm as CertKeyAlgorithm); @@ -90,7 +90,7 @@ export const certificateAuthorityQueueFactory = ({ const privateKey = await kmsService.decrypt({ kmsId: keyId, - cipherTextBlob: caKeys.encryptedPrivateKey + cipherTextBlob: caSecret.encryptedPrivateKey }); const skObj = crypto.createPrivateKey({ key: privateKey, format: "der", type: "pkcs8" }); diff --git a/backend/src/services/certificate-authority/certificate-authority-service.ts b/backend/src/services/certificate-authority/certificate-authority-service.ts index f7d170e5bc..b4b0bb3409 100644 --- a/backend/src/services/certificate-authority/certificate-authority-service.ts +++ b/backend/src/services/certificate-authority/certificate-authority-service.ts @@ -38,7 +38,7 @@ import { type TCertificateAuthorityServiceFactoryDep = { certificateAuthorityDAL: Pick< TCertificateAuthorityDALFactory, - "transaction" | "create" | "findById" | "updateById" | "deleteById" | "findOne" | "buildCertificateChain" + "transaction" | "create" | "findById" | "updateById" | "deleteById" | "findOne" >; certificateAuthorityCertDAL: Pick; certificateAuthoritySecretDAL: Pick; @@ -53,8 +53,6 @@ type TCertificateAuthorityServiceFactoryDep = { export type TCertificateAuthorityServiceFactory = ReturnType; -// TODO: reconsider build cert chain due to imported chains - export const certificateAuthorityServiceFactory = ({ certificateAuthorityDAL, certificateAuthorityCertDAL, @@ -321,9 +319,7 @@ export const certificateAuthorityServiceFactory = ({ const caCert = await certificateAuthorityCertDAL.findOne({ caId: ca.id }); if (caCert) throw new BadRequestError({ message: "CA already has a certificate installed" }); - const caKeys = await certificateAuthoritySecretDAL.findOne({ caId: ca.id }); - - const alg = keyAlgorithmToAlgCfg(ca.keyAlgorithm as CertKeyAlgorithm); + const caSecret = await certificateAuthoritySecretDAL.findOne({ caId: ca.id }); const keyId = await getProjectKmsCertificateKeyId({ projectId: ca.projectId, @@ -333,9 +329,10 @@ export const certificateAuthorityServiceFactory = ({ const privateKey = await kmsService.decrypt({ kmsId: keyId, - cipherTextBlob: caKeys.encryptedPrivateKey + cipherTextBlob: caSecret.encryptedPrivateKey }); + const alg = keyAlgorithmToAlgCfg(ca.keyAlgorithm as CertKeyAlgorithm); const skObj = crypto.createPrivateKey({ key: privateKey, format: "der", type: "pkcs8" }); const sk = await crypto.subtle.importKey("pkcs8", skObj.export({ format: "der", type: "pkcs8" }), alg, true, [ "sign" @@ -414,7 +411,6 @@ export const certificateAuthorityServiceFactory = ({ /** * Issue certificate to be imported back in for intermediate CA - * TODO: cannot chain to self */ const signIntermediate = async ({ caId, @@ -454,11 +450,11 @@ export const certificateAuthorityServiceFactory = ({ }); const caCert = await certificateAuthorityCertDAL.findOne({ caId: ca.id }); - const caKeys = await certificateAuthoritySecretDAL.findOne({ caId: ca.id }); + const caSecret = await certificateAuthoritySecretDAL.findOne({ caId: ca.id }); const privateKey = await kmsService.decrypt({ kmsId: keyId, - cipherTextBlob: caKeys.encryptedPrivateKey + cipherTextBlob: caSecret.encryptedPrivateKey }); const skObj = crypto.createPrivateKey({ key: privateKey, format: "der", type: "pkcs8" }); @@ -471,11 +467,11 @@ export const certificateAuthorityServiceFactory = ({ cipherTextBlob: caCert.encryptedCertificate }); - const certObj = new x509.X509Certificate(decryptedCaCert); + const caCertObj = new x509.X509Certificate(decryptedCaCert); const csrObj = new x509.Pkcs10CertificateRequest(csr); // check path length constraint - const caPathLength = certObj.getExtension(x509.BasicConstraintsExtension)?.pathLength; + const caPathLength = caCertObj.getExtension(x509.BasicConstraintsExtension)?.pathLength; if (caPathLength !== undefined) { if (caPathLength === 0) throw new BadRequestError({ @@ -490,8 +486,8 @@ export const certificateAuthorityServiceFactory = ({ const notBeforeDate = notBefore ? new Date(notBefore) : new Date(); const notAfterDate = new Date(notAfter); - const caCertNotBeforeDate = new Date(certObj.notBefore); - const caCertNotAfterDate = new Date(certObj.notAfter); + const caCertNotBeforeDate = new Date(caCertObj.notBefore); + const caCertNotAfterDate = new Date(caCertObj.notAfter); // check not before constraint if (notBeforeDate < caCertNotBeforeDate) { @@ -509,7 +505,7 @@ export const certificateAuthorityServiceFactory = ({ const intermediateCert = await x509.X509CertificateGenerator.create({ serialNumber, subject: csrObj.subject, - issuer: certObj.subject, + issuer: caCertObj.subject, notBefore: notBeforeDate, notAfter: notAfterDate, signingKey: sk, @@ -524,28 +520,22 @@ export const certificateAuthorityServiceFactory = ({ true ), new x509.BasicConstraintsExtension(true, maxPathLength === -1 ? undefined : maxPathLength, true), - await x509.AuthorityKeyIdentifierExtension.create(certObj, false), + await x509.AuthorityKeyIdentifierExtension.create(caCertObj, false), await x509.SubjectKeyIdentifierExtension.create(csrObj.publicKey) ] }); - const chain = await certificateAuthorityDAL.buildCertificateChain(caId); - const decryptedChain = await Promise.all( - chain.map(async (c) => { - const decryptedCaChainCert = await kmsService.decrypt({ - kmsId: keyId, - cipherTextBlob: c - }); + const caCertChain = await kmsService.decrypt({ + kmsId: keyId, + cipherTextBlob: caCert.encryptedCertificateChain + }); - const chainCertObj = new x509.X509Certificate(decryptedCaChainCert); - return chainCertObj.toString("pem"); - }) - ); + const certificateChain = `${caCertObj.toString("pem")}\n${caCertChain.toString("utf-8")}`.trim(); return { certificate: intermediateCert.toString("pem"), - issuingCaCertificate: certObj.toString("pem"), - certificateChain: decryptedChain.join("\n"), + issuingCaCertificate: caCertObj.toString("pem"), + certificateChain, serialNumber: intermediateCert.serialNumber }; }; @@ -689,15 +679,15 @@ export const certificateAuthorityServiceFactory = ({ const caCertObj = new x509.X509Certificate(decryptedCaCert); - const alg = keyAlgorithmToAlgCfg(ca.keyAlgorithm as CertKeyAlgorithm); - - const caKeys = await certificateAuthoritySecretDAL.findOne({ caId: ca.id }); + const caSecret = await certificateAuthoritySecretDAL.findOne({ caId: ca.id }); const privateKey = await kmsService.decrypt({ kmsId: keyId, - cipherTextBlob: caKeys.encryptedPrivateKey + cipherTextBlob: caSecret.encryptedPrivateKey }); + const alg = keyAlgorithmToAlgCfg(ca.keyAlgorithm as CertKeyAlgorithm); + const caSkObj = crypto.createPrivateKey({ key: privateKey, format: "der", type: "pkcs8" }); const caSk = await crypto.subtle.importKey("pkcs8", caSkObj.export({ format: "der", type: "pkcs8" }), alg, true, [ "sign" @@ -762,28 +752,14 @@ export const certificateAuthorityServiceFactory = ({ const skLeafObj = KeyObject.from(leafKeys.privateKey); const skLeaf = skLeafObj.export({ format: "pem", type: "pkcs8" }) as string; - const chain = await certificateAuthorityDAL.buildCertificateChain(caId); - const { cipherTextBlob: encryptedCertificate } = await kmsService.encrypt({ kmsId: keyId, plainText: Buffer.from(new Uint8Array(leafCert.rawData)) }); - const decryptedChain = await Promise.all( - chain.map(async (c) => { - const decryptedCaChainCert = await kmsService.decrypt({ - kmsId: keyId, - cipherTextBlob: c - }); - - const certObj = new x509.X509Certificate(decryptedCaChainCert); - return certObj.toString("pem"); - }) - ); - - const { cipherTextBlob: encryptedCertificateChain } = await kmsService.encrypt({ + const caCertChain = await kmsService.decrypt({ kmsId: keyId, - plainText: Buffer.from(decryptedChain.join("\n")) + cipherTextBlob: caCert.encryptedCertificateChain }); await certificateDAL.transaction(async (tx) => { @@ -802,8 +778,7 @@ export const certificateAuthorityServiceFactory = ({ await certificateCertDAL.create( { certId: cert.id, - encryptedCertificate, - encryptedCertificateChain + encryptedCertificate }, tx ); @@ -811,9 +786,11 @@ export const certificateAuthorityServiceFactory = ({ return cert; }); + const certificateChain = `${caCertObj.toString("pem")}\n${caCertChain.toString("utf-8")}`.trim(); + return { certificate: leafCert.toString("pem"), - certificateChain: decryptedChain.join("\n"), + certificateChain, issuingCaCertificate: caCertObj.toString("pem"), privateKey: skLeaf, serialNumber @@ -840,7 +817,7 @@ export const certificateAuthorityServiceFactory = ({ ProjectPermissionSub.CertificateAuthorities ); - const caKeys = await certificateAuthoritySecretDAL.findOne({ caId: ca.id }); + const caSecret = await certificateAuthoritySecretDAL.findOne({ caId: ca.id }); const alg = keyAlgorithmToAlgCfg(ca.keyAlgorithm as CertKeyAlgorithm); @@ -852,7 +829,7 @@ export const certificateAuthorityServiceFactory = ({ const privateKey = await kmsService.decrypt({ kmsId: keyId, - cipherTextBlob: caKeys.encryptedPrivateKey + cipherTextBlob: caSecret.encryptedPrivateKey }); const skObj = crypto.createPrivateKey({ key: privateKey, format: "der", type: "pkcs8" }); @@ -907,7 +884,7 @@ export const certificateAuthorityServiceFactory = ({ ProjectPermissionSub.CertificateAuthorities ); - const caKeys = await certificateAuthoritySecretDAL.findOne({ caId: ca.id }); + const caSecret = await certificateAuthoritySecretDAL.findOne({ caId: ca.id }); const alg = keyAlgorithmToAlgCfg(ca.keyAlgorithm as CertKeyAlgorithm); @@ -919,7 +896,7 @@ export const certificateAuthorityServiceFactory = ({ const privateKey = await kmsService.decrypt({ kmsId: keyId, - cipherTextBlob: caKeys.encryptedPrivateKey + cipherTextBlob: caSecret.encryptedPrivateKey }); const skObj = crypto.createPrivateKey({ key: privateKey, format: "der", type: "pkcs8" }); diff --git a/backend/src/services/certificate/certificate-service.ts b/backend/src/services/certificate/certificate-service.ts index 992b2296d8..bac7104320 100644 --- a/backend/src/services/certificate/certificate-service.ts +++ b/backend/src/services/certificate/certificate-service.ts @@ -5,6 +5,7 @@ import { TPermissionServiceFactory } from "@app/ee/services/permission/permissio import { ProjectPermissionActions, ProjectPermissionSub } from "@app/ee/services/permission/project-permission"; import { TCertificateCertDALFactory } from "@app/services/certificate/certificate-cert-dal"; import { TCertificateDALFactory } from "@app/services/certificate/certificate-dal"; +import { TCertificateAuthorityCertDALFactory } from "@app/services/certificate-authority/certificate-authority-cert-dal"; import { TCertificateAuthorityDALFactory } from "@app/services/certificate-authority/certificate-authority-dal"; import { TKmsServiceFactory } from "@app/services/kms/kms-service"; import { TProjectDALFactory } from "@app/services/project/project-dal"; @@ -17,6 +18,7 @@ type TCertificateServiceFactoryDep = { certificateDAL: Pick; certificateCertDAL: Pick; certificateAuthorityDAL: Pick; + certificateAuthorityCertDAL: Pick; projectDAL: Pick; kmsService: Pick; permissionService: Pick; @@ -28,6 +30,7 @@ export const certificateServiceFactory = ({ certificateDAL, certificateCertDAL, certificateAuthorityDAL, + certificateAuthorityCertDAL, projectDAL, kmsService, permissionService @@ -108,6 +111,7 @@ export const certificateServiceFactory = ({ const getCertCert = async ({ serialNumber, actorId, actorAuthMethod, actor, actorOrgId }: TGetCertCertDTO) => { const cert = await certificateDAL.findOne({ serialNumber }); const ca = await certificateAuthorityDAL.findById(cert.caId); + const caCert = await certificateAuthorityCertDAL.findOne({ caId: ca.id }); const { permission } = await permissionService.getProjectPermission( actor, @@ -132,16 +136,25 @@ export const certificateServiceFactory = ({ cipherTextBlob: certCert.encryptedCertificate }); - const decryptedChain = await kmsService.decrypt({ + const caCertChain = await kmsService.decrypt({ kmsId: keyId, - cipherTextBlob: certCert.encryptedCertificateChain + cipherTextBlob: caCert.encryptedCertificateChain }); const certObj = new x509.X509Certificate(decryptedCert); + const decryptedCaCert = await kmsService.decrypt({ + kmsId: keyId, + cipherTextBlob: caCert.encryptedCertificate + }); + + const caCertObj = new x509.X509Certificate(decryptedCaCert); + + const certificateChain = `${caCertObj.toString("pem")}\n${caCertChain.toString("utf-8")}`.trim(); + return { certificate: certObj.toString("pem"), - certificateChain: decryptedChain.toString("utf-8"), + certificateChain, serialNumber: certObj.serialNumber }; }; diff --git a/docs/documentation/platform/pki/certificates.mdx b/docs/documentation/platform/pki/certificates.mdx index 83759762f9..f1a025a8d6 100644 --- a/docs/documentation/platform/pki/certificates.mdx +++ b/docs/documentation/platform/pki/certificates.mdx @@ -97,7 +97,7 @@ In the following steps, we explore how to revoke a X.509 certificate under a CA downloaded CRL with OpenSSL, you can use the following command: ```bash -openssl verify -crl_check -CAfile chain.pem -CRLfile crl.pem certificate.pem +openssl verify -crl_check -CAfile chain.pem -CRLfile crl.pem cert.pem ``` diff --git a/frontend/src/views/Project/CertificatesPage/components/CertificatesTab/components/CertificateContent.tsx b/frontend/src/views/Project/CertificatesPage/components/CertificatesTab/components/CertificateContent.tsx index 54ab50ab50..42746d91a0 100644 --- a/frontend/src/views/Project/CertificatesPage/components/CertificatesTab/components/CertificateContent.tsx +++ b/frontend/src/views/Project/CertificatesPage/components/CertificatesTab/components/CertificateContent.tsx @@ -98,7 +98,7 @@ export const CertificateContent = ({ colorSchema="secondary" className="group relative ml-2" onClick={() => { - downloadTxtFile("certificate.pem", certificate); + downloadTxtFile("cert.pem", certificate); }} >