mirror of
https://github.com/awatertrevi/infisical.git
synced 2026-10-11 13:29:12 +00:00
made certificates store PK and chain in relation to the main table, added /bundle endpoints, new audit log and permission entries
This commit is contained in:
@@ -215,6 +215,8 @@ export enum EventType {
|
|||||||
DELETE_CERT = "delete-cert",
|
DELETE_CERT = "delete-cert",
|
||||||
REVOKE_CERT = "revoke-cert",
|
REVOKE_CERT = "revoke-cert",
|
||||||
GET_CERT_BODY = "get-cert-body",
|
GET_CERT_BODY = "get-cert-body",
|
||||||
|
GET_CERT_PRIVATE_KEY = "get-cert-private-key",
|
||||||
|
GET_CERT_BUNDLE = "get-cert-bundle",
|
||||||
CREATE_PKI_ALERT = "create-pki-alert",
|
CREATE_PKI_ALERT = "create-pki-alert",
|
||||||
GET_PKI_ALERT = "get-pki-alert",
|
GET_PKI_ALERT = "get-pki-alert",
|
||||||
UPDATE_PKI_ALERT = "update-pki-alert",
|
UPDATE_PKI_ALERT = "update-pki-alert",
|
||||||
@@ -1719,6 +1721,24 @@ interface GetCertBody {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
interface GetCertPrivateKey {
|
||||||
|
type: EventType.GET_CERT_PRIVATE_KEY;
|
||||||
|
metadata: {
|
||||||
|
certId: string;
|
||||||
|
cn: string;
|
||||||
|
serialNumber: string;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
interface GetCertBundle {
|
||||||
|
type: EventType.GET_CERT_BUNDLE;
|
||||||
|
metadata: {
|
||||||
|
certId: string;
|
||||||
|
cn: string;
|
||||||
|
serialNumber: string;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
interface CreatePkiAlert {
|
interface CreatePkiAlert {
|
||||||
type: EventType.CREATE_PKI_ALERT;
|
type: EventType.CREATE_PKI_ALERT;
|
||||||
metadata: {
|
metadata: {
|
||||||
@@ -2691,6 +2711,8 @@ export type Event =
|
|||||||
| DeleteCert
|
| DeleteCert
|
||||||
| RevokeCert
|
| RevokeCert
|
||||||
| GetCertBody
|
| GetCertBody
|
||||||
|
| GetCertPrivateKey
|
||||||
|
| GetCertBundle
|
||||||
| CreatePkiAlert
|
| CreatePkiAlert
|
||||||
| GetPkiAlert
|
| GetPkiAlert
|
||||||
| UpdatePkiAlert
|
| UpdatePkiAlert
|
||||||
|
|||||||
@@ -129,6 +129,7 @@ export enum ProjectPermissionSub {
|
|||||||
Identity = "identity",
|
Identity = "identity",
|
||||||
CertificateAuthorities = "certificate-authorities",
|
CertificateAuthorities = "certificate-authorities",
|
||||||
Certificates = "certificates",
|
Certificates = "certificates",
|
||||||
|
CertificatePrivateKey = "certificate-private-key",
|
||||||
CertificateTemplates = "certificate-templates",
|
CertificateTemplates = "certificate-templates",
|
||||||
SshCertificateAuthorities = "ssh-certificate-authorities",
|
SshCertificateAuthorities = "ssh-certificate-authorities",
|
||||||
SshCertificates = "ssh-certificates",
|
SshCertificates = "ssh-certificates",
|
||||||
@@ -232,6 +233,7 @@ export type ProjectPermissionSet =
|
|||||||
]
|
]
|
||||||
| [ProjectPermissionActions, ProjectPermissionSub.CertificateAuthorities]
|
| [ProjectPermissionActions, ProjectPermissionSub.CertificateAuthorities]
|
||||||
| [ProjectPermissionActions, ProjectPermissionSub.Certificates]
|
| [ProjectPermissionActions, ProjectPermissionSub.Certificates]
|
||||||
|
| [ProjectPermissionActions, ProjectPermissionSub.CertificatePrivateKey]
|
||||||
| [ProjectPermissionActions, ProjectPermissionSub.CertificateTemplates]
|
| [ProjectPermissionActions, ProjectPermissionSub.CertificateTemplates]
|
||||||
| [ProjectPermissionActions, ProjectPermissionSub.SshCertificateAuthorities]
|
| [ProjectPermissionActions, ProjectPermissionSub.SshCertificateAuthorities]
|
||||||
| [ProjectPermissionActions, ProjectPermissionSub.SshCertificates]
|
| [ProjectPermissionActions, ProjectPermissionSub.SshCertificates]
|
||||||
@@ -480,6 +482,12 @@ const GeneralPermissionSchema = [
|
|||||||
"Describe what action an entity can take."
|
"Describe what action an entity can take."
|
||||||
)
|
)
|
||||||
}),
|
}),
|
||||||
|
z.object({
|
||||||
|
subject: z.literal(ProjectPermissionSub.CertificatePrivateKey).describe("The entity this permission pertains to."),
|
||||||
|
action: CASL_ACTION_SCHEMA_NATIVE_ENUM(ProjectPermissionActions).describe(
|
||||||
|
"Describe what action an entity can take."
|
||||||
|
)
|
||||||
|
}),
|
||||||
z.object({
|
z.object({
|
||||||
subject: z.literal(ProjectPermissionSub.CertificateTemplates).describe("The entity this permission pertains to."),
|
subject: z.literal(ProjectPermissionSub.CertificateTemplates).describe("The entity this permission pertains to."),
|
||||||
action: CASL_ACTION_SCHEMA_NATIVE_ENUM(ProjectPermissionActions).describe(
|
action: CASL_ACTION_SCHEMA_NATIVE_ENUM(ProjectPermissionActions).describe(
|
||||||
@@ -681,6 +689,7 @@ const buildAdminPermissionRules = () => {
|
|||||||
ProjectPermissionSub.IpAllowList,
|
ProjectPermissionSub.IpAllowList,
|
||||||
ProjectPermissionSub.CertificateAuthorities,
|
ProjectPermissionSub.CertificateAuthorities,
|
||||||
ProjectPermissionSub.Certificates,
|
ProjectPermissionSub.Certificates,
|
||||||
|
ProjectPermissionSub.CertificatePrivateKey,
|
||||||
ProjectPermissionSub.CertificateTemplates,
|
ProjectPermissionSub.CertificateTemplates,
|
||||||
ProjectPermissionSub.PkiAlerts,
|
ProjectPermissionSub.PkiAlerts,
|
||||||
ProjectPermissionSub.PkiCollections,
|
ProjectPermissionSub.PkiCollections,
|
||||||
|
|||||||
@@ -1580,7 +1580,8 @@ export const CERTIFICATES = {
|
|||||||
serialNumber: "The serial number of the certificate to get the certificate body and certificate chain for.",
|
serialNumber: "The serial number of the certificate to get the certificate body and certificate chain for.",
|
||||||
certificate: "The certificate body of the certificate.",
|
certificate: "The certificate body of the certificate.",
|
||||||
certificateChain: "The certificate chain of the certificate.",
|
certificateChain: "The certificate chain of the certificate.",
|
||||||
serialNumberRes: "The serial number of the certificate."
|
serialNumberRes: "The serial number of the certificate.",
|
||||||
|
privateKey: "The private key of the certificate."
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -260,6 +260,7 @@ import { registerSecretScannerGhApp } from "../plugins/secret-scanner";
|
|||||||
import { registerV1Routes } from "./v1";
|
import { registerV1Routes } from "./v1";
|
||||||
import { registerV2Routes } from "./v2";
|
import { registerV2Routes } from "./v2";
|
||||||
import { registerV3Routes } from "./v3";
|
import { registerV3Routes } from "./v3";
|
||||||
|
import { certificateSecretDALFactory } from "@app/services/certificate/certificate-secret-dal";
|
||||||
|
|
||||||
const histogram = monitorEventLoopDelay({ resolution: 20 });
|
const histogram = monitorEventLoopDelay({ resolution: 20 });
|
||||||
histogram.enable();
|
histogram.enable();
|
||||||
@@ -791,6 +792,7 @@ export const registerRoutes = async (
|
|||||||
|
|
||||||
const certificateDAL = certificateDALFactory(db);
|
const certificateDAL = certificateDALFactory(db);
|
||||||
const certificateBodyDAL = certificateBodyDALFactory(db);
|
const certificateBodyDAL = certificateBodyDALFactory(db);
|
||||||
|
const certificateSecretDAL = certificateSecretDALFactory(db);
|
||||||
|
|
||||||
const pkiAlertDAL = pkiAlertDALFactory(db);
|
const pkiAlertDAL = pkiAlertDALFactory(db);
|
||||||
const pkiCollectionDAL = pkiCollectionDALFactory(db);
|
const pkiCollectionDAL = pkiCollectionDALFactory(db);
|
||||||
@@ -799,6 +801,7 @@ export const registerRoutes = async (
|
|||||||
const certificateService = certificateServiceFactory({
|
const certificateService = certificateServiceFactory({
|
||||||
certificateDAL,
|
certificateDAL,
|
||||||
certificateBodyDAL,
|
certificateBodyDAL,
|
||||||
|
certificateSecretDAL,
|
||||||
certificateAuthorityDAL,
|
certificateAuthorityDAL,
|
||||||
certificateAuthorityCertDAL,
|
certificateAuthorityCertDAL,
|
||||||
certificateAuthorityCrlDAL,
|
certificateAuthorityCrlDAL,
|
||||||
@@ -858,6 +861,7 @@ export const registerRoutes = async (
|
|||||||
certificateAuthorityQueue,
|
certificateAuthorityQueue,
|
||||||
certificateDAL,
|
certificateDAL,
|
||||||
certificateBodyDAL,
|
certificateBodyDAL,
|
||||||
|
certificateSecretDAL,
|
||||||
pkiCollectionDAL,
|
pkiCollectionDAL,
|
||||||
pkiCollectionItemDAL,
|
pkiCollectionItemDAL,
|
||||||
projectDAL,
|
projectDAL,
|
||||||
|
|||||||
@@ -64,7 +64,7 @@ export const registerCertRouter = async (server: FastifyZodProvider) => {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
// TODO(andrey): In the future add support for other formats outside of PEM. Adding a "format" query param may be best.
|
// TODO: In the future add support for other formats outside of PEM (such as DER). Adding a "format" query param may be best.
|
||||||
server.route({
|
server.route({
|
||||||
method: "GET",
|
method: "GET",
|
||||||
url: "/:serialNumber/private-key",
|
url: "/:serialNumber/private-key",
|
||||||
@@ -96,7 +96,7 @@ export const registerCertRouter = async (server: FastifyZodProvider) => {
|
|||||||
...req.auditLogInfo,
|
...req.auditLogInfo,
|
||||||
projectId: ca.projectId,
|
projectId: ca.projectId,
|
||||||
event: {
|
event: {
|
||||||
type: EventType.GET_CERT,
|
type: EventType.GET_CERT_PRIVATE_KEY,
|
||||||
metadata: {
|
metadata: {
|
||||||
certId: cert.id,
|
certId: cert.id,
|
||||||
cn: cert.commonName,
|
cn: cert.commonName,
|
||||||
@@ -109,6 +109,62 @@ export const registerCertRouter = async (server: FastifyZodProvider) => {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// TODO: In the future add support for other formats outside of PEM (such as DER). Adding a "format" query param may be best.
|
||||||
|
server.route({
|
||||||
|
method: "GET",
|
||||||
|
url: "/:serialNumber/bundle",
|
||||||
|
config: {
|
||||||
|
rateLimit: readLimit
|
||||||
|
},
|
||||||
|
onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]),
|
||||||
|
schema: {
|
||||||
|
hide: false,
|
||||||
|
tags: [ApiDocsTags.PkiCertificates],
|
||||||
|
description: "Get certificate bundle including the certificate, chain, and private key.",
|
||||||
|
params: z.object({
|
||||||
|
serialNumber: z.string().trim().describe(CERTIFICATES.GET_CERT.serialNumber)
|
||||||
|
}),
|
||||||
|
response: {
|
||||||
|
200: z.object({
|
||||||
|
certificate: z.string().trim().describe(CERTIFICATES.GET_CERT.certificate),
|
||||||
|
certificateChain: z.string().trim().describe(CERTIFICATES.GET_CERT.certificateChain),
|
||||||
|
privateKey: z.string().trim().describe(CERTIFICATES.GET_CERT.certificateChain),
|
||||||
|
serialNumber: z.string().trim().describe(CERTIFICATES.GET_CERT.serialNumberRes)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
},
|
||||||
|
handler: async (req) => {
|
||||||
|
const { certificate, certificateChain, serialNumber, cert, ca, privateKey } =
|
||||||
|
await server.services.certificate.getCertBundle({
|
||||||
|
serialNumber: req.params.serialNumber,
|
||||||
|
actor: req.permission.type,
|
||||||
|
actorId: req.permission.id,
|
||||||
|
actorAuthMethod: req.permission.authMethod,
|
||||||
|
actorOrgId: req.permission.orgId
|
||||||
|
});
|
||||||
|
|
||||||
|
await server.services.auditLog.createAuditLog({
|
||||||
|
...req.auditLogInfo,
|
||||||
|
projectId: ca.projectId,
|
||||||
|
event: {
|
||||||
|
type: EventType.GET_CERT_BUNDLE,
|
||||||
|
metadata: {
|
||||||
|
certId: cert.id,
|
||||||
|
cn: cert.commonName,
|
||||||
|
serialNumber: cert.serialNumber
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return {
|
||||||
|
certificate,
|
||||||
|
certificateChain,
|
||||||
|
serialNumber,
|
||||||
|
privateKey
|
||||||
|
};
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
server.route({
|
server.route({
|
||||||
method: "POST",
|
method: "POST",
|
||||||
url: "/issue-certificate",
|
url: "/issue-certificate",
|
||||||
@@ -474,7 +530,7 @@ export const registerCertRouter = async (server: FastifyZodProvider) => {
|
|||||||
...req.auditLogInfo,
|
...req.auditLogInfo,
|
||||||
projectId: ca.projectId,
|
projectId: ca.projectId,
|
||||||
event: {
|
event: {
|
||||||
type: EventType.DELETE_CERT,
|
type: EventType.GET_CERT_BODY,
|
||||||
metadata: {
|
metadata: {
|
||||||
certId: cert.id,
|
certId: cert.id,
|
||||||
cn: cert.commonName,
|
cn: cert.commonName,
|
||||||
|
|||||||
@@ -21,6 +21,7 @@ import { TProjectDALFactory } from "@app/services/project/project-dal";
|
|||||||
import { getProjectKmsCertificateKeyId } from "@app/services/project/project-fns";
|
import { getProjectKmsCertificateKeyId } from "@app/services/project/project-fns";
|
||||||
|
|
||||||
import { TCertificateAuthorityCrlDALFactory } from "../../ee/services/certificate-authority-crl/certificate-authority-crl-dal";
|
import { TCertificateAuthorityCrlDALFactory } from "../../ee/services/certificate-authority-crl/certificate-authority-crl-dal";
|
||||||
|
import { TCertificateSecretDALFactory } from "../certificate/certificate-secret-dal";
|
||||||
import {
|
import {
|
||||||
CertExtendedKeyUsage,
|
CertExtendedKeyUsage,
|
||||||
CertExtendedKeyUsageOIDToName,
|
CertExtendedKeyUsageOIDToName,
|
||||||
@@ -75,6 +76,7 @@ type TCertificateAuthorityServiceFactoryDep = {
|
|||||||
certificateTemplateDAL: Pick<TCertificateTemplateDALFactory, "getById" | "find">;
|
certificateTemplateDAL: Pick<TCertificateTemplateDALFactory, "getById" | "find">;
|
||||||
certificateAuthorityQueue: TCertificateAuthorityQueueFactory; // TODO: Pick
|
certificateAuthorityQueue: TCertificateAuthorityQueueFactory; // TODO: Pick
|
||||||
certificateDAL: Pick<TCertificateDALFactory, "transaction" | "create" | "find">;
|
certificateDAL: Pick<TCertificateDALFactory, "transaction" | "create" | "find">;
|
||||||
|
certificateSecretDAL: Pick<TCertificateSecretDALFactory, "create">;
|
||||||
certificateBodyDAL: Pick<TCertificateBodyDALFactory, "create">;
|
certificateBodyDAL: Pick<TCertificateBodyDALFactory, "create">;
|
||||||
pkiCollectionDAL: Pick<TPkiCollectionDALFactory, "findById">;
|
pkiCollectionDAL: Pick<TPkiCollectionDALFactory, "findById">;
|
||||||
pkiCollectionItemDAL: Pick<TPkiCollectionItemDALFactory, "create">;
|
pkiCollectionItemDAL: Pick<TPkiCollectionItemDALFactory, "create">;
|
||||||
@@ -96,6 +98,7 @@ export const certificateAuthorityServiceFactory = ({
|
|||||||
certificateTemplateDAL,
|
certificateTemplateDAL,
|
||||||
certificateDAL,
|
certificateDAL,
|
||||||
certificateBodyDAL,
|
certificateBodyDAL,
|
||||||
|
certificateSecretDAL,
|
||||||
pkiCollectionDAL,
|
pkiCollectionDAL,
|
||||||
pkiCollectionItemDAL,
|
pkiCollectionItemDAL,
|
||||||
projectDAL,
|
projectDAL,
|
||||||
@@ -1373,6 +1376,23 @@ export const certificateAuthorityServiceFactory = ({
|
|||||||
const { cipherTextBlob: encryptedCertificate } = await kmsEncryptor({
|
const { cipherTextBlob: encryptedCertificate } = await kmsEncryptor({
|
||||||
plainText: Buffer.from(new Uint8Array(leafCert.rawData))
|
plainText: Buffer.from(new Uint8Array(leafCert.rawData))
|
||||||
});
|
});
|
||||||
|
const { cipherTextBlob: encryptedPrivateKey } = await kmsEncryptor({
|
||||||
|
plainText: Buffer.from(skLeaf)
|
||||||
|
});
|
||||||
|
|
||||||
|
const { caCert: issuingCaCertificate, caCertChain } = await getCaCertChain({
|
||||||
|
caCertId: caCert.id,
|
||||||
|
certificateAuthorityDAL,
|
||||||
|
certificateAuthorityCertDAL,
|
||||||
|
projectDAL,
|
||||||
|
kmsService
|
||||||
|
});
|
||||||
|
|
||||||
|
const certificateChainPem = `${issuingCaCertificate}\n${caCertChain}`.trim();
|
||||||
|
|
||||||
|
const { cipherTextBlob: encryptedCertificateChain } = await kmsEncryptor({
|
||||||
|
plainText: Buffer.from(certificateChainPem)
|
||||||
|
});
|
||||||
|
|
||||||
await certificateDAL.transaction(async (tx) => {
|
await certificateDAL.transaction(async (tx) => {
|
||||||
const cert = await certificateDAL.create(
|
const cert = await certificateDAL.create(
|
||||||
@@ -1396,7 +1416,16 @@ export const certificateAuthorityServiceFactory = ({
|
|||||||
await certificateBodyDAL.create(
|
await certificateBodyDAL.create(
|
||||||
{
|
{
|
||||||
certId: cert.id,
|
certId: cert.id,
|
||||||
encryptedCertificate
|
encryptedCertificate,
|
||||||
|
encryptedCertificateChain
|
||||||
|
},
|
||||||
|
tx
|
||||||
|
);
|
||||||
|
|
||||||
|
await certificateSecretDAL.create(
|
||||||
|
{
|
||||||
|
certId: cert.id,
|
||||||
|
encryptedPrivateKey
|
||||||
},
|
},
|
||||||
tx
|
tx
|
||||||
);
|
);
|
||||||
@@ -1414,17 +1443,9 @@ export const certificateAuthorityServiceFactory = ({
|
|||||||
return cert;
|
return cert;
|
||||||
});
|
});
|
||||||
|
|
||||||
const { caCert: issuingCaCertificate, caCertChain } = await getCaCertChain({
|
|
||||||
caCertId: caCert.id,
|
|
||||||
certificateAuthorityDAL,
|
|
||||||
certificateAuthorityCertDAL,
|
|
||||||
projectDAL,
|
|
||||||
kmsService
|
|
||||||
});
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
certificate: leafCert.toString("pem"),
|
certificate: leafCert.toString("pem"),
|
||||||
certificateChain: `${issuingCaCertificate}\n${caCertChain}`.trim(),
|
certificateChain: certificateChainPem,
|
||||||
issuingCaCertificate,
|
issuingCaCertificate,
|
||||||
privateKey: skLeaf,
|
privateKey: skLeaf,
|
||||||
serialNumber,
|
serialNumber,
|
||||||
|
|||||||
@@ -72,7 +72,6 @@ export const getCertificateCredentials = async ({
|
|||||||
projectDAL,
|
projectDAL,
|
||||||
kmsService
|
kmsService
|
||||||
});
|
});
|
||||||
|
|
||||||
const kmsDecryptor = await kmsService.decryptWithKmsKey({
|
const kmsDecryptor = await kmsService.decryptWithKmsKey({
|
||||||
kmsId: keyId
|
kmsId: keyId
|
||||||
});
|
});
|
||||||
@@ -80,7 +79,7 @@ export const getCertificateCredentials = async ({
|
|||||||
cipherTextBlob: certificateSecret.encryptedPrivateKey
|
cipherTextBlob: certificateSecret.encryptedPrivateKey
|
||||||
});
|
});
|
||||||
|
|
||||||
const skObj = crypto.createPrivateKey({ key: decryptedPrivateKey, format: "der", type: "pkcs8" });
|
const skObj = crypto.createPrivateKey({ key: decryptedPrivateKey, format: "pem", type: "pkcs8" });
|
||||||
const certPrivateKey = skObj.export({ format: "pem", type: "pkcs8" }).toString();
|
const certPrivateKey = skObj.export({ format: "pem", type: "pkcs8" }).toString();
|
||||||
|
|
||||||
const pkObj = crypto.createPublicKey(skObj);
|
const pkObj = crypto.createPublicKey(skObj);
|
||||||
|
|||||||
@@ -5,6 +5,6 @@ import { ormify } from "@app/lib/knex";
|
|||||||
export type TCertificateSecretDALFactory = ReturnType<typeof certificateSecretDALFactory>;
|
export type TCertificateSecretDALFactory = ReturnType<typeof certificateSecretDALFactory>;
|
||||||
|
|
||||||
export const certificateSecretDALFactory = (db: TDbClient) => {
|
export const certificateSecretDALFactory = (db: TDbClient) => {
|
||||||
const caSecretOrm = ormify(db, TableName.CertificateSecret);
|
const certSecretOrm = ormify(db, TableName.CertificateSecret);
|
||||||
return caSecretOrm;
|
return certSecretOrm;
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -17,7 +17,14 @@ import { getProjectKmsCertificateKeyId } from "@app/services/project/project-fns
|
|||||||
import { getCaCertChain, rebuildCaCrl } from "../certificate-authority/certificate-authority-fns";
|
import { getCaCertChain, rebuildCaCrl } from "../certificate-authority/certificate-authority-fns";
|
||||||
import { getCertificateCredentials, revocationReasonToCrlCode } from "./certificate-fns";
|
import { getCertificateCredentials, revocationReasonToCrlCode } from "./certificate-fns";
|
||||||
import { TCertificateSecretDALFactory } from "./certificate-secret-dal";
|
import { TCertificateSecretDALFactory } from "./certificate-secret-dal";
|
||||||
import { CertStatus, TDeleteCertDTO, TGetCertBodyDTO, TGetCertDTO, TRevokeCertDTO } from "./certificate-types";
|
import {
|
||||||
|
CertStatus,
|
||||||
|
TDeleteCertDTO,
|
||||||
|
TGetCertBodyDTO,
|
||||||
|
TGetCertBundleDTO,
|
||||||
|
TGetCertDTO,
|
||||||
|
TRevokeCertDTO
|
||||||
|
} from "./certificate-types";
|
||||||
|
|
||||||
type TCertificateServiceFactoryDep = {
|
type TCertificateServiceFactoryDep = {
|
||||||
certificateDAL: Pick<TCertificateDALFactory, "findOne" | "deleteById" | "update" | "find">;
|
certificateDAL: Pick<TCertificateDALFactory, "findOne" | "deleteById" | "update" | "find">;
|
||||||
@@ -86,11 +93,13 @@ export const certificateServiceFactory = ({
|
|||||||
actionProjectType: ActionProjectType.CertificateManager
|
actionProjectType: ActionProjectType.CertificateManager
|
||||||
});
|
});
|
||||||
|
|
||||||
// TODO(andrey): Update permission for privateKey fetching. Should be very strict.
|
ForbiddenError.from(permission).throwUnlessCan(
|
||||||
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Read, ProjectPermissionSub.Certificates);
|
ProjectPermissionActions.Read,
|
||||||
|
ProjectPermissionSub.CertificatePrivateKey
|
||||||
|
);
|
||||||
|
|
||||||
const { certPrivateKey } = await getCertificateCredentials({
|
const { certPrivateKey } = await getCertificateCredentials({
|
||||||
certId: ca.id,
|
certId: cert.id,
|
||||||
projectId: ca.projectId,
|
projectId: ca.projectId,
|
||||||
certificateSecretDAL,
|
certificateSecretDAL,
|
||||||
projectDAL,
|
projectDAL,
|
||||||
@@ -229,20 +238,110 @@ export const certificateServiceFactory = ({
|
|||||||
kmsService
|
kmsService
|
||||||
});
|
});
|
||||||
|
|
||||||
|
let certificateChain = `${caCert}\n${caCertChain}`.trim();
|
||||||
|
|
||||||
|
// If the certificate was generated after ~05/01/25 it will have a encryptedCertificateChain attached to it's body
|
||||||
|
if (certBody.encryptedCertificateChain) {
|
||||||
|
const decryptedCertChain = await kmsDecryptor({
|
||||||
|
cipherTextBlob: certBody.encryptedCertificateChain
|
||||||
|
});
|
||||||
|
const certChainObj = new x509.X509Certificate(decryptedCertChain);
|
||||||
|
certificateChain = certChainObj.toString("pem");
|
||||||
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
certificate: certObj.toString("pem"),
|
certificate: certObj.toString("pem"),
|
||||||
certificateChain: `${caCert}\n${caCertChain}`.trim(),
|
certificateChain,
|
||||||
serialNumber: certObj.serialNumber,
|
serialNumber: certObj.serialNumber,
|
||||||
cert,
|
cert,
|
||||||
ca
|
ca
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return certificate body and certificate chain for certificate with
|
||||||
|
* serial number [serialNumber]
|
||||||
|
*/
|
||||||
|
const getCertBundle = async ({ serialNumber, actorId, actorAuthMethod, actor, actorOrgId }: TGetCertBundleDTO) => {
|
||||||
|
const cert = await certificateDAL.findOne({ serialNumber });
|
||||||
|
const ca = await certificateAuthorityDAL.findById(cert.caId);
|
||||||
|
|
||||||
|
const { permission } = await permissionService.getProjectPermission({
|
||||||
|
actor,
|
||||||
|
actorId,
|
||||||
|
projectId: ca.projectId,
|
||||||
|
actorAuthMethod,
|
||||||
|
actorOrgId,
|
||||||
|
actionProjectType: ActionProjectType.CertificateManager
|
||||||
|
});
|
||||||
|
|
||||||
|
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionActions.Read, ProjectPermissionSub.Certificates);
|
||||||
|
ForbiddenError.from(permission).throwUnlessCan(
|
||||||
|
ProjectPermissionActions.Read,
|
||||||
|
ProjectPermissionSub.CertificatePrivateKey
|
||||||
|
);
|
||||||
|
|
||||||
|
const certBody = await certificateBodyDAL.findOne({ certId: cert.id });
|
||||||
|
|
||||||
|
const certificateManagerKeyId = await getProjectKmsCertificateKeyId({
|
||||||
|
projectId: ca.projectId,
|
||||||
|
projectDAL,
|
||||||
|
kmsService
|
||||||
|
});
|
||||||
|
|
||||||
|
const kmsDecryptor = await kmsService.decryptWithKmsKey({
|
||||||
|
kmsId: certificateManagerKeyId
|
||||||
|
});
|
||||||
|
const decryptedCert = await kmsDecryptor({
|
||||||
|
cipherTextBlob: certBody.encryptedCertificate
|
||||||
|
});
|
||||||
|
|
||||||
|
const certObj = new x509.X509Certificate(decryptedCert);
|
||||||
|
const certificate = certObj.toString("pem");
|
||||||
|
|
||||||
|
const { caCert, caCertChain } = await getCaCertChain({
|
||||||
|
caCertId: cert.caCertId,
|
||||||
|
certificateAuthorityDAL,
|
||||||
|
certificateAuthorityCertDAL,
|
||||||
|
projectDAL,
|
||||||
|
kmsService
|
||||||
|
});
|
||||||
|
|
||||||
|
let certificateChain = `${caCert}\n${caCertChain}`.trim();
|
||||||
|
|
||||||
|
// If the certificate was generated after ~05/01/25 it will have a encryptedCertificateChain attached to it's body
|
||||||
|
if (certBody.encryptedCertificateChain) {
|
||||||
|
const decryptedCertChain = await kmsDecryptor({
|
||||||
|
cipherTextBlob: certBody.encryptedCertificateChain
|
||||||
|
});
|
||||||
|
const certChainObj = new x509.X509Certificate(decryptedCertChain);
|
||||||
|
certificateChain = certChainObj.toString("pem");
|
||||||
|
}
|
||||||
|
|
||||||
|
const { certPrivateKey } = await getCertificateCredentials({
|
||||||
|
certId: cert.id,
|
||||||
|
projectId: ca.projectId,
|
||||||
|
certificateSecretDAL,
|
||||||
|
projectDAL,
|
||||||
|
kmsService
|
||||||
|
});
|
||||||
|
|
||||||
|
return {
|
||||||
|
certificate,
|
||||||
|
certificateChain,
|
||||||
|
privateKey: certPrivateKey,
|
||||||
|
serialNumber,
|
||||||
|
cert,
|
||||||
|
ca
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
return {
|
return {
|
||||||
getCert,
|
getCert,
|
||||||
getCertPrivateKey,
|
getCertPrivateKey,
|
||||||
deleteCert,
|
deleteCert,
|
||||||
revokeCert,
|
revokeCert,
|
||||||
getCertBody
|
getCertBody,
|
||||||
|
getCertBundle
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -78,6 +78,10 @@ export type TGetCertBodyDTO = {
|
|||||||
serialNumber: string;
|
serialNumber: string;
|
||||||
} & Omit<TProjectPermission, "projectId">;
|
} & Omit<TProjectPermission, "projectId">;
|
||||||
|
|
||||||
|
export type TGetCertBundleDTO = {
|
||||||
|
serialNumber: string;
|
||||||
|
} & Omit<TProjectPermission, "projectId">;
|
||||||
|
|
||||||
export type TGetCertificateCredentialsDTO = {
|
export type TGetCertificateCredentialsDTO = {
|
||||||
certId: string;
|
certId: string;
|
||||||
projectId: string;
|
projectId: string;
|
||||||
|
|||||||
Reference in New Issue
Block a user