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

View File

@@ -98,7 +98,6 @@ export async function up(knex: Knex): Promise<void> {
t.uuid("certId").notNullable().unique();
t.foreign("certId").references("id").inTable(TableName.Certificate).onDelete("CASCADE");
t.binary("encryptedCertificate").notNullable();
t.binary("encryptedCertificateChain").notNullable();
});
}

View File

@@ -14,8 +14,7 @@ export const CertificateCertsSchema = z.object({
createdAt: z.date(),
updatedAt: z.date(),
certId: z.string().uuid(),
encryptedCertificate: zodBuffer,
encryptedCertificateChain: zodBuffer
encryptedCertificate: zodBuffer
});
export type TCertificateCerts = z.infer<typeof CertificateCertsSchema>;

View File

@@ -527,6 +527,7 @@ export const registerRoutes = async (
certificateDAL,
certificateCertDAL,
certificateAuthorityDAL,
certificateAuthorityCertDAL,
projectDAL,
kmsService,
permissionService

View File

@@ -8,6 +8,7 @@ export type TCertificateAuthorityDALFactory = ReturnType<typeof certificateAutho
export const certificateAuthorityDALFactory = (db: TDbClient) => {
const caOrm = ormify(db, TableName.CertificateAuthority);
// note: not used
const buildCertificateChain = async (caId: string) => {
try {
const result: {

View File

@@ -78,7 +78,7 @@ export const certificateAuthorityQueueFactory = ({
const ca = await certificateAuthorityDAL.findById(caId);
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);
@@ -90,7 +90,7 @@ export const certificateAuthorityQueueFactory = ({
const privateKey = await kmsService.decrypt({
kmsId: keyId,
cipherTextBlob: caKeys.encryptedPrivateKey
cipherTextBlob: caSecret.encryptedPrivateKey
});
const skObj = crypto.createPrivateKey({ key: privateKey, format: "der", type: "pkcs8" });

View File

@@ -38,7 +38,7 @@ import {
type TCertificateAuthorityServiceFactoryDep = {
certificateAuthorityDAL: Pick<
TCertificateAuthorityDALFactory,
"transaction" | "create" | "findById" | "updateById" | "deleteById" | "findOne" | "buildCertificateChain"
"transaction" | "create" | "findById" | "updateById" | "deleteById" | "findOne"
>;
certificateAuthorityCertDAL: Pick<TCertificateAuthorityCertDALFactory, "create" | "findOne" | "transaction">;
certificateAuthoritySecretDAL: Pick<TCertificateAuthoritySecretDALFactory, "create" | "findOne">;
@@ -53,8 +53,6 @@ type TCertificateAuthorityServiceFactoryDep = {
export type TCertificateAuthorityServiceFactory = ReturnType<typeof certificateAuthorityServiceFactory>;
// TODO: reconsider build cert chain due to imported chains
export const certificateAuthorityServiceFactory = ({
certificateAuthorityDAL,
certificateAuthorityCertDAL,
@@ -321,9 +319,7 @@ export const certificateAuthorityServiceFactory = ({
const caCert = await certificateAuthorityCertDAL.findOne({ caId: ca.id });
if (caCert) throw new BadRequestError({ message: "CA already has a certificate installed" });
const caKeys = await certificateAuthoritySecretDAL.findOne({ caId: ca.id });
const alg = keyAlgorithmToAlgCfg(ca.keyAlgorithm as CertKeyAlgorithm);
const caSecret = await certificateAuthoritySecretDAL.findOne({ caId: ca.id });
const keyId = await getProjectKmsCertificateKeyId({
projectId: ca.projectId,
@@ -333,9 +329,10 @@ export const certificateAuthorityServiceFactory = ({
const privateKey = await kmsService.decrypt({
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 sk = await crypto.subtle.importKey("pkcs8", skObj.export({ format: "der", type: "pkcs8" }), alg, true, [
"sign"
@@ -414,7 +411,6 @@ export const certificateAuthorityServiceFactory = ({
/**
* Issue certificate to be imported back in for intermediate CA
* TODO: cannot chain to self
*/
const signIntermediate = async ({
caId,
@@ -454,11 +450,11 @@ export const certificateAuthorityServiceFactory = ({
});
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({
kmsId: keyId,
cipherTextBlob: caKeys.encryptedPrivateKey
cipherTextBlob: caSecret.encryptedPrivateKey
});
const skObj = crypto.createPrivateKey({ key: privateKey, format: "der", type: "pkcs8" });
@@ -471,11 +467,11 @@ export const certificateAuthorityServiceFactory = ({
cipherTextBlob: caCert.encryptedCertificate
});
const certObj = new x509.X509Certificate(decryptedCaCert);
const caCertObj = new x509.X509Certificate(decryptedCaCert);
const csrObj = new x509.Pkcs10CertificateRequest(csr);
// check path length constraint
const caPathLength = certObj.getExtension(x509.BasicConstraintsExtension)?.pathLength;
const caPathLength = caCertObj.getExtension(x509.BasicConstraintsExtension)?.pathLength;
if (caPathLength !== undefined) {
if (caPathLength === 0)
throw new BadRequestError({
@@ -490,8 +486,8 @@ export const certificateAuthorityServiceFactory = ({
const notBeforeDate = notBefore ? new Date(notBefore) : new Date();
const notAfterDate = new Date(notAfter);
const caCertNotBeforeDate = new Date(certObj.notBefore);
const caCertNotAfterDate = new Date(certObj.notAfter);
const caCertNotBeforeDate = new Date(caCertObj.notBefore);
const caCertNotAfterDate = new Date(caCertObj.notAfter);
// check not before constraint
if (notBeforeDate < caCertNotBeforeDate) {
@@ -509,7 +505,7 @@ export const certificateAuthorityServiceFactory = ({
const intermediateCert = await x509.X509CertificateGenerator.create({
serialNumber,
subject: csrObj.subject,
issuer: certObj.subject,
issuer: caCertObj.subject,
notBefore: notBeforeDate,
notAfter: notAfterDate,
signingKey: sk,
@@ -524,28 +520,22 @@ export const certificateAuthorityServiceFactory = ({
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)
]
});
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 caCertChain = await kmsService.decrypt({
kmsId: keyId,
cipherTextBlob: caCert.encryptedCertificateChain
});
const chainCertObj = new x509.X509Certificate(decryptedCaChainCert);
return chainCertObj.toString("pem");
})
);
const certificateChain = `${caCertObj.toString("pem")}\n${caCertChain.toString("utf-8")}`.trim();
return {
certificate: intermediateCert.toString("pem"),
issuingCaCertificate: certObj.toString("pem"),
certificateChain: decryptedChain.join("\n"),
issuingCaCertificate: caCertObj.toString("pem"),
certificateChain,
serialNumber: intermediateCert.serialNumber
};
};
@@ -689,15 +679,15 @@ export const certificateAuthorityServiceFactory = ({
const caCertObj = new x509.X509Certificate(decryptedCaCert);
const alg = keyAlgorithmToAlgCfg(ca.keyAlgorithm as CertKeyAlgorithm);
const caKeys = await certificateAuthoritySecretDAL.findOne({ caId: ca.id });
const caSecret = await certificateAuthoritySecretDAL.findOne({ caId: ca.id });
const privateKey = await kmsService.decrypt({
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 caSk = await crypto.subtle.importKey("pkcs8", caSkObj.export({ format: "der", type: "pkcs8" }), alg, true, [
"sign"
@@ -762,28 +752,14 @@ export const certificateAuthorityServiceFactory = ({
const skLeafObj = KeyObject.from(leafKeys.privateKey);
const skLeaf = skLeafObj.export({ format: "pem", type: "pkcs8" }) as string;
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({
const caCertChain = await kmsService.decrypt({
kmsId: keyId,
plainText: Buffer.from(decryptedChain.join("\n"))
cipherTextBlob: caCert.encryptedCertificateChain
});
await certificateDAL.transaction(async (tx) => {
@@ -802,8 +778,7 @@ export const certificateAuthorityServiceFactory = ({
await certificateCertDAL.create(
{
certId: cert.id,
encryptedCertificate,
encryptedCertificateChain
encryptedCertificate
},
tx
);
@@ -811,9 +786,11 @@ export const certificateAuthorityServiceFactory = ({
return cert;
});
const certificateChain = `${caCertObj.toString("pem")}\n${caCertChain.toString("utf-8")}`.trim();
return {
certificate: leafCert.toString("pem"),
certificateChain: decryptedChain.join("\n"),
certificateChain,
issuingCaCertificate: caCertObj.toString("pem"),
privateKey: skLeaf,
serialNumber
@@ -840,7 +817,7 @@ export const certificateAuthorityServiceFactory = ({
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);
@@ -852,7 +829,7 @@ export const certificateAuthorityServiceFactory = ({
const privateKey = await kmsService.decrypt({
kmsId: keyId,
cipherTextBlob: caKeys.encryptedPrivateKey
cipherTextBlob: caSecret.encryptedPrivateKey
});
const skObj = crypto.createPrivateKey({ key: privateKey, format: "der", type: "pkcs8" });
@@ -907,7 +884,7 @@ export const certificateAuthorityServiceFactory = ({
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);
@@ -919,7 +896,7 @@ export const certificateAuthorityServiceFactory = ({
const privateKey = await kmsService.decrypt({
kmsId: keyId,
cipherTextBlob: caKeys.encryptedPrivateKey
cipherTextBlob: caSecret.encryptedPrivateKey
});
const skObj = crypto.createPrivateKey({ key: privateKey, format: "der", type: "pkcs8" });

View File

@@ -5,6 +5,7 @@ import { TPermissionServiceFactory } from "@app/ee/services/permission/permissio
import { ProjectPermissionActions, ProjectPermissionSub } from "@app/ee/services/permission/project-permission";
import { TCertificateCertDALFactory } from "@app/services/certificate/certificate-cert-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 { TKmsServiceFactory } from "@app/services/kms/kms-service";
import { TProjectDALFactory } from "@app/services/project/project-dal";
@@ -17,6 +18,7 @@ type TCertificateServiceFactoryDep = {
certificateDAL: Pick<TCertificateDALFactory, "findOne" | "deleteById" | "update">;
certificateCertDAL: Pick<TCertificateCertDALFactory, "findOne">;
certificateAuthorityDAL: Pick<TCertificateAuthorityDALFactory, "findById">;
certificateAuthorityCertDAL: Pick<TCertificateAuthorityCertDALFactory, "findOne">;
projectDAL: Pick<TProjectDALFactory, "findOne" | "updateById" | "findById" | "transaction">;
kmsService: Pick<TKmsServiceFactory, "generateKmsKey" | "encrypt" | "decrypt">;
permissionService: Pick<TPermissionServiceFactory, "getProjectPermission">;
@@ -28,6 +30,7 @@ export const certificateServiceFactory = ({
certificateDAL,
certificateCertDAL,
certificateAuthorityDAL,
certificateAuthorityCertDAL,
projectDAL,
kmsService,
permissionService
@@ -108,6 +111,7 @@ export const certificateServiceFactory = ({
const getCertCert = async ({ serialNumber, actorId, actorAuthMethod, actor, actorOrgId }: TGetCertCertDTO) => {
const cert = await certificateDAL.findOne({ serialNumber });
const ca = await certificateAuthorityDAL.findById(cert.caId);
const caCert = await certificateAuthorityCertDAL.findOne({ caId: ca.id });
const { permission } = await permissionService.getProjectPermission(
actor,
@@ -132,16 +136,25 @@ export const certificateServiceFactory = ({
cipherTextBlob: certCert.encryptedCertificate
});
const decryptedChain = await kmsService.decrypt({
const caCertChain = await kmsService.decrypt({
kmsId: keyId,
cipherTextBlob: certCert.encryptedCertificateChain
cipherTextBlob: caCert.encryptedCertificateChain
});
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 {
certificate: certObj.toString("pem"),
certificateChain: decryptedChain.toString("utf-8"),
certificateChain,
serialNumber: certObj.serialNumber
};
};

View File

@@ -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:
```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>

View File

@@ -98,7 +98,7 @@ export const CertificateContent = ({
colorSchema="secondary"
className="group relative ml-2"
onClick={() => {
downloadTxtFile("certificate.pem", certificate);
downloadTxtFile("cert.pem", certificate);
}}
>
<FontAwesomeIcon icon={faDownload} />