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

View File

@@ -68,7 +68,7 @@ export async function up(knex: Knex): Promise<void> {
t.timestamps(true, true, true);
t.uuid("caId").notNullable().unique();
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
// TODO: consider type (crl or delta)
// TODO: rebuild interval

View File

@@ -5,6 +5,8 @@
import { z } from "zod";
import { zodBuffer } from "@app/lib/zod";
import { TImmutableDBKeys } from "./models";
export const CertificateAuthorityCrlSchema = z.object({
@@ -12,7 +14,7 @@ export const CertificateAuthorityCrlSchema = z.object({
createdAt: z.date(),
updatedAt: z.date(),
caId: z.string().uuid(),
crl: z.string(),
encryptedCrl: zodBuffer,
ttl: z.number()
});

View File

@@ -537,6 +537,8 @@ export const registerRoutes = async (
certificateAuthorityDAL,
certificateAuthoritySecretDAL,
certificateDAL,
projectDAL,
kmsService,
queueService
});

View File

@@ -13,17 +13,17 @@ export const certificateAuthorityDALFactory = (db: TDbClient) => {
const result: {
caId: string;
parentCaId?: string;
certificate: Buffer;
encryptedCertificate: Buffer;
}[] = await db
.withRecursive("cte", (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 })
.leftJoin({ cert: TableName.CertificateAuthorityCert }, "ca.id", "cert.caId")
.where("ca.id", caId)
.unionAll((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 })
.leftJoin({ cert: TableName.CertificateAuthorityCert }, "ca.id", "cert.caId")
.innerJoin("cte", "cte.parentCaId", "ca.id");
@@ -33,7 +33,7 @@ export const certificateAuthorityDALFactory = (db: TDbClient) => {
.from("cte");
// 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;
} catch (error) {
throw new DatabaseError({ error, name: "BuildCertificateChain" });

View File

@@ -1,17 +1,20 @@
// import * as x509 from "@peculiar/x509";
// import crypto from "crypto";
import * as x509 from "@peculiar/x509";
import crypto from "crypto";
import { getConfig } from "@app/lib/config/env";
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 { QueueJobs, QueueName, TQueueServiceFactory } from "@app/queue";
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 { 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 { TRotateCaCrlTriggerDTO } from "./certificate-authority-types";
@@ -21,15 +24,19 @@ type TCertificateAuthorityQueueFactoryDep = {
certificateAuthorityCrlDAL: TCertificateAuthorityCrlDALFactory;
certificateAuthoritySecretDAL: TCertificateAuthoritySecretDALFactory;
certificateDAL: TCertificateDALFactory;
projectDAL: Pick<TProjectDALFactory, "findProjectBySlug" | "findOne" | "updateById" | "findById" | "transaction">;
kmsService: Pick<TKmsServiceFactory, "generateKmsKey" | "encrypt" | "decrypt">;
queueService: TQueueServiceFactory;
};
export type TCertificateAuthorityQueueFactory = ReturnType<typeof certificateAuthorityQueueFactory>;
export const certificateAuthorityQueueFactory = ({
// certificateAuthorityCrlDAL,
// certificateAuthorityDAL,
// certificateAuthoritySecretDAL,
// certificateDAL,
certificateAuthorityCrlDAL,
certificateAuthorityDAL,
certificateAuthoritySecretDAL,
certificateDAL,
projectDAL,
kmsService,
queueService
}: TCertificateAuthorityQueueFactoryDep) => {
// TODO 1: auto-periodic rotation
@@ -64,55 +71,69 @@ export const certificateAuthorityQueueFactory = ({
);
};
// queueService.start(QueueName.CaCrlRotation, async (job) => {
// const { caId } = job.data;
// logger.info(`secretReminderQueue.process: [secretDocument=${caId}]`);
queueService.start(QueueName.CaCrlRotation, async (job) => {
const { caId } = job.data;
logger.info(`secretReminderQueue.process: [secretDocument=${caId}]`);
// const ca = await certificateAuthorityDAL.findById(caId);
// if (!ca) throw new BadRequestError({ message: "CA not found" });
const ca = await certificateAuthorityDAL.findById(caId);
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 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 alg = keyAlgorithmToAlgCfg(ca.keyAlgorithm as CertKeyAlgorithm);
// const revokedCerts = await certificateDAL.find({
// caId: ca.id,
// status: CertStatus.REVOKED
// });
const keyId = await getProjectKmsCertificateKeyId({
projectId: ca.projectId,
projectDAL,
kmsService
});
// 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 privateKey = await kmsService.decrypt({
kmsId: keyId,
cipherTextBlob: caKeys.encryptedPrivateKey
});
// const base64crl = crl.toString("base64");
// const crlPem = `-----BEGIN X509 CRL-----\n${base64crl.match(/.{1,64}/g)?.join("\n")}\n-----END X509 CRL-----`;
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"
]);
// await certificateAuthorityCrlDAL.update(
// {
// caId: ca.id
// },
// {
// crl: crlPem // TODO: encrypt
// }
// );
// });
const revokedCerts = await certificateDAL.find({
caId: ca.id,
status: CertStatus.REVOKED
});
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) => {
logger.error(err, "Failed to rotate CA CRL %s", job?.id);

View File

@@ -193,10 +193,15 @@ export const certificateAuthorityServiceFactory = ({
tx
);
const { cipherTextBlob: encryptedCrl } = await kmsService.encrypt({
kmsId: keyId,
plainText: Buffer.alloc(0)
});
await certificateAuthorityCrlDAL.create(
{
caId: ca.id,
crl: "", // TODO: encrypt
encryptedCrl,
ttl: 60 // in minutes
},
tx
@@ -944,18 +949,23 @@ export const certificateAuthorityServiceFactory = ({
signingKey: sk
});
const base64crl = crl.toString("base64");
const crlPem = `-----BEGIN X509 CRL-----\n${base64crl.match(/.{1,64}/g)?.join("\n")}\n-----END X509 CRL-----`;
const { cipherTextBlob: encryptedCrl } = await kmsService.encrypt({
kmsId: keyId,
plainText: Buffer.from(new Uint8Array(crl.rawData))
});
await certificateAuthorityCrlDAL.update(
{
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 {
crl: crlPem
};