Add preliminary dynamically generated CRLs on fetching CA CRL

This commit is contained in:
Tuan Dang
2024-05-30 15:45:34 -07:00
parent 45fdd4ebc2
commit 2937a46943
25 changed files with 560 additions and 84 deletions
@@ -1,4 +1,5 @@
import { CertKeyAlgorithm, TDNParts } from "./certificate-authority-types";
import { CertKeyAlgorithm } from "../certificate/certificate-types";
import { TDNParts } from "./certificate-authority-types";
export const createDistinguishedName = (parts: TDNParts) => {
const dnParts = [];
@@ -10,7 +10,7 @@ import { TCertificateCertDALFactory } from "@app/services/certificate/certificat
import { TCertificateDALFactory } from "@app/services/certificate/certificate-dal";
import { TProjectDALFactory } from "@app/services/project/project-dal";
import { TCertStatus } from "../certificate/certificate-types";
import { CertKeyAlgorithm, CertStatus } from "../certificate/certificate-types";
import { TCertificateAuthorityCertDALFactory } from "./certificate-authority-cert-dal";
import { TCertificateAuthorityDALFactory } from "./certificate-authority-dal";
import { createDistinguishedName, keyAlgorithmToAlgCfg } from "./certificate-authority-fns";
@@ -18,12 +18,12 @@ import { TCertificateAuthoritySkDALFactory } from "./certificate-authority-sk-da
import {
CaStatus,
CaType,
CertKeyAlgorithm,
TCreateCaDTO,
TDeleteCaDTO,
TGetCaCertDTO,
TGetCaCsrDTO,
TGetCaDTO,
TGetCrl,
TImportCertToCaDTO,
TIssueCertFromCaDTO,
TSignIntermediateDTO,
@@ -37,7 +37,7 @@ type TCertificateAuthorityServiceFactoryDep = {
>;
certificateAuthorityCertDAL: Pick<TCertificateAuthorityCertDALFactory, "create" | "findOne" | "transaction">;
certificateAuthoritySkDAL: Pick<TCertificateAuthoritySkDALFactory, "create" | "findOne">;
certificateDAL: Pick<TCertificateDALFactory, "transaction" | "create">;
certificateDAL: Pick<TCertificateDALFactory, "transaction" | "create" | "find">;
certificateCertDAL: Pick<TCertificateCertDALFactory, "create">;
projectDAL: Pick<TProjectDALFactory, "findProjectBySlug">;
permissionService: Pick<TPermissionServiceFactory, "getProjectPermission">;
@@ -633,7 +633,7 @@ export const certificateAuthorityServiceFactory = ({
const cert = await certificateDAL.create(
{
caId: ca.id,
status: TCertStatus.ACTIVE,
status: CertStatus.ACTIVE,
commonName,
serialNumber,
notBefore: notBeforeDate,
@@ -663,6 +663,64 @@ export const certificateAuthorityServiceFactory = ({
};
};
/**
* Return the Certificate Revocation List (CRL) for the CA
*/
const getCaCrl = async ({ caId, actorId, actorAuthMethod, actor, actorOrgId }: TGetCrl) => {
const ca = await certificateAuthorityDAL.findById(caId);
if (!ca) throw new BadRequestError({ message: "CA not found" });
const { permission } = await permissionService.getProjectPermission(
actor,
actorId,
ca.projectId,
actorAuthMethod,
actorOrgId
);
ForbiddenError.from(permission).throwUnlessCan(
ProjectPermissionActions.Read,
ProjectPermissionSub.CertificateAuthorities
);
const caKeys = await certificateAuthoritySkDAL.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 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"),
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 crlPem = `-----BEGIN X509 CRL-----\n${base64crl.match(/.{1,64}/g)?.join("\n")}\n-----END X509 CRL-----`;
return {
crl: crlPem
};
};
return {
createCa,
getCaById,
@@ -672,6 +730,7 @@ export const certificateAuthorityServiceFactory = ({
getCaCert,
signIntermediate,
importCertToCa,
issueCertFromCa
issueCertFromCa,
getCaCrl
};
};
@@ -1,5 +1,7 @@
import { TProjectPermission } from "@app/lib/types";
import { CertKeyAlgorithm } from "../certificate/certificate-types";
export enum CaType {
ROOT = "root",
INTERMEDIATE = "intermediate"
@@ -11,13 +13,6 @@ export enum CaStatus {
PENDING_CERTIFICATE = "pending-certificate"
}
export enum CertKeyAlgorithm {
RSA_2048 = "RSA_2048",
RSA_4096 = "RSA_4096",
ECDSA_P256 = "EC_prime256v1",
ECDSA_P384 = "EC_secp384r1"
}
export type TCreateCaDTO = {
projectSlug: string;
type: CaType;
@@ -76,6 +71,10 @@ export type TIssueCertFromCaDTO = {
notAfter?: string;
} & Omit<TProjectPermission, "projectId">;
export type TGetCrl = {
caId: string;
} & Omit<TProjectPermission, "projectId">;
export type TDNParts = {
commonName?: string;
organization?: string;
@@ -0,0 +1,26 @@
import * as x509 from "@peculiar/x509";
import { CrlReason } from "./certificate-types";
export const revocationReasonToCrlCode = (crlReason: CrlReason) => {
switch (crlReason) {
case CrlReason.KEY_COMPROMISE:
return x509.X509CrlReason.keyCompromise;
case CrlReason.CA_COMPROMISE:
return x509.X509CrlReason.cACompromise;
case CrlReason.AFFILIATION_CHANGED:
return x509.X509CrlReason.affiliationChanged;
case CrlReason.SUPERSEDED:
return x509.X509CrlReason.superseded;
case CrlReason.CESSATION_OF_OPERATION:
return x509.X509CrlReason.cessationOfOperation;
case CrlReason.CERTIFICATE_HOLD:
return x509.X509CrlReason.certificateHold;
case CrlReason.PRIVILEGE_WITHDRAWN:
return x509.X509CrlReason.privilegeWithdrawn;
case CrlReason.A_A_COMPROMISE:
return x509.X509CrlReason.aACompromise;
default:
return x509.X509CrlReason.unspecified;
}
};
@@ -7,10 +7,11 @@ import { TCertificateCertDALFactory } from "@app/services/certificate/certificat
import { TCertificateDALFactory } from "@app/services/certificate/certificate-dal";
import { TCertificateAuthorityDALFactory } from "@app/services/certificate-authority/certificate-authority-dal";
import { TDeleteCertDTO, TGetCertCertDTO, TGetCertDTO, TRevokeCertDTO } from "./certificate-types";
import { revocationReasonToCrlCode } from "./certificate-fns";
import { CertStatus, TDeleteCertDTO, TGetCertCertDTO, TGetCertDTO, TRevokeCertDTO } from "./certificate-types";
type TCertificateServiceFactoryDep = {
certificateDAL: Pick<TCertificateDALFactory, "findOne" | "deleteById">;
certificateDAL: Pick<TCertificateDALFactory, "findOne" | "deleteById" | "update">;
certificateCertDAL: Pick<TCertificateCertDALFactory, "findOne">;
certificateAuthorityDAL: Pick<TCertificateAuthorityDALFactory, "findById">;
permissionService: Pick<TPermissionServiceFactory, "getProjectPermission">;
@@ -59,7 +60,14 @@ export const certificateServiceFactory = ({
return deletedCert;
};
const revokeCert = async ({ serialNumber, actorId, actorAuthMethod, actor, actorOrgId }: TRevokeCertDTO) => {
const revokeCert = async ({
serialNumber,
revocationReason,
actorId,
actorAuthMethod,
actor,
actorOrgId
}: TRevokeCertDTO) => {
const cert = await certificateDAL.findOne({ serialNumber });
const ca = await certificateAuthorityDAL.findById(cert.caId);
@@ -72,14 +80,22 @@ export const certificateServiceFactory = ({
);
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Delete, ProjectPermissionSub.Certificates);
// WIP
// const revocationDate = new Date();
if (cert.status === CertStatus.REVOKED) throw new Error("Certificate already revoked");
// const serialNumber2 = crypto.randomBytes(16).toString("hex");
// const crlEntry = new x509.X509CrlEntry(serialNumber2, new Date(), []);
const revokedAt = new Date();
await certificateDAL.update(
{
id: cert.id
},
{
status: CertStatus.REVOKED,
revokedAt,
revocationReason: revocationReasonToCrlCode(revocationReason)
}
);
return {};
return { revokedAt };
};
const getCertCert = async ({ serialNumber, actorId, actorAuthMethod, actor, actorOrgId }: TGetCertCertDTO) => {
@@ -1,10 +1,30 @@
import { TProjectPermission } from "@app/lib/types";
export enum TCertStatus {
export enum CertStatus {
ACTIVE = "active",
REVOKED = "revoked"
}
export enum CertKeyAlgorithm {
RSA_2048 = "RSA_2048",
RSA_4096 = "RSA_4096",
ECDSA_P256 = "EC_prime256v1",
ECDSA_P384 = "EC_secp384r1"
}
export enum CrlReason {
UNSPECIFIED = "UNSPECIFIED",
KEY_COMPROMISE = "KEY_COMPROMISE",
CA_COMPROMISE = "CA_COMPROMISE",
AFFILIATION_CHANGED = "AFFILIATION_CHANGED",
SUPERSEDED = "SUPERSEDED",
CESSATION_OF_OPERATION = "CESSATION_OF_OPERATION",
CERTIFICATE_HOLD = "CERTIFICATE_HOLD",
// REMOVE_FROM_CRL = "REMOVE_FROM_CRL",
PRIVILEGE_WITHDRAWN = "PRIVILEGE_WITHDRAWN",
A_A_COMPROMISE = "A_A_COMPROMISE"
}
export type TGetCertDTO = {
serialNumber: string;
} & Omit<TProjectPermission, "projectId">;
@@ -15,6 +35,7 @@ export type TDeleteCertDTO = {
export type TRevokeCertDTO = {
serialNumber: string;
revocationReason: CrlReason;
} & Omit<TProjectPermission, "projectId">;
export type TGetCertCertDTO = {