Encrypt CRL

This commit is contained in:
Tuan Dang
2024-06-09 18:09:34 -04:00
parent 931119f6ea
commit b6c924ef37
6 changed files with 96 additions and 61 deletions
@@ -68,7 +68,7 @@ export async function up(knex: Knex): Promise<void> {
t.timestamps(true, true, true); t.timestamps(true, true, true);
t.uuid("caId").notNullable().unique(); t.uuid("caId").notNullable().unique();
t.foreign("caId").references("id").inTable(TableName.CertificateAuthority).onDelete("CASCADE"); t.foreign("caId").references("id").inTable(TableName.CertificateAuthority).onDelete("CASCADE");
t.text("crl").notNullable(); // TODO: encrypt t.binary("encryptedCrl").notNullable(); // TODO: encrypt
t.integer("ttl").notNullable(); // in minutes t.integer("ttl").notNullable(); // in minutes
// TODO: consider type (crl or delta) // TODO: consider type (crl or delta)
// TODO: rebuild interval // TODO: rebuild interval
@@ -5,6 +5,8 @@
import { z } from "zod"; import { z } from "zod";
import { zodBuffer } from "@app/lib/zod";
import { TImmutableDBKeys } from "./models"; import { TImmutableDBKeys } from "./models";
export const CertificateAuthorityCrlSchema = z.object({ export const CertificateAuthorityCrlSchema = z.object({
@@ -12,7 +14,7 @@ export const CertificateAuthorityCrlSchema = z.object({
createdAt: z.date(), createdAt: z.date(),
updatedAt: z.date(), updatedAt: z.date(),
caId: z.string().uuid(), caId: z.string().uuid(),
crl: z.string(), encryptedCrl: zodBuffer,
ttl: z.number() ttl: z.number()
}); });
+2
View File
@@ -537,6 +537,8 @@ export const registerRoutes = async (
certificateAuthorityDAL, certificateAuthorityDAL,
certificateAuthoritySecretDAL, certificateAuthoritySecretDAL,
certificateDAL, certificateDAL,
projectDAL,
kmsService,
queueService queueService
}); });
@@ -13,17 +13,17 @@ export const certificateAuthorityDALFactory = (db: TDbClient) => {
const result: { const result: {
caId: string; caId: string;
parentCaId?: string; parentCaId?: string;
certificate: Buffer; encryptedCertificate: Buffer;
}[] = await db }[] = await db
.withRecursive("cte", (cte) => { .withRecursive("cte", (cte) => {
void cte void cte
.select("ca.id as caId", "ca.parentCaId", "cert.certificate") .select("ca.id as caId", "ca.parentCaId", "cert.encryptedCertificate")
.from({ ca: TableName.CertificateAuthority }) .from({ ca: TableName.CertificateAuthority })
.leftJoin({ cert: TableName.CertificateAuthorityCert }, "ca.id", "cert.caId") .leftJoin({ cert: TableName.CertificateAuthorityCert }, "ca.id", "cert.caId")
.where("ca.id", caId) .where("ca.id", caId)
.unionAll((builder) => { .unionAll((builder) => {
void builder void builder
.select("ca.id as caId", "ca.parentCaId", "cert.certificate") .select("ca.id as caId", "ca.parentCaId", "cert.encryptedCertificate")
.from({ ca: TableName.CertificateAuthority }) .from({ ca: TableName.CertificateAuthority })
.leftJoin({ cert: TableName.CertificateAuthorityCert }, "ca.id", "cert.caId") .leftJoin({ cert: TableName.CertificateAuthorityCert }, "ca.id", "cert.caId")
.innerJoin("cte", "cte.parentCaId", "ca.id"); .innerJoin("cte", "cte.parentCaId", "ca.id");
@@ -33,7 +33,7 @@ export const certificateAuthorityDALFactory = (db: TDbClient) => {
.from("cte"); .from("cte");
// Extract certificates and reverse the order to have the root CA at the end // Extract certificates and reverse the order to have the root CA at the end
const certChain: Buffer[] = result.map((row) => row.certificate); const certChain: Buffer[] = result.map((row) => row.encryptedCertificate);
return certChain; return certChain;
} catch (error) { } catch (error) {
throw new DatabaseError({ error, name: "BuildCertificateChain" }); throw new DatabaseError({ error, name: "BuildCertificateChain" });
@@ -1,17 +1,20 @@
// import * as x509 from "@peculiar/x509"; import * as x509 from "@peculiar/x509";
// import crypto from "crypto"; import crypto from "crypto";
import { getConfig } from "@app/lib/config/env"; import { getConfig } from "@app/lib/config/env";
import { daysToMillisecond, secondsToMillis } from "@app/lib/dates"; import { daysToMillisecond, secondsToMillis } from "@app/lib/dates";
// import { BadRequestError } from "@app/lib/errors"; import { BadRequestError } from "@app/lib/errors";
import { logger } from "@app/lib/logger"; import { logger } from "@app/lib/logger";
import { QueueJobs, QueueName, TQueueServiceFactory } from "@app/queue"; import { QueueJobs, QueueName, TQueueServiceFactory } from "@app/queue";
import { TCertificateDALFactory } from "@app/services/certificate/certificate-dal"; import { TCertificateDALFactory } from "@app/services/certificate/certificate-dal";
import { CertKeyAlgorithm, CertStatus } from "@app/services/certificate/certificate-types";
import { TKmsServiceFactory } from "@app/services/kms/kms-service";
import { TProjectDALFactory } from "@app/services/project/project-dal";
import { getProjectKmsCertificateKeyId } from "@app/services/project/project-fns";
// import { CertKeyAlgorithm, CertStatus } from "@app/services/certificate/certificate-types";
import { TCertificateAuthorityCrlDALFactory } from "./certificate-authority-crl-dal"; import { TCertificateAuthorityCrlDALFactory } from "./certificate-authority-crl-dal";
import { TCertificateAuthorityDALFactory } from "./certificate-authority-dal"; import { TCertificateAuthorityDALFactory } from "./certificate-authority-dal";
// import { keyAlgorithmToAlgCfg } from "./certificate-authority-fns"; import { keyAlgorithmToAlgCfg } from "./certificate-authority-fns";
import { TCertificateAuthoritySecretDALFactory } from "./certificate-authority-secret-dal"; import { TCertificateAuthoritySecretDALFactory } from "./certificate-authority-secret-dal";
import { TRotateCaCrlTriggerDTO } from "./certificate-authority-types"; import { TRotateCaCrlTriggerDTO } from "./certificate-authority-types";
@@ -21,15 +24,19 @@ type TCertificateAuthorityQueueFactoryDep = {
certificateAuthorityCrlDAL: TCertificateAuthorityCrlDALFactory; certificateAuthorityCrlDAL: TCertificateAuthorityCrlDALFactory;
certificateAuthoritySecretDAL: TCertificateAuthoritySecretDALFactory; certificateAuthoritySecretDAL: TCertificateAuthoritySecretDALFactory;
certificateDAL: TCertificateDALFactory; certificateDAL: TCertificateDALFactory;
projectDAL: Pick<TProjectDALFactory, "findProjectBySlug" | "findOne" | "updateById" | "findById" | "transaction">;
kmsService: Pick<TKmsServiceFactory, "generateKmsKey" | "encrypt" | "decrypt">;
queueService: TQueueServiceFactory; queueService: TQueueServiceFactory;
}; };
export type TCertificateAuthorityQueueFactory = ReturnType<typeof certificateAuthorityQueueFactory>; export type TCertificateAuthorityQueueFactory = ReturnType<typeof certificateAuthorityQueueFactory>;
export const certificateAuthorityQueueFactory = ({ export const certificateAuthorityQueueFactory = ({
// certificateAuthorityCrlDAL, certificateAuthorityCrlDAL,
// certificateAuthorityDAL, certificateAuthorityDAL,
// certificateAuthoritySecretDAL, certificateAuthoritySecretDAL,
// certificateDAL, certificateDAL,
projectDAL,
kmsService,
queueService queueService
}: TCertificateAuthorityQueueFactoryDep) => { }: TCertificateAuthorityQueueFactoryDep) => {
// TODO 1: auto-periodic rotation // TODO 1: auto-periodic rotation
@@ -64,55 +71,69 @@ export const certificateAuthorityQueueFactory = ({
); );
}; };
// queueService.start(QueueName.CaCrlRotation, async (job) => { queueService.start(QueueName.CaCrlRotation, async (job) => {
// const { caId } = job.data; const { caId } = job.data;
// logger.info(`secretReminderQueue.process: [secretDocument=${caId}]`); logger.info(`secretReminderQueue.process: [secretDocument=${caId}]`);
// 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 caKeys = await certificateAuthoritySecretDAL.findOne({ caId: ca.id });
// const alg = keyAlgorithmToAlgCfg(ca.keyAlgorithm as CertKeyAlgorithm); const alg = keyAlgorithmToAlgCfg(ca.keyAlgorithm as CertKeyAlgorithm);
// const skObj = crypto.createPrivateKey({ key: caKeys.sk, format: "pem", type: "pkcs8" });
// const sk = await crypto.subtle.importKey("pkcs8", skObj.export({ format: "der", type: "pkcs8" }), alg, true, [
// "sign"
// ]);
// const revokedCerts = await certificateDAL.find({ const keyId = await getProjectKmsCertificateKeyId({
// caId: ca.id, projectId: ca.projectId,
// status: CertStatus.REVOKED projectDAL,
// }); kmsService
});
// const crl = await x509.X509CrlGenerator.create({ const privateKey = await kmsService.decrypt({
// issuer: ca.dn, kmsId: keyId,
// thisUpdate: new Date(), cipherTextBlob: caKeys.encryptedPrivateKey
// nextUpdate: new Date("2025/12/12"), // TODO: depends on configured rebuild interval });
// entries: revokedCerts.map((revokedCert) => {
// return {
// serialNumber: revokedCert.serialNumber,
// revocationDate: new Date(revokedCert.revokedAt as Date),
// reason: revokedCert.revocationReason as number,
// invalidity: new Date("2022/01/01"),
// issuer: ca.dn
// };
// }),
// signingAlgorithm: alg,
// signingKey: sk
// });
// const base64crl = crl.toString("base64"); const skObj = crypto.createPrivateKey({ key: privateKey, format: "der", type: "pkcs8" });
// const crlPem = `-----BEGIN X509 CRL-----\n${base64crl.match(/.{1,64}/g)?.join("\n")}\n-----END X509 CRL-----`; const sk = await crypto.subtle.importKey("pkcs8", skObj.export({ format: "der", type: "pkcs8" }), alg, true, [
"sign"
]);
// await certificateAuthorityCrlDAL.update( const revokedCerts = await certificateDAL.find({
// { caId: ca.id,
// caId: ca.id status: CertStatus.REVOKED
// }, });
// {
// crl: crlPem // TODO: encrypt const crl = await x509.X509CrlGenerator.create({
// } issuer: ca.dn,
// ); thisUpdate: new Date(),
// }); nextUpdate: new Date("2025/12/12"), // TODO: depends on configured rebuild interval
entries: revokedCerts.map((revokedCert) => {
return {
serialNumber: revokedCert.serialNumber,
revocationDate: new Date(revokedCert.revokedAt as Date),
reason: revokedCert.revocationReason as number,
invalidity: new Date("2022/01/01"),
issuer: ca.dn
};
}),
signingAlgorithm: alg,
signingKey: sk
});
const { cipherTextBlob: encryptedCrl } = await kmsService.encrypt({
kmsId: keyId,
plainText: Buffer.from(new Uint8Array(crl.rawData))
});
await certificateAuthorityCrlDAL.update(
{
caId: ca.id
},
{
encryptedCrl
}
);
});
queueService.listen(QueueName.CaCrlRotation, "failed", (job, err) => { queueService.listen(QueueName.CaCrlRotation, "failed", (job, err) => {
logger.error(err, "Failed to rotate CA CRL %s", job?.id); logger.error(err, "Failed to rotate CA CRL %s", job?.id);
@@ -193,10 +193,15 @@ export const certificateAuthorityServiceFactory = ({
tx tx
); );
const { cipherTextBlob: encryptedCrl } = await kmsService.encrypt({
kmsId: keyId,
plainText: Buffer.alloc(0)
});
await certificateAuthorityCrlDAL.create( await certificateAuthorityCrlDAL.create(
{ {
caId: ca.id, caId: ca.id,
crl: "", // TODO: encrypt encryptedCrl,
ttl: 60 // in minutes ttl: 60 // in minutes
}, },
tx tx
@@ -944,18 +949,23 @@ export const certificateAuthorityServiceFactory = ({
signingKey: sk signingKey: sk
}); });
const base64crl = crl.toString("base64"); const { cipherTextBlob: encryptedCrl } = await kmsService.encrypt({
const crlPem = `-----BEGIN X509 CRL-----\n${base64crl.match(/.{1,64}/g)?.join("\n")}\n-----END X509 CRL-----`; kmsId: keyId,
plainText: Buffer.from(new Uint8Array(crl.rawData))
});
await certificateAuthorityCrlDAL.update( await certificateAuthorityCrlDAL.update(
{ {
caId: ca.id caId: ca.id
}, },
{ {
crl: crlPem // TODO: encrypt encryptedCrl
} }
); );
const base64crl = crl.toString("base64");
const crlPem = `-----BEGIN X509 CRL-----\n${base64crl.match(/.{1,64}/g)?.join("\n")}\n-----END X509 CRL-----`;
return { return {
crl: crlPem crl: crlPem
}; };