Link cert mgmt to kms

This commit is contained in:
Tuan Dang
2024-06-09 17:46:16 -04:00
parent 2cbf471beb
commit 931119f6ea
19 changed files with 434 additions and 173 deletions
+7 -7
View File
@@ -41,9 +41,9 @@ import {
TCertificateAuthorityCrl, TCertificateAuthorityCrl,
TCertificateAuthorityCrlInsert, TCertificateAuthorityCrlInsert,
TCertificateAuthorityCrlUpdate, TCertificateAuthorityCrlUpdate,
TCertificateAuthoritySk, TCertificateAuthoritySecret,
TCertificateAuthoritySkInsert, TCertificateAuthoritySecretInsert,
TCertificateAuthoritySkUpdate, TCertificateAuthoritySecretUpdate,
TCertificateCerts, TCertificateCerts,
TCertificateCertsInsert, TCertificateCertsInsert,
TCertificateCertsUpdate, TCertificateCertsUpdate,
@@ -288,10 +288,10 @@ declare module "knex/types/tables" {
TCertificateAuthorityCertsInsert, TCertificateAuthorityCertsInsert,
TCertificateAuthorityCertsUpdate TCertificateAuthorityCertsUpdate
>; >;
[TableName.CertificateAuthoritySk]: Knex.CompositeTableType< [TableName.CertificateAuthoritySecret]: Knex.CompositeTableType<
TCertificateAuthoritySk, TCertificateAuthoritySecret,
TCertificateAuthoritySkInsert, TCertificateAuthoritySecretInsert,
TCertificateAuthoritySkUpdate TCertificateAuthoritySecretUpdate
>; >;
[TableName.CertificateAuthorityCrl]: Knex.CompositeTableType< [TableName.CertificateAuthorityCrl]: Knex.CompositeTableType<
TCertificateAuthorityCrl, TCertificateAuthorityCrl,
@@ -4,6 +4,16 @@ import { TableName } from "../schemas";
import { createOnUpdateTrigger, dropOnUpdateTrigger } from "../utils"; import { createOnUpdateTrigger, dropOnUpdateTrigger } from "../utils";
export async function up(knex: Knex): Promise<void> { export async function up(knex: Knex): Promise<void> {
if (await knex.schema.hasTable(TableName.Project)) {
const doesProjectCertificateKeyIdExist = await knex.schema.hasColumn(TableName.Project, "kmsCertificateKeyId");
await knex.schema.alterTable(TableName.Project, (t) => {
if (!doesProjectCertificateKeyIdExist) {
t.uuid("kmsCertificateKeyId").nullable();
t.foreign("kmsCertificateKeyId").references("id").inTable(TableName.KmsKey);
}
});
}
if (!(await knex.schema.hasTable(TableName.CertificateAuthority))) { if (!(await knex.schema.hasTable(TableName.CertificateAuthority))) {
await knex.schema.createTable(TableName.CertificateAuthority, (t) => { await knex.schema.createTable(TableName.CertificateAuthority, (t) => {
t.uuid("id", { primaryKey: true }).defaultTo(knex.fn.uuid()); t.uuid("id", { primaryKey: true }).defaultTo(knex.fn.uuid());
@@ -35,22 +45,20 @@ export async function up(knex: Knex): Promise<void> {
await knex.schema.createTable(TableName.CertificateAuthorityCert, (t) => { await knex.schema.createTable(TableName.CertificateAuthorityCert, (t) => {
t.uuid("id", { primaryKey: true }).defaultTo(knex.fn.uuid()); t.uuid("id", { primaryKey: true }).defaultTo(knex.fn.uuid());
t.timestamps(true, true, true); t.timestamps(true, true, true);
t.uuid("caId").notNullable().unique(); // TODO: consider that cert can be rotated so may be multiple / non-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("certificate").notNullable(); // TODO: encrypt t.binary("encryptedCertificate").notNullable();
t.text("certificateChain").notNullable(); // TODO: encrypt t.binary("encryptedCertificateChain").notNullable();
}); });
} }
// TODO: consider renaming this to CertificateAuthoritySecret if (!(await knex.schema.hasTable(TableName.CertificateAuthoritySecret))) {
if (!(await knex.schema.hasTable(TableName.CertificateAuthoritySk))) { await knex.schema.createTable(TableName.CertificateAuthoritySecret, (t) => {
await knex.schema.createTable(TableName.CertificateAuthoritySk, (t) => {
t.uuid("id", { primaryKey: true }).defaultTo(knex.fn.uuid()); t.uuid("id", { primaryKey: true }).defaultTo(knex.fn.uuid());
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("pk").notNullable(); // TODO: encrypt t.binary("encryptedPrivateKey").notNullable();
t.text("sk").notNullable(); // TODO: encrypt
}); });
} }
@@ -89,19 +97,27 @@ export async function up(knex: Knex): Promise<void> {
t.timestamps(true, true, true); t.timestamps(true, true, true);
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.text("certificate").notNullable(); // TODO: encrypt t.binary("encryptedCertificate").notNullable();
t.text("certificateChain").notNullable(); // TODO: encrypt t.binary("encryptedCertificateChain").notNullable();
}); });
} }
await createOnUpdateTrigger(knex, TableName.CertificateAuthority); await createOnUpdateTrigger(knex, TableName.CertificateAuthority);
await createOnUpdateTrigger(knex, TableName.CertificateAuthorityCert); await createOnUpdateTrigger(knex, TableName.CertificateAuthorityCert);
await createOnUpdateTrigger(knex, TableName.CertificateAuthoritySk); await createOnUpdateTrigger(knex, TableName.CertificateAuthoritySecret);
await createOnUpdateTrigger(knex, TableName.Certificate); await createOnUpdateTrigger(knex, TableName.Certificate);
await createOnUpdateTrigger(knex, TableName.CertificateCert); await createOnUpdateTrigger(knex, TableName.CertificateCert);
} }
export async function down(knex: Knex): Promise<void> { export async function down(knex: Knex): Promise<void> {
// project
if (await knex.schema.hasTable(TableName.Project)) {
const doesProjectCertificateKeyIdExist = await knex.schema.hasColumn(TableName.Project, "kmsCertificateKeyId");
await knex.schema.alterTable(TableName.Project, (t) => {
if (doesProjectCertificateKeyIdExist) t.dropColumn("kmsCertificateKeyId");
});
}
// certificates // certificates
await knex.schema.dropTableIfExists(TableName.CertificateCert); await knex.schema.dropTableIfExists(TableName.CertificateCert);
await dropOnUpdateTrigger(knex, TableName.CertificateCert); await dropOnUpdateTrigger(knex, TableName.CertificateCert);
@@ -110,8 +126,8 @@ export async function down(knex: Knex): Promise<void> {
await dropOnUpdateTrigger(knex, TableName.Certificate); await dropOnUpdateTrigger(knex, TableName.Certificate);
// certificate authorities // certificate authorities
await knex.schema.dropTableIfExists(TableName.CertificateAuthoritySk); await knex.schema.dropTableIfExists(TableName.CertificateAuthoritySecret);
await dropOnUpdateTrigger(knex, TableName.CertificateAuthoritySk); await dropOnUpdateTrigger(knex, TableName.CertificateAuthoritySecret);
await knex.schema.dropTableIfExists(TableName.CertificateAuthorityCrl); await knex.schema.dropTableIfExists(TableName.CertificateAuthorityCrl);
await dropOnUpdateTrigger(knex, TableName.CertificateAuthorityCrl); await dropOnUpdateTrigger(knex, TableName.CertificateAuthorityCrl);
@@ -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 CertificateAuthorityCertsSchema = z.object({ export const CertificateAuthorityCertsSchema = z.object({
@@ -12,8 +14,8 @@ export const CertificateAuthorityCertsSchema = z.object({
createdAt: z.date(), createdAt: z.date(),
updatedAt: z.date(), updatedAt: z.date(),
caId: z.string().uuid(), caId: z.string().uuid(),
certificate: z.string(), encryptedCertificate: zodBuffer,
certificateChain: z.string() encryptedCertificateChain: zodBuffer
}); });
export type TCertificateAuthorityCerts = z.infer<typeof CertificateAuthorityCertsSchema>; export type TCertificateAuthorityCerts = z.infer<typeof CertificateAuthorityCertsSchema>;
@@ -0,0 +1,27 @@
// Code generated by automation script, DO NOT EDIT.
// Automated by pulling database and generating zod schema
// To update. Just run npm run generate:schema
// Written by akhilmhdh.
import { z } from "zod";
import { zodBuffer } from "@app/lib/zod";
import { TImmutableDBKeys } from "./models";
export const CertificateAuthoritySecretSchema = z.object({
id: z.string().uuid(),
createdAt: z.date(),
updatedAt: z.date(),
caId: z.string().uuid(),
encryptedPrivateKey: zodBuffer
});
export type TCertificateAuthoritySecret = z.infer<typeof CertificateAuthoritySecretSchema>;
export type TCertificateAuthoritySecretInsert = Omit<
z.input<typeof CertificateAuthoritySecretSchema>,
TImmutableDBKeys
>;
export type TCertificateAuthoritySecretUpdate = Partial<
Omit<z.input<typeof CertificateAuthoritySecretSchema>, TImmutableDBKeys>
>;
@@ -1,23 +0,0 @@
// Code generated by automation script, DO NOT EDIT.
// Automated by pulling database and generating zod schema
// To update. Just run npm run generate:schema
// Written by akhilmhdh.
import { z } from "zod";
import { TImmutableDBKeys } from "./models";
export const CertificateAuthoritySkSchema = z.object({
id: z.string().uuid(),
createdAt: z.date(),
updatedAt: z.date(),
caId: z.string().uuid(),
pk: z.string(),
sk: z.string()
});
export type TCertificateAuthoritySk = z.infer<typeof CertificateAuthoritySkSchema>;
export type TCertificateAuthoritySkInsert = Omit<z.input<typeof CertificateAuthoritySkSchema>, TImmutableDBKeys>;
export type TCertificateAuthoritySkUpdate = Partial<
Omit<z.input<typeof CertificateAuthoritySkSchema>, TImmutableDBKeys>
>;
+4 -2
View File
@@ -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 CertificateCertsSchema = z.object({ export const CertificateCertsSchema = z.object({
@@ -12,8 +14,8 @@ 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(),
certificate: z.string(), encryptedCertificate: zodBuffer,
certificateChain: z.string() encryptedCertificateChain: zodBuffer
}); });
export type TCertificateCerts = z.infer<typeof CertificateCertsSchema>; export type TCertificateCerts = z.infer<typeof CertificateCertsSchema>;
+1 -1
View File
@@ -11,7 +11,7 @@ export * from "./backup-private-key";
export * from "./certificate-authorities"; export * from "./certificate-authorities";
export * from "./certificate-authority-certs"; export * from "./certificate-authority-certs";
export * from "./certificate-authority-crl"; export * from "./certificate-authority-crl";
export * from "./certificate-authority-sk"; export * from "./certificate-authority-secret";
export * from "./certificate-certs"; export * from "./certificate-certs";
export * from "./certificate-secrets"; export * from "./certificate-secrets";
export * from "./certificates"; export * from "./certificates";
+1 -1
View File
@@ -4,7 +4,7 @@ export enum TableName {
Users = "users", Users = "users",
CertificateAuthority = "certificate_authorities", CertificateAuthority = "certificate_authorities",
CertificateAuthorityCert = "certificate_authority_certs", CertificateAuthorityCert = "certificate_authority_certs",
CertificateAuthoritySk = "certificate_authority_sk", CertificateAuthoritySecret = "certificate_authority_secret",
CertificateAuthorityCrl = "certificate_authority_crl", CertificateAuthorityCrl = "certificate_authority_crl",
Certificate = "certificates", Certificate = "certificates",
CertificateCert = "certificate_certs", CertificateCert = "certificate_certs",
+2 -1
View File
@@ -16,7 +16,8 @@ export const ProjectsSchema = z.object({
createdAt: z.date(), createdAt: z.date(),
updatedAt: z.date(), updatedAt: z.date(),
version: z.number().default(1), version: z.number().default(1),
upgradeStatus: z.string().nullable().optional() upgradeStatus: z.string().nullable().optional(),
kmsCertificateKeyId: z.string().uuid().nullable().optional()
}); });
export type TProjects = z.infer<typeof ProjectsSchema>; export type TProjects = z.infer<typeof ProjectsSchema>;
+7 -4
View File
@@ -78,8 +78,8 @@ import { certificateAuthorityCertDALFactory } from "@app/services/certificate-au
import { certificateAuthorityCrlDALFactory } from "@app/services/certificate-authority/certificate-authority-crl-dal"; import { certificateAuthorityCrlDALFactory } from "@app/services/certificate-authority/certificate-authority-crl-dal";
import { certificateAuthorityDALFactory } from "@app/services/certificate-authority/certificate-authority-dal"; import { certificateAuthorityDALFactory } from "@app/services/certificate-authority/certificate-authority-dal";
import { certificateAuthorityQueueFactory } from "@app/services/certificate-authority/certificate-authority-queue"; import { certificateAuthorityQueueFactory } from "@app/services/certificate-authority/certificate-authority-queue";
import { certificateAuthoritySecretDALFactory } from "@app/services/certificate-authority/certificate-authority-secret-dal";
import { certificateAuthorityServiceFactory } from "@app/services/certificate-authority/certificate-authority-service"; import { certificateAuthorityServiceFactory } from "@app/services/certificate-authority/certificate-authority-service";
import { certificateAuthoritySkDALFactory } from "@app/services/certificate-authority/certificate-authority-sk-dal";
import { groupProjectDALFactory } from "@app/services/group-project/group-project-dal"; import { groupProjectDALFactory } from "@app/services/group-project/group-project-dal";
import { groupProjectMembershipRoleDALFactory } from "@app/services/group-project/group-project-membership-role-dal"; import { groupProjectMembershipRoleDALFactory } from "@app/services/group-project/group-project-membership-role-dal";
import { groupProjectServiceFactory } from "@app/services/group-project/group-project-service"; import { groupProjectServiceFactory } from "@app/services/group-project/group-project-service";
@@ -517,7 +517,7 @@ export const registerRoutes = async (
const certificateAuthorityDAL = certificateAuthorityDALFactory(db); const certificateAuthorityDAL = certificateAuthorityDALFactory(db);
const certificateAuthorityCertDAL = certificateAuthorityCertDALFactory(db); const certificateAuthorityCertDAL = certificateAuthorityCertDALFactory(db);
const certificateAuthoritySkDAL = certificateAuthoritySkDALFactory(db); const certificateAuthoritySecretDAL = certificateAuthoritySecretDALFactory(db);
const certificateAuthorityCrlDAL = certificateAuthorityCrlDALFactory(db); const certificateAuthorityCrlDAL = certificateAuthorityCrlDALFactory(db);
const certificateDAL = certificateDALFactory(db); const certificateDAL = certificateDALFactory(db);
@@ -527,13 +527,15 @@ export const registerRoutes = async (
certificateDAL, certificateDAL,
certificateCertDAL, certificateCertDAL,
certificateAuthorityDAL, certificateAuthorityDAL,
projectDAL,
kmsService,
permissionService permissionService
}); });
const certificateAuthorityQueue = certificateAuthorityQueueFactory({ const certificateAuthorityQueue = certificateAuthorityQueueFactory({
certificateAuthorityCrlDAL, certificateAuthorityCrlDAL,
certificateAuthorityDAL, certificateAuthorityDAL,
certificateAuthoritySkDAL, certificateAuthoritySecretDAL,
certificateDAL, certificateDAL,
queueService queueService
}); });
@@ -541,12 +543,13 @@ export const registerRoutes = async (
const certificateAuthorityService = certificateAuthorityServiceFactory({ const certificateAuthorityService = certificateAuthorityServiceFactory({
certificateAuthorityDAL, certificateAuthorityDAL,
certificateAuthorityCertDAL, certificateAuthorityCertDAL,
certificateAuthoritySkDAL, certificateAuthoritySecretDAL,
certificateAuthorityCrlDAL, certificateAuthorityCrlDAL,
certificateAuthorityQueue, certificateAuthorityQueue,
certificateDAL, certificateDAL,
certificateCertDAL, certificateCertDAL,
projectDAL, projectDAL,
kmsService,
permissionService permissionService
}); });
@@ -13,7 +13,7 @@ export const certificateAuthorityDALFactory = (db: TDbClient) => {
const result: { const result: {
caId: string; caId: string;
parentCaId?: string; parentCaId?: string;
certificate: string; certificate: Buffer;
}[] = await db }[] = await db
.withRecursive("cte", (cte) => { .withRecursive("cte", (cte) => {
void cte void cte
@@ -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: string[] = result.map((row) => row.certificate); const certChain: Buffer[] = result.map((row) => row.certificate);
return certChain; return certChain;
} catch (error) { } catch (error) {
throw new DatabaseError({ error, name: "BuildCertificateChain" }); throw new DatabaseError({ error, name: "BuildCertificateChain" });
@@ -1,35 +1,35 @@
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 { 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 { TCertificateAuthoritySkDALFactory } from "./certificate-authority-sk-dal"; import { TCertificateAuthoritySecretDALFactory } from "./certificate-authority-secret-dal";
import { TRotateCaCrlTriggerDTO } from "./certificate-authority-types"; import { TRotateCaCrlTriggerDTO } from "./certificate-authority-types";
type TCertificateAuthorityQueueFactoryDep = { type TCertificateAuthorityQueueFactoryDep = {
// TODO: Pick // TODO: Pick
certificateAuthorityDAL: TCertificateAuthorityDALFactory; certificateAuthorityDAL: TCertificateAuthorityDALFactory;
certificateAuthorityCrlDAL: TCertificateAuthorityCrlDALFactory; certificateAuthorityCrlDAL: TCertificateAuthorityCrlDALFactory;
certificateAuthoritySkDAL: TCertificateAuthoritySkDALFactory; certificateAuthoritySecretDAL: TCertificateAuthoritySecretDALFactory;
certificateDAL: TCertificateDALFactory; certificateDAL: TCertificateDALFactory;
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,
certificateAuthoritySkDAL, // certificateAuthoritySecretDAL,
certificateDAL, // certificateDAL,
queueService queueService
}: TCertificateAuthorityQueueFactoryDep) => { }: TCertificateAuthorityQueueFactoryDep) => {
// TODO 1: auto-periodic rotation // TODO 1: auto-periodic rotation
@@ -64,55 +64,55 @@ 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 certificateAuthoritySkDAL.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 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, [ // const sk = await crypto.subtle.importKey("pkcs8", skObj.export({ format: "der", type: "pkcs8" }), alg, true, [
"sign" // "sign"
]); // ]);
const revokedCerts = await certificateDAL.find({ // const revokedCerts = await certificateDAL.find({
caId: ca.id, // caId: ca.id,
status: CertStatus.REVOKED // status: CertStatus.REVOKED
}); // });
const crl = await x509.X509CrlGenerator.create({ // const crl = await x509.X509CrlGenerator.create({
issuer: ca.dn, // issuer: ca.dn,
thisUpdate: new Date(), // thisUpdate: new Date(),
nextUpdate: new Date("2025/12/12"), // TODO: depends on configured rebuild interval // nextUpdate: new Date("2025/12/12"), // TODO: depends on configured rebuild interval
entries: revokedCerts.map((revokedCert) => { // entries: revokedCerts.map((revokedCert) => {
return { // return {
serialNumber: revokedCert.serialNumber, // serialNumber: revokedCert.serialNumber,
revocationDate: new Date(revokedCert.revokedAt as Date), // revocationDate: new Date(revokedCert.revokedAt as Date),
reason: revokedCert.revocationReason as number, // reason: revokedCert.revocationReason as number,
invalidity: new Date("2022/01/01"), // invalidity: new Date("2022/01/01"),
issuer: ca.dn // issuer: ca.dn
}; // };
}), // }),
signingAlgorithm: alg, // signingAlgorithm: alg,
signingKey: sk // signingKey: sk
}); // });
const base64crl = crl.toString("base64"); // const base64crl = crl.toString("base64");
const crlPem = `-----BEGIN X509 CRL-----\n${base64crl.match(/.{1,64}/g)?.join("\n")}\n-----END X509 CRL-----`; // const crlPem = `-----BEGIN X509 CRL-----\n${base64crl.match(/.{1,64}/g)?.join("\n")}\n-----END X509 CRL-----`;
await certificateAuthorityCrlDAL.update( // await certificateAuthorityCrlDAL.update(
{ // {
caId: ca.id // caId: ca.id
}, // },
{ // {
crl: crlPem // TODO: encrypt // crl: crlPem // TODO: encrypt
} // }
); // );
}); // });
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);
@@ -0,0 +1,10 @@
import { TDbClient } from "@app/db";
import { TableName } from "@app/db/schemas";
import { ormify } from "@app/lib/knex";
export type TCertificateAuthoritySecretDALFactory = ReturnType<typeof certificateAuthoritySecretDALFactory>;
export const certificateAuthoritySecretDALFactory = (db: TDbClient) => {
const caSecretOrm = ormify(db, TableName.CertificateAuthoritySecret);
return caSecretOrm;
};
@@ -8,7 +8,9 @@ import { ProjectPermissionActions, ProjectPermissionSub } from "@app/ee/services
import { BadRequestError } from "@app/lib/errors"; import { BadRequestError } from "@app/lib/errors";
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 { TKmsServiceFactory } from "@app/services/kms/kms-service";
import { TProjectDALFactory } from "@app/services/project/project-dal"; import { TProjectDALFactory } from "@app/services/project/project-dal";
import { getProjectKmsCertificateKeyId } from "@app/services/project/project-fns";
import { CertKeyAlgorithm, CertStatus } from "../certificate/certificate-types"; import { CertKeyAlgorithm, CertStatus } from "../certificate/certificate-types";
import { TCertificateAuthorityCertDALFactory } from "./certificate-authority-cert-dal"; import { TCertificateAuthorityCertDALFactory } from "./certificate-authority-cert-dal";
@@ -16,7 +18,7 @@ import { TCertificateAuthorityCrlDALFactory } from "./certificate-authority-crl-
import { TCertificateAuthorityDALFactory } from "./certificate-authority-dal"; import { TCertificateAuthorityDALFactory } from "./certificate-authority-dal";
import { createDistinguishedName, keyAlgorithmToAlgCfg } from "./certificate-authority-fns"; import { createDistinguishedName, keyAlgorithmToAlgCfg } from "./certificate-authority-fns";
import { TCertificateAuthorityQueueFactory } from "./certificate-authority-queue"; import { TCertificateAuthorityQueueFactory } from "./certificate-authority-queue";
import { TCertificateAuthoritySkDALFactory } from "./certificate-authority-sk-dal"; import { TCertificateAuthoritySecretDALFactory } from "./certificate-authority-secret-dal";
import { import {
CaStatus, CaStatus,
CaType, CaType,
@@ -39,25 +41,29 @@ type TCertificateAuthorityServiceFactoryDep = {
"transaction" | "create" | "findById" | "updateById" | "deleteById" | "findOne" | "buildCertificateChain" "transaction" | "create" | "findById" | "updateById" | "deleteById" | "findOne" | "buildCertificateChain"
>; >;
certificateAuthorityCertDAL: Pick<TCertificateAuthorityCertDALFactory, "create" | "findOne" | "transaction">; certificateAuthorityCertDAL: Pick<TCertificateAuthorityCertDALFactory, "create" | "findOne" | "transaction">;
certificateAuthoritySkDAL: Pick<TCertificateAuthoritySkDALFactory, "create" | "findOne">; certificateAuthoritySecretDAL: Pick<TCertificateAuthoritySecretDALFactory, "create" | "findOne">;
certificateAuthorityCrlDAL: Pick<TCertificateAuthorityCrlDALFactory, "create" | "findOne" | "update">; certificateAuthorityCrlDAL: Pick<TCertificateAuthorityCrlDALFactory, "create" | "findOne" | "update">;
certificateAuthorityQueue: TCertificateAuthorityQueueFactory; // TODO: Pick certificateAuthorityQueue: TCertificateAuthorityQueueFactory; // TODO: Pick
certificateDAL: Pick<TCertificateDALFactory, "transaction" | "create" | "find">; certificateDAL: Pick<TCertificateDALFactory, "transaction" | "create" | "find">;
certificateCertDAL: Pick<TCertificateCertDALFactory, "create">; certificateCertDAL: Pick<TCertificateCertDALFactory, "create">;
projectDAL: Pick<TProjectDALFactory, "findProjectBySlug">; projectDAL: Pick<TProjectDALFactory, "findProjectBySlug" | "findOne" | "updateById" | "findById" | "transaction">;
kmsService: Pick<TKmsServiceFactory, "generateKmsKey" | "encrypt" | "decrypt">;
permissionService: Pick<TPermissionServiceFactory, "getProjectPermission">; permissionService: Pick<TPermissionServiceFactory, "getProjectPermission">;
}; };
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,
certificateAuthoritySkDAL, certificateAuthoritySecretDAL,
certificateAuthorityCrlDAL, certificateAuthorityCrlDAL,
certificateDAL, certificateDAL,
certificateCertDAL, certificateCertDAL,
projectDAL, projectDAL,
kmsService,
permissionService permissionService
}: TCertificateAuthorityServiceFactoryDep) => { }: TCertificateAuthorityServiceFactoryDep) => {
/** /**
@@ -109,12 +115,6 @@ export const certificateAuthorityServiceFactory = ({
const alg = keyAlgorithmToAlgCfg(keyAlgorithm); const alg = keyAlgorithmToAlgCfg(keyAlgorithm);
const keys = await crypto.subtle.generateKey(alg, true, ["sign", "verify"]); const keys = await crypto.subtle.generateKey(alg, true, ["sign", "verify"]);
// https://nodejs.org/api/crypto.html#static-method-keyobjectfromkey
const skObj = KeyObject.from(keys.privateKey);
const sk = skObj.export({ format: "pem", type: "pkcs8" }) as string;
const pkObj = KeyObject.from(keys.publicKey);
const pk = pkObj.export({ format: "pem", type: "spki" }) as string;
const newCa = await certificateAuthorityDAL.transaction(async (tx) => { const newCa = await certificateAuthorityDAL.transaction(async (tx) => {
const notBeforeDate = notBefore ? new Date(notBefore) : new Date(); const notBeforeDate = notBefore ? new Date(notBefore) : new Date();
@@ -149,6 +149,12 @@ export const certificateAuthorityServiceFactory = ({
// TODO: create CRL // TODO: create CRL
const keyId = await getProjectKmsCertificateKeyId({
projectId: project.id,
projectDAL,
kmsService
});
if (type === CaType.ROOT) { if (type === CaType.ROOT) {
// note: self-signed cert only applicable for root CA // note: self-signed cert only applicable for root CA
@@ -167,12 +173,22 @@ export const certificateAuthorityServiceFactory = ({
await x509.SubjectKeyIdentifierExtension.create(keys.publicKey) await x509.SubjectKeyIdentifierExtension.create(keys.publicKey)
] ]
}); });
const certificate = cert.toString("pem");
const { cipherTextBlob: encryptedCertificate } = await kmsService.encrypt({
kmsId: keyId,
plainText: Buffer.from(new Uint8Array(cert.rawData))
});
const { cipherTextBlob: encryptedCertificateChain } = await kmsService.encrypt({
kmsId: keyId,
plainText: Buffer.alloc(0)
});
await certificateAuthorityCertDAL.create( await certificateAuthorityCertDAL.create(
{ {
caId: ca.id, caId: ca.id,
certificate, // TODO: encrypt encryptedCertificate,
certificateChain: "" // TODO: encrypt encryptedCertificateChain
}, },
tx tx
); );
@@ -187,11 +203,21 @@ export const certificateAuthorityServiceFactory = ({
); );
} }
await certificateAuthoritySkDAL.create( // https://nodejs.org/api/crypto.html#static-method-keyobjectfromkey
const skObj = KeyObject.from(keys.privateKey);
const { cipherTextBlob: encryptedPrivateKey } = await kmsService.encrypt({
kmsId: keyId,
plainText: skObj.export({
type: "pkcs8",
format: "der"
})
});
await certificateAuthoritySecretDAL.create(
{ {
caId: ca.id, caId: ca.id,
pk, // TODO: encrypt encryptedPrivateKey
sk // TODO: encrypt
}, },
tx tx
); );
@@ -290,16 +316,27 @@ 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 certificateAuthoritySkDAL.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 keyId = await getProjectKmsCertificateKeyId({
const pkObj = crypto.createPublicKey({ key: caKeys.pk, format: "pem", type: "spki" }); projectId: ca.projectId,
projectDAL,
kmsService
});
const privateKey = await kmsService.decrypt({
kmsId: keyId,
cipherTextBlob: caKeys.encryptedPrivateKey
});
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"
]); ]);
const pkObj = crypto.createPublicKey(skObj);
const pk = await crypto.subtle.importKey("spki", pkObj.export({ format: "der", type: "spki" }), alg, true, [ const pk = await crypto.subtle.importKey("spki", pkObj.export({ format: "der", type: "spki" }), alg, true, [
"verify" "verify"
]); ]);
@@ -344,11 +381,28 @@ export const certificateAuthorityServiceFactory = ({
); );
const caCert = await certificateAuthorityCertDAL.findOne({ caId: ca.id }); const caCert = await certificateAuthorityCertDAL.findOne({ caId: ca.id });
const certObj = new x509.X509Certificate(caCert.certificate);
const keyId = await getProjectKmsCertificateKeyId({
projectId: ca.projectId,
projectDAL,
kmsService
});
const decryptedCaCert = await kmsService.decrypt({
kmsId: keyId,
cipherTextBlob: caCert.encryptedCertificate
});
const certObj = new x509.X509Certificate(decryptedCaCert);
const decryptedChain = await kmsService.decrypt({
kmsId: keyId,
cipherTextBlob: caCert.encryptedCertificateChain
});
return { return {
certificate: caCert.certificate, certificate: certObj.toString("pem"),
certificateChain: caCert.certificateChain, certificateChain: decryptedChain.toString("utf-8"),
serialNumber: certObj.serialNumber serialNumber: certObj.serialNumber
}; };
}; };
@@ -388,15 +442,31 @@ export const certificateAuthorityServiceFactory = ({
const alg = keyAlgorithmToAlgCfg(ca.keyAlgorithm as CertKeyAlgorithm); const alg = keyAlgorithmToAlgCfg(ca.keyAlgorithm as CertKeyAlgorithm);
const caCert = await certificateAuthorityCertDAL.findOne({ caId: ca.id }); const keyId = await getProjectKmsCertificateKeyId({
const caKeys = await certificateAuthoritySkDAL.findOne({ caId: ca.id }); projectId: ca.projectId,
projectDAL,
kmsService
});
const skObj = crypto.createPrivateKey({ key: caKeys.sk, format: "pem", type: "pkcs8" }); const caCert = await certificateAuthorityCertDAL.findOne({ caId: ca.id });
const caKeys = await certificateAuthoritySecretDAL.findOne({ caId: ca.id });
const privateKey = await kmsService.decrypt({
kmsId: keyId,
cipherTextBlob: caKeys.encryptedPrivateKey
});
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"
]); ]);
const certObj = new x509.X509Certificate(caCert.certificate); const decryptedCaCert = await kmsService.decrypt({
kmsId: keyId,
cipherTextBlob: caCert.encryptedCertificate
});
const certObj = new x509.X509Certificate(decryptedCaCert);
const csrObj = new x509.Pkcs10CertificateRequest(csr); const csrObj = new x509.Pkcs10CertificateRequest(csr);
// check path length constraint // check path length constraint
@@ -455,11 +525,22 @@ export const certificateAuthorityServiceFactory = ({
}); });
const chain = await certificateAuthorityDAL.buildCertificateChain(caId); 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 chainCertObj = new x509.X509Certificate(decryptedCaChainCert);
return chainCertObj.toString("pem");
})
);
return { return {
certificate: intermediateCert.toString("pem"), certificate: intermediateCert.toString("pem"),
issuingCaCertificate: caCert.certificate, issuingCaCertificate: certObj.toString("pem"),
certificateChain: chain.join("\n"), certificateChain: decryptedChain.join("\n"),
serialNumber: intermediateCert.serialNumber serialNumber: intermediateCert.serialNumber
}; };
}; };
@@ -520,12 +601,28 @@ export const certificateAuthorityServiceFactory = ({
dn: parentCertSubject dn: parentCertSubject
}); });
const keyId = await getProjectKmsCertificateKeyId({
projectId: ca.projectId,
projectDAL,
kmsService
});
const { cipherTextBlob: encryptedCertificate } = await kmsService.encrypt({
kmsId: keyId,
plainText: Buffer.from(new Uint8Array(certObj.rawData))
});
const { cipherTextBlob: encryptedCertificateChain } = await kmsService.encrypt({
kmsId: keyId,
plainText: Buffer.from(certificateChain)
});
await certificateAuthorityCertDAL.transaction(async (tx) => { await certificateAuthorityCertDAL.transaction(async (tx) => {
await certificateAuthorityCertDAL.create( await certificateAuthorityCertDAL.create(
{ {
caId: ca.id, caId: ca.id,
certificate, // TODO: encrypt encryptedCertificate,
certificateChain // TODO: encrypt encryptedCertificateChain
}, },
tx tx
); );
@@ -569,18 +666,34 @@ export const certificateAuthorityServiceFactory = ({
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Create, ProjectPermissionSub.Certificates); ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Create, ProjectPermissionSub.Certificates);
if (ca.status === CaStatus.DISABLED) throw new BadRequestError({ message: "CA is disabled" });
const caCert = await certificateAuthorityCertDAL.findOne({ caId: ca.id }); const caCert = await certificateAuthorityCertDAL.findOne({ caId: ca.id });
if (!caCert) throw new BadRequestError({ message: "CA does not have a certificate installed" }); if (!caCert) throw new BadRequestError({ message: "CA does not have a certificate installed" });
if (ca.status === CaStatus.DISABLED) throw new BadRequestError({ message: "CA is disabled" }); const keyId = await getProjectKmsCertificateKeyId({
projectId: ca.projectId,
projectDAL,
kmsService
});
const caCertObj = new x509.X509Certificate(caCert.certificate); const decryptedCaCert = await kmsService.decrypt({
kmsId: keyId,
cipherTextBlob: caCert.encryptedCertificate
});
const caCertObj = new x509.X509Certificate(decryptedCaCert);
const alg = keyAlgorithmToAlgCfg(ca.keyAlgorithm as CertKeyAlgorithm); const alg = keyAlgorithmToAlgCfg(ca.keyAlgorithm as CertKeyAlgorithm);
const caKeys = await certificateAuthoritySkDAL.findOne({ caId: ca.id }); const caKeys = await certificateAuthoritySecretDAL.findOne({ caId: ca.id });
const caSkObj = crypto.createPrivateKey({ key: caKeys.sk, format: "pem", type: "pkcs8" }); const privateKey = await kmsService.decrypt({
kmsId: keyId,
cipherTextBlob: caKeys.encryptedPrivateKey
});
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"
]); ]);
@@ -646,6 +759,28 @@ export const certificateAuthorityServiceFactory = ({
const chain = await certificateAuthorityDAL.buildCertificateChain(caId); 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({
kmsId: keyId,
plainText: Buffer.from(decryptedChain.join("\n"))
});
await certificateDAL.transaction(async (tx) => { await certificateDAL.transaction(async (tx) => {
const cert = await certificateDAL.create( const cert = await certificateDAL.create(
{ {
@@ -662,8 +797,8 @@ export const certificateAuthorityServiceFactory = ({
await certificateCertDAL.create( await certificateCertDAL.create(
{ {
certId: cert.id, certId: cert.id,
certificate: leafCert.toString("pem"), // TODO: encrypt encryptedCertificate,
certificateChain: chain.join("\n") // TODO: encrypt encryptedCertificateChain
}, },
tx tx
); );
@@ -673,8 +808,8 @@ export const certificateAuthorityServiceFactory = ({
return { return {
certificate: leafCert.toString("pem"), certificate: leafCert.toString("pem"),
certificateChain: chain.join("\n"), certificateChain: decryptedChain.join("\n"),
issuingCaCertificate: caCert.certificate, issuingCaCertificate: caCertObj.toString("pem"),
privateKey: skLeaf, privateKey: skLeaf,
serialNumber serialNumber
}; };
@@ -700,10 +835,22 @@ export const certificateAuthorityServiceFactory = ({
ProjectPermissionSub.CertificateAuthorities ProjectPermissionSub.CertificateAuthorities
); );
const caKeys = await certificateAuthoritySkDAL.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 keyId = await getProjectKmsCertificateKeyId({
projectId: ca.projectId,
projectDAL,
kmsService
});
const privateKey = await kmsService.decrypt({
kmsId: keyId,
cipherTextBlob: caKeys.encryptedPrivateKey
});
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"
]); ]);
@@ -755,10 +902,22 @@ export const certificateAuthorityServiceFactory = ({
ProjectPermissionSub.CertificateAuthorities ProjectPermissionSub.CertificateAuthorities
); );
const caKeys = await certificateAuthoritySkDAL.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 keyId = await getProjectKmsCertificateKeyId({
projectId: ca.projectId,
projectDAL,
kmsService
});
const privateKey = await kmsService.decrypt({
kmsId: keyId,
cipherTextBlob: caKeys.encryptedPrivateKey
});
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"
]); ]);
@@ -1,10 +0,0 @@
import { TDbClient } from "@app/db";
import { TableName } from "@app/db/schemas";
import { ormify } from "@app/lib/knex";
export type TCertificateAuthoritySkDALFactory = ReturnType<typeof certificateAuthoritySkDALFactory>;
export const certificateAuthoritySkDALFactory = (db: TDbClient) => {
const caSkOrm = ormify(db, TableName.CertificateAuthoritySk);
return caSkOrm;
};
@@ -6,6 +6,9 @@ import { ProjectPermissionActions, ProjectPermissionSub } from "@app/ee/services
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 { 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 { TProjectDALFactory } from "@app/services/project/project-dal";
import { getProjectKmsCertificateKeyId } from "@app/services/project/project-fns";
import { revocationReasonToCrlCode } from "./certificate-fns"; import { revocationReasonToCrlCode } from "./certificate-fns";
import { CertStatus, TDeleteCertDTO, TGetCertCertDTO, TGetCertDTO, TRevokeCertDTO } from "./certificate-types"; import { CertStatus, TDeleteCertDTO, TGetCertCertDTO, TGetCertDTO, TRevokeCertDTO } from "./certificate-types";
@@ -14,6 +17,8 @@ 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">;
projectDAL: Pick<TProjectDALFactory, "findOne" | "updateById" | "findById" | "transaction">;
kmsService: Pick<TKmsServiceFactory, "generateKmsKey" | "encrypt" | "decrypt">;
permissionService: Pick<TPermissionServiceFactory, "getProjectPermission">; permissionService: Pick<TPermissionServiceFactory, "getProjectPermission">;
}; };
@@ -23,6 +28,8 @@ export const certificateServiceFactory = ({
certificateDAL, certificateDAL,
certificateCertDAL, certificateCertDAL,
certificateAuthorityDAL, certificateAuthorityDAL,
projectDAL,
kmsService,
permissionService permissionService
}: TCertificateServiceFactoryDep) => { }: TCertificateServiceFactoryDep) => {
const getCert = async ({ serialNumber, actorId, actorAuthMethod, actor, actorOrgId }: TGetCertDTO) => { const getCert = async ({ serialNumber, actorId, actorAuthMethod, actor, actorOrgId }: TGetCertDTO) => {
@@ -113,11 +120,28 @@ export const certificateServiceFactory = ({
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Read, ProjectPermissionSub.Certificates); ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Read, ProjectPermissionSub.Certificates);
const certCert = await certificateCertDAL.findOne({ certId: cert.id }); const certCert = await certificateCertDAL.findOne({ certId: cert.id });
const certObj = new x509.X509Certificate(certCert.certificate);
const keyId = await getProjectKmsCertificateKeyId({
projectId: ca.projectId,
projectDAL,
kmsService
});
const decryptedCert = await kmsService.decrypt({
kmsId: keyId,
cipherTextBlob: certCert.encryptedCertificate
});
const decryptedChain = await kmsService.decrypt({
kmsId: keyId,
cipherTextBlob: certCert.encryptedCertificateChain
});
const certObj = new x509.X509Certificate(decryptedCert);
return { return {
certificate: certCert.certificate, certificate: certObj.toString("pem"),
certificateChain: certCert.certificateChain, certificateChain: decryptedChain.toString("utf-8"),
serialNumber: certObj.serialNumber serialNumber: certObj.serialNumber
}; };
}; };
+12 -9
View File
@@ -29,19 +29,22 @@ export const kmsServiceFactory = ({ kmsDAL, kmsRootConfigDAL, keyStore }: TKmsSe
let ROOT_ENCRYPTION_KEY = Buffer.alloc(0); let ROOT_ENCRYPTION_KEY = Buffer.alloc(0);
// this is used symmetric encryption // this is used symmetric encryption
const generateKmsKey = async ({ scopeId, scopeType, isReserved = true }: TGenerateKMSDTO) => { const generateKmsKey = async ({ scopeId, scopeType, isReserved = true, tx }: TGenerateKMSDTO) => {
const cipher = symmetricCipherService(SymmetricEncryption.AES_GCM_256); const cipher = symmetricCipherService(SymmetricEncryption.AES_GCM_256);
const kmsKeyMaterial = randomSecureBytes(32); const kmsKeyMaterial = randomSecureBytes(32);
const encryptedKeyMaterial = cipher.encrypt(kmsKeyMaterial, ROOT_ENCRYPTION_KEY); const encryptedKeyMaterial = cipher.encrypt(kmsKeyMaterial, ROOT_ENCRYPTION_KEY);
const { encryptedKey, ...doc } = await kmsDAL.create({ const { encryptedKey, ...doc } = await kmsDAL.create(
version: 1, {
encryptedKey: encryptedKeyMaterial, version: 1,
encryptionAlgorithm: SymmetricEncryption.AES_GCM_256, encryptedKey: encryptedKeyMaterial,
isReserved, encryptionAlgorithm: SymmetricEncryption.AES_GCM_256,
orgId: scopeType === "org" ? scopeId : undefined, isReserved,
projectId: scopeType === "project" ? scopeId : undefined orgId: scopeType === "org" ? scopeId : undefined,
}); projectId: scopeType === "project" ? scopeId : undefined
},
tx
);
return doc; return doc;
}; };
+3
View File
@@ -1,7 +1,10 @@
import { Knex } from "knex";
export type TGenerateKMSDTO = { export type TGenerateKMSDTO = {
scopeType: "project" | "org"; scopeType: "project" | "org";
scopeId: string; scopeId: string;
isReserved?: boolean; isReserved?: boolean;
tx?: Knex;
}; };
export type TEncryptWithKmsDTO = { export type TEncryptWithKmsDTO = {
@@ -1,6 +1,9 @@
import crypto from "crypto"; import crypto from "crypto";
import { decryptAsymmetric, encryptAsymmetric } from "@app/lib/crypto"; import { decryptAsymmetric, encryptAsymmetric } from "@app/lib/crypto";
import { BadRequestError } from "@app/lib/errors";
import { TKmsServiceFactory } from "@app/services/kms/kms-service";
import { TProjectDALFactory } from "@app/services/project/project-dal";
import { AddUserToWsDTO } from "./project-types"; import { AddUserToWsDTO } from "./project-types";
@@ -49,3 +52,44 @@ export const createProjectKey = ({ publicKey, privateKey, plainProjectKey }: TCr
return { key: encryptedProjectKey, iv: encryptedProjectKeyIv }; return { key: encryptedProjectKey, iv: encryptedProjectKeyIv };
}; };
export const getProjectKmsCertificateKeyId = async ({
projectId,
projectDAL,
kmsService
}: {
projectId: string;
projectDAL: Pick<TProjectDALFactory, "findOne" | "updateById" | "findById" | "transaction">;
kmsService: Pick<TKmsServiceFactory, "generateKmsKey">;
}) => {
const keyId = await projectDAL.transaction(async (tx) => {
const project = await projectDAL.findOne({ id: projectId }, tx);
if (!project) {
throw new BadRequestError({ message: "Project not found" });
}
if (!project.kmsCertificateKeyId) {
// create default kms key for certificate service
const key = await kmsService.generateKmsKey({
scopeId: projectId,
scopeType: "project",
isReserved: true,
tx
});
await projectDAL.updateById(
projectId,
{
kmsCertificateKeyId: key.id
},
tx
);
return key.id;
}
return project.kmsCertificateKeyId;
});
return keyId;
};