Update certificate chain handling

This commit is contained in:
Tuan Dang
2024-06-09 23:23:01 -04:00
parent b6c924ef37
commit b48325b4ba
9 changed files with 56 additions and 66 deletions
@@ -98,7 +98,6 @@ export async function up(knex: Knex): Promise<void> {
t.uuid("certId").notNullable().unique(); t.uuid("certId").notNullable().unique();
t.foreign("certId").references("id").inTable(TableName.Certificate).onDelete("CASCADE"); t.foreign("certId").references("id").inTable(TableName.Certificate).onDelete("CASCADE");
t.binary("encryptedCertificate").notNullable(); t.binary("encryptedCertificate").notNullable();
t.binary("encryptedCertificateChain").notNullable();
}); });
} }
+1 -2
View File
@@ -14,8 +14,7 @@ export const CertificateCertsSchema = z.object({
createdAt: z.date(), createdAt: z.date(),
updatedAt: z.date(), updatedAt: z.date(),
certId: z.string().uuid(), certId: z.string().uuid(),
encryptedCertificate: zodBuffer, encryptedCertificate: zodBuffer
encryptedCertificateChain: zodBuffer
}); });
export type TCertificateCerts = z.infer<typeof CertificateCertsSchema>; export type TCertificateCerts = z.infer<typeof CertificateCertsSchema>;
+1
View File
@@ -527,6 +527,7 @@ export const registerRoutes = async (
certificateDAL, certificateDAL,
certificateCertDAL, certificateCertDAL,
certificateAuthorityDAL, certificateAuthorityDAL,
certificateAuthorityCertDAL,
projectDAL, projectDAL,
kmsService, kmsService,
permissionService permissionService
@@ -8,6 +8,7 @@ export type TCertificateAuthorityDALFactory = ReturnType<typeof certificateAutho
export const certificateAuthorityDALFactory = (db: TDbClient) => { export const certificateAuthorityDALFactory = (db: TDbClient) => {
const caOrm = ormify(db, TableName.CertificateAuthority); const caOrm = ormify(db, TableName.CertificateAuthority);
// note: not used
const buildCertificateChain = async (caId: string) => { const buildCertificateChain = async (caId: string) => {
try { try {
const result: { const result: {
@@ -78,7 +78,7 @@ export const certificateAuthorityQueueFactory = ({
const ca = await certificateAuthorityDAL.findById(caId); const ca = await certificateAuthorityDAL.findById(caId);
if (!ca) throw new BadRequestError({ message: "CA not found" }); 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); const alg = keyAlgorithmToAlgCfg(ca.keyAlgorithm as CertKeyAlgorithm);
@@ -90,7 +90,7 @@ export const certificateAuthorityQueueFactory = ({
const privateKey = await kmsService.decrypt({ const privateKey = await kmsService.decrypt({
kmsId: keyId, kmsId: keyId,
cipherTextBlob: caKeys.encryptedPrivateKey cipherTextBlob: caSecret.encryptedPrivateKey
}); });
const skObj = crypto.createPrivateKey({ key: privateKey, format: "der", type: "pkcs8" }); const skObj = crypto.createPrivateKey({ key: privateKey, format: "der", type: "pkcs8" });
@@ -38,7 +38,7 @@ import {
type TCertificateAuthorityServiceFactoryDep = { type TCertificateAuthorityServiceFactoryDep = {
certificateAuthorityDAL: Pick< certificateAuthorityDAL: Pick<
TCertificateAuthorityDALFactory, TCertificateAuthorityDALFactory,
"transaction" | "create" | "findById" | "updateById" | "deleteById" | "findOne" | "buildCertificateChain" "transaction" | "create" | "findById" | "updateById" | "deleteById" | "findOne"
>; >;
certificateAuthorityCertDAL: Pick<TCertificateAuthorityCertDALFactory, "create" | "findOne" | "transaction">; certificateAuthorityCertDAL: Pick<TCertificateAuthorityCertDALFactory, "create" | "findOne" | "transaction">;
certificateAuthoritySecretDAL: Pick<TCertificateAuthoritySecretDALFactory, "create" | "findOne">; certificateAuthoritySecretDAL: Pick<TCertificateAuthoritySecretDALFactory, "create" | "findOne">;
@@ -53,8 +53,6 @@ type TCertificateAuthorityServiceFactoryDep = {
export type TCertificateAuthorityServiceFactory = ReturnType<typeof certificateAuthorityServiceFactory>; export type TCertificateAuthorityServiceFactory = ReturnType<typeof certificateAuthorityServiceFactory>;
// TODO: reconsider build cert chain due to imported chains
export const certificateAuthorityServiceFactory = ({ export const certificateAuthorityServiceFactory = ({
certificateAuthorityDAL, certificateAuthorityDAL,
certificateAuthorityCertDAL, certificateAuthorityCertDAL,
@@ -321,9 +319,7 @@ export const certificateAuthorityServiceFactory = ({
const caCert = await certificateAuthorityCertDAL.findOne({ caId: ca.id }); const caCert = await certificateAuthorityCertDAL.findOne({ caId: ca.id });
if (caCert) throw new BadRequestError({ message: "CA already has a certificate installed" }); if (caCert) throw new BadRequestError({ message: "CA already has a certificate installed" });
const caKeys = await certificateAuthoritySecretDAL.findOne({ caId: ca.id }); const caSecret = await certificateAuthoritySecretDAL.findOne({ caId: ca.id });
const alg = keyAlgorithmToAlgCfg(ca.keyAlgorithm as CertKeyAlgorithm);
const keyId = await getProjectKmsCertificateKeyId({ const keyId = await getProjectKmsCertificateKeyId({
projectId: ca.projectId, projectId: ca.projectId,
@@ -333,9 +329,10 @@ export const certificateAuthorityServiceFactory = ({
const privateKey = await kmsService.decrypt({ const privateKey = await kmsService.decrypt({
kmsId: keyId, 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 skObj = crypto.createPrivateKey({ key: privateKey, format: "der", type: "pkcs8" });
const sk = await crypto.subtle.importKey("pkcs8", skObj.export({ format: "der", type: "pkcs8" }), alg, true, [ const sk = await crypto.subtle.importKey("pkcs8", skObj.export({ format: "der", type: "pkcs8" }), alg, true, [
"sign" "sign"
@@ -414,7 +411,6 @@ export const certificateAuthorityServiceFactory = ({
/** /**
* Issue certificate to be imported back in for intermediate CA * Issue certificate to be imported back in for intermediate CA
* TODO: cannot chain to self
*/ */
const signIntermediate = async ({ const signIntermediate = async ({
caId, caId,
@@ -454,11 +450,11 @@ export const certificateAuthorityServiceFactory = ({
}); });
const caCert = await certificateAuthorityCertDAL.findOne({ caId: ca.id }); 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({ const privateKey = await kmsService.decrypt({
kmsId: keyId, kmsId: keyId,
cipherTextBlob: caKeys.encryptedPrivateKey cipherTextBlob: caSecret.encryptedPrivateKey
}); });
const skObj = crypto.createPrivateKey({ key: privateKey, format: "der", type: "pkcs8" }); const skObj = crypto.createPrivateKey({ key: privateKey, format: "der", type: "pkcs8" });
@@ -471,11 +467,11 @@ export const certificateAuthorityServiceFactory = ({
cipherTextBlob: caCert.encryptedCertificate cipherTextBlob: caCert.encryptedCertificate
}); });
const certObj = new x509.X509Certificate(decryptedCaCert); const caCertObj = new x509.X509Certificate(decryptedCaCert);
const csrObj = new x509.Pkcs10CertificateRequest(csr); const csrObj = new x509.Pkcs10CertificateRequest(csr);
// check path length constraint // check path length constraint
const caPathLength = certObj.getExtension(x509.BasicConstraintsExtension)?.pathLength; const caPathLength = caCertObj.getExtension(x509.BasicConstraintsExtension)?.pathLength;
if (caPathLength !== undefined) { if (caPathLength !== undefined) {
if (caPathLength === 0) if (caPathLength === 0)
throw new BadRequestError({ throw new BadRequestError({
@@ -490,8 +486,8 @@ export const certificateAuthorityServiceFactory = ({
const notBeforeDate = notBefore ? new Date(notBefore) : new Date(); const notBeforeDate = notBefore ? new Date(notBefore) : new Date();
const notAfterDate = new Date(notAfter); const notAfterDate = new Date(notAfter);
const caCertNotBeforeDate = new Date(certObj.notBefore); const caCertNotBeforeDate = new Date(caCertObj.notBefore);
const caCertNotAfterDate = new Date(certObj.notAfter); const caCertNotAfterDate = new Date(caCertObj.notAfter);
// check not before constraint // check not before constraint
if (notBeforeDate < caCertNotBeforeDate) { if (notBeforeDate < caCertNotBeforeDate) {
@@ -509,7 +505,7 @@ export const certificateAuthorityServiceFactory = ({
const intermediateCert = await x509.X509CertificateGenerator.create({ const intermediateCert = await x509.X509CertificateGenerator.create({
serialNumber, serialNumber,
subject: csrObj.subject, subject: csrObj.subject,
issuer: certObj.subject, issuer: caCertObj.subject,
notBefore: notBeforeDate, notBefore: notBeforeDate,
notAfter: notAfterDate, notAfter: notAfterDate,
signingKey: sk, signingKey: sk,
@@ -524,28 +520,22 @@ export const certificateAuthorityServiceFactory = ({
true true
), ),
new x509.BasicConstraintsExtension(true, maxPathLength === -1 ? undefined : maxPathLength, 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) await x509.SubjectKeyIdentifierExtension.create(csrObj.publicKey)
] ]
}); });
const chain = await certificateAuthorityDAL.buildCertificateChain(caId); const caCertChain = await kmsService.decrypt({
const decryptedChain = await Promise.all(
chain.map(async (c) => {
const decryptedCaChainCert = await kmsService.decrypt({
kmsId: keyId, kmsId: keyId,
cipherTextBlob: c cipherTextBlob: caCert.encryptedCertificateChain
}); });
const chainCertObj = new x509.X509Certificate(decryptedCaChainCert); const certificateChain = `${caCertObj.toString("pem")}\n${caCertChain.toString("utf-8")}`.trim();
return chainCertObj.toString("pem");
})
);
return { return {
certificate: intermediateCert.toString("pem"), certificate: intermediateCert.toString("pem"),
issuingCaCertificate: certObj.toString("pem"), issuingCaCertificate: caCertObj.toString("pem"),
certificateChain: decryptedChain.join("\n"), certificateChain,
serialNumber: intermediateCert.serialNumber serialNumber: intermediateCert.serialNumber
}; };
}; };
@@ -689,15 +679,15 @@ export const certificateAuthorityServiceFactory = ({
const caCertObj = new x509.X509Certificate(decryptedCaCert); const caCertObj = new x509.X509Certificate(decryptedCaCert);
const alg = keyAlgorithmToAlgCfg(ca.keyAlgorithm as CertKeyAlgorithm); const caSecret = await certificateAuthoritySecretDAL.findOne({ caId: ca.id });
const caKeys = await certificateAuthoritySecretDAL.findOne({ caId: ca.id });
const privateKey = await kmsService.decrypt({ const privateKey = await kmsService.decrypt({
kmsId: keyId, 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 caSkObj = crypto.createPrivateKey({ key: privateKey, format: "der", type: "pkcs8" });
const caSk = await crypto.subtle.importKey("pkcs8", caSkObj.export({ format: "der", type: "pkcs8" }), alg, true, [ const caSk = await crypto.subtle.importKey("pkcs8", caSkObj.export({ format: "der", type: "pkcs8" }), alg, true, [
"sign" "sign"
@@ -762,28 +752,14 @@ export const certificateAuthorityServiceFactory = ({
const skLeafObj = KeyObject.from(leafKeys.privateKey); const skLeafObj = KeyObject.from(leafKeys.privateKey);
const skLeaf = skLeafObj.export({ format: "pem", type: "pkcs8" }) as string; const skLeaf = skLeafObj.export({ format: "pem", type: "pkcs8" }) as string;
const chain = await certificateAuthorityDAL.buildCertificateChain(caId);
const { cipherTextBlob: encryptedCertificate } = await kmsService.encrypt({ const { cipherTextBlob: encryptedCertificate } = await kmsService.encrypt({
kmsId: keyId, kmsId: keyId,
plainText: Buffer.from(new Uint8Array(leafCert.rawData)) plainText: Buffer.from(new Uint8Array(leafCert.rawData))
}); });
const decryptedChain = await Promise.all( const caCertChain = await kmsService.decrypt({
chain.map(async (c) => {
const decryptedCaChainCert = await kmsService.decrypt({
kmsId: keyId, kmsId: keyId,
cipherTextBlob: c cipherTextBlob: caCert.encryptedCertificateChain
});
const certObj = new x509.X509Certificate(decryptedCaChainCert);
return certObj.toString("pem");
})
);
const { cipherTextBlob: encryptedCertificateChain } = await kmsService.encrypt({
kmsId: keyId,
plainText: Buffer.from(decryptedChain.join("\n"))
}); });
await certificateDAL.transaction(async (tx) => { await certificateDAL.transaction(async (tx) => {
@@ -802,8 +778,7 @@ export const certificateAuthorityServiceFactory = ({
await certificateCertDAL.create( await certificateCertDAL.create(
{ {
certId: cert.id, certId: cert.id,
encryptedCertificate, encryptedCertificate
encryptedCertificateChain
}, },
tx tx
); );
@@ -811,9 +786,11 @@ export const certificateAuthorityServiceFactory = ({
return cert; return cert;
}); });
const certificateChain = `${caCertObj.toString("pem")}\n${caCertChain.toString("utf-8")}`.trim();
return { return {
certificate: leafCert.toString("pem"), certificate: leafCert.toString("pem"),
certificateChain: decryptedChain.join("\n"), certificateChain,
issuingCaCertificate: caCertObj.toString("pem"), issuingCaCertificate: caCertObj.toString("pem"),
privateKey: skLeaf, privateKey: skLeaf,
serialNumber serialNumber
@@ -840,7 +817,7 @@ export const certificateAuthorityServiceFactory = ({
ProjectPermissionSub.CertificateAuthorities 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); const alg = keyAlgorithmToAlgCfg(ca.keyAlgorithm as CertKeyAlgorithm);
@@ -852,7 +829,7 @@ export const certificateAuthorityServiceFactory = ({
const privateKey = await kmsService.decrypt({ const privateKey = await kmsService.decrypt({
kmsId: keyId, kmsId: keyId,
cipherTextBlob: caKeys.encryptedPrivateKey cipherTextBlob: caSecret.encryptedPrivateKey
}); });
const skObj = crypto.createPrivateKey({ key: privateKey, format: "der", type: "pkcs8" }); const skObj = crypto.createPrivateKey({ key: privateKey, format: "der", type: "pkcs8" });
@@ -907,7 +884,7 @@ export const certificateAuthorityServiceFactory = ({
ProjectPermissionSub.CertificateAuthorities 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); const alg = keyAlgorithmToAlgCfg(ca.keyAlgorithm as CertKeyAlgorithm);
@@ -919,7 +896,7 @@ export const certificateAuthorityServiceFactory = ({
const privateKey = await kmsService.decrypt({ const privateKey = await kmsService.decrypt({
kmsId: keyId, kmsId: keyId,
cipherTextBlob: caKeys.encryptedPrivateKey cipherTextBlob: caSecret.encryptedPrivateKey
}); });
const skObj = crypto.createPrivateKey({ key: privateKey, format: "der", type: "pkcs8" }); const skObj = crypto.createPrivateKey({ key: privateKey, format: "der", type: "pkcs8" });
@@ -5,6 +5,7 @@ import { TPermissionServiceFactory } from "@app/ee/services/permission/permissio
import { ProjectPermissionActions, ProjectPermissionSub } from "@app/ee/services/permission/project-permission"; import { ProjectPermissionActions, ProjectPermissionSub } from "@app/ee/services/permission/project-permission";
import { TCertificateCertDALFactory } from "@app/services/certificate/certificate-cert-dal"; import { TCertificateCertDALFactory } from "@app/services/certificate/certificate-cert-dal";
import { TCertificateDALFactory } from "@app/services/certificate/certificate-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 { TCertificateAuthorityDALFactory } from "@app/services/certificate-authority/certificate-authority-dal";
import { TKmsServiceFactory } from "@app/services/kms/kms-service"; import { TKmsServiceFactory } from "@app/services/kms/kms-service";
import { TProjectDALFactory } from "@app/services/project/project-dal"; import { TProjectDALFactory } from "@app/services/project/project-dal";
@@ -17,6 +18,7 @@ type TCertificateServiceFactoryDep = {
certificateDAL: Pick<TCertificateDALFactory, "findOne" | "deleteById" | "update">; certificateDAL: Pick<TCertificateDALFactory, "findOne" | "deleteById" | "update">;
certificateCertDAL: Pick<TCertificateCertDALFactory, "findOne">; certificateCertDAL: Pick<TCertificateCertDALFactory, "findOne">;
certificateAuthorityDAL: Pick<TCertificateAuthorityDALFactory, "findById">; certificateAuthorityDAL: Pick<TCertificateAuthorityDALFactory, "findById">;
certificateAuthorityCertDAL: Pick<TCertificateAuthorityCertDALFactory, "findOne">;
projectDAL: Pick<TProjectDALFactory, "findOne" | "updateById" | "findById" | "transaction">; projectDAL: Pick<TProjectDALFactory, "findOne" | "updateById" | "findById" | "transaction">;
kmsService: Pick<TKmsServiceFactory, "generateKmsKey" | "encrypt" | "decrypt">; kmsService: Pick<TKmsServiceFactory, "generateKmsKey" | "encrypt" | "decrypt">;
permissionService: Pick<TPermissionServiceFactory, "getProjectPermission">; permissionService: Pick<TPermissionServiceFactory, "getProjectPermission">;
@@ -28,6 +30,7 @@ export const certificateServiceFactory = ({
certificateDAL, certificateDAL,
certificateCertDAL, certificateCertDAL,
certificateAuthorityDAL, certificateAuthorityDAL,
certificateAuthorityCertDAL,
projectDAL, projectDAL,
kmsService, kmsService,
permissionService permissionService
@@ -108,6 +111,7 @@ export const certificateServiceFactory = ({
const getCertCert = async ({ serialNumber, actorId, actorAuthMethod, actor, actorOrgId }: TGetCertCertDTO) => { const getCertCert = async ({ serialNumber, actorId, actorAuthMethod, actor, actorOrgId }: TGetCertCertDTO) => {
const cert = await certificateDAL.findOne({ serialNumber }); const cert = await certificateDAL.findOne({ serialNumber });
const ca = await certificateAuthorityDAL.findById(cert.caId); const ca = await certificateAuthorityDAL.findById(cert.caId);
const caCert = await certificateAuthorityCertDAL.findOne({ caId: ca.id });
const { permission } = await permissionService.getProjectPermission( const { permission } = await permissionService.getProjectPermission(
actor, actor,
@@ -132,16 +136,25 @@ export const certificateServiceFactory = ({
cipherTextBlob: certCert.encryptedCertificate cipherTextBlob: certCert.encryptedCertificate
}); });
const decryptedChain = await kmsService.decrypt({ const caCertChain = await kmsService.decrypt({
kmsId: keyId, kmsId: keyId,
cipherTextBlob: certCert.encryptedCertificateChain cipherTextBlob: caCert.encryptedCertificateChain
}); });
const certObj = new x509.X509Certificate(decryptedCert); 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 { return {
certificate: certObj.toString("pem"), certificate: certObj.toString("pem"),
certificateChain: decryptedChain.toString("utf-8"), certificateChain,
serialNumber: certObj.serialNumber serialNumber: certObj.serialNumber
}; };
}; };
@@ -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: downloaded CRL with OpenSSL, you can use the following command:
```bash ```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
``` ```
</Step> </Step>
@@ -98,7 +98,7 @@ export const CertificateContent = ({
colorSchema="secondary" colorSchema="secondary"
className="group relative ml-2" className="group relative ml-2"
onClick={() => { onClick={() => {
downloadTxtFile("certificate.pem", certificate); downloadTxtFile("cert.pem", certificate);
}} }}
> >
<FontAwesomeIcon icon={faDownload} /> <FontAwesomeIcon icon={faDownload} />