mirror of
https://github.com/awatertrevi/infisical.git
synced 2026-10-15 22:46:24 +00:00
393 lines
14 KiB
TypeScript
393 lines
14 KiB
TypeScript
import { ForbiddenError } from "@casl/ability";
|
|
|
|
import { TPermissionServiceFactory } from "@app/ee/services/permission/permission-service-types";
|
|
import { ProjectPermissionCmekActions, ProjectPermissionSub } from "@app/ee/services/permission/project-permission";
|
|
import { SigningAlgorithm } from "@app/lib/crypto/sign";
|
|
import { DatabaseErrorCode } from "@app/lib/error-codes";
|
|
import { BadRequestError, DatabaseError, NotFoundError } from "@app/lib/errors";
|
|
import { OrgServiceActor } from "@app/lib/types";
|
|
import {
|
|
TCmekDecryptDTO,
|
|
TCmekEncryptDTO,
|
|
TCmekGetPublicKeyDTO,
|
|
TCmekKeyEncryptionAlgorithm,
|
|
TCmekListSigningAlgorithmsDTO,
|
|
TCmekSignDTO,
|
|
TCmekVerifyDTO,
|
|
TCreateCmekDTO,
|
|
TListCmeksByProjectIdDTO,
|
|
TUpdabteCmekByIdDTO
|
|
} from "@app/services/cmek/cmek-types";
|
|
import { TKmsKeyDALFactory } from "@app/services/kms/kms-key-dal";
|
|
import { TKmsServiceFactory } from "@app/services/kms/kms-service";
|
|
|
|
import { KmsKeyUsage } from "../kms/kms-types";
|
|
|
|
type TCmekServiceFactoryDep = {
|
|
kmsService: TKmsServiceFactory;
|
|
kmsDAL: TKmsKeyDALFactory;
|
|
permissionService: TPermissionServiceFactory;
|
|
};
|
|
|
|
export type TCmekServiceFactory = ReturnType<typeof cmekServiceFactory>;
|
|
|
|
export const cmekServiceFactory = ({ kmsService, kmsDAL, permissionService }: TCmekServiceFactoryDep) => {
|
|
const createCmek = async ({ projectId, ...dto }: TCreateCmekDTO, actor: OrgServiceActor) => {
|
|
const { permission } = await permissionService.getProjectPermission({
|
|
actor: actor.type,
|
|
actorId: actor.id,
|
|
projectId,
|
|
actorAuthMethod: actor.authMethod,
|
|
actorOrgId: actor.orgId
|
|
});
|
|
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionCmekActions.Create, ProjectPermissionSub.Cmek);
|
|
|
|
try {
|
|
const cmek = await kmsService.generateKmsKey({
|
|
...dto,
|
|
projectId,
|
|
isReserved: false
|
|
});
|
|
|
|
return {
|
|
...cmek,
|
|
version: 1,
|
|
encryptionAlgorithm: dto.encryptionAlgorithm
|
|
};
|
|
} catch (err) {
|
|
if (err instanceof DatabaseError && (err.error as { code: string })?.code === DatabaseErrorCode.UniqueViolation) {
|
|
throw new BadRequestError({
|
|
message: `A KMS key with the name "${dto.name}" already exists for the project with ID "${projectId}"`
|
|
});
|
|
}
|
|
|
|
throw err;
|
|
}
|
|
};
|
|
|
|
const updateCmekById = async ({ keyId, ...data }: TUpdabteCmekByIdDTO, actor: OrgServiceActor) => {
|
|
const key = await kmsDAL.findCmekById(keyId);
|
|
|
|
if (!key) throw new NotFoundError({ message: `Key with ID ${keyId} not found` });
|
|
|
|
if (!key.projectId || key.isReserved) throw new BadRequestError({ message: "Key is not customer managed" });
|
|
|
|
const { permission } = await permissionService.getProjectPermission({
|
|
actor: actor.type,
|
|
actorId: actor.id,
|
|
projectId: key.projectId,
|
|
actorAuthMethod: actor.authMethod,
|
|
actorOrgId: actor.orgId
|
|
});
|
|
|
|
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionCmekActions.Edit, ProjectPermissionSub.Cmek);
|
|
|
|
try {
|
|
const cmek = await kmsDAL.updateById(keyId, data);
|
|
|
|
return {
|
|
...cmek,
|
|
version: key.version,
|
|
encryptionAlgorithm: key.encryptionAlgorithm
|
|
};
|
|
} catch (err) {
|
|
if (err instanceof DatabaseError && (err.error as { code: string })?.code === DatabaseErrorCode.UniqueViolation) {
|
|
throw new BadRequestError({
|
|
message: `A KMS key with the name "${data.name!}" already exists for the project with ID "${key.projectId}"`
|
|
});
|
|
}
|
|
|
|
throw err;
|
|
}
|
|
};
|
|
|
|
const deleteCmekById = async (keyId: string, actor: OrgServiceActor) => {
|
|
const key = await kmsDAL.findCmekById(keyId);
|
|
|
|
if (!key) throw new NotFoundError({ message: `Key with ID ${keyId} not found` });
|
|
|
|
if (!key.projectId || key.isReserved) throw new BadRequestError({ message: "Key is not customer managed" });
|
|
|
|
const { permission } = await permissionService.getProjectPermission({
|
|
actor: actor.type,
|
|
actorId: actor.id,
|
|
projectId: key.projectId,
|
|
actorAuthMethod: actor.authMethod,
|
|
actorOrgId: actor.orgId
|
|
});
|
|
|
|
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionCmekActions.Delete, ProjectPermissionSub.Cmek);
|
|
|
|
await kmsDAL.deleteById(keyId);
|
|
|
|
return key;
|
|
};
|
|
|
|
const listCmeksByProjectId = async ({ projectId, ...filters }: TListCmeksByProjectIdDTO, actor: OrgServiceActor) => {
|
|
const { permission } = await permissionService.getProjectPermission({
|
|
actor: actor.type,
|
|
actorId: actor.id,
|
|
projectId,
|
|
actorAuthMethod: actor.authMethod,
|
|
actorOrgId: actor.orgId
|
|
});
|
|
|
|
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionCmekActions.Read, ProjectPermissionSub.Cmek);
|
|
|
|
const { keys: cmeks, totalCount } = await kmsDAL.listCmeksByProjectId({ projectId, ...filters });
|
|
|
|
return { cmeks, totalCount };
|
|
};
|
|
|
|
const findCmekById = async (keyId: string, actor: OrgServiceActor) => {
|
|
const key = await kmsDAL.findCmekById(keyId);
|
|
|
|
if (!key) throw new NotFoundError({ message: `Key with ID "${keyId}" not found` });
|
|
|
|
if (!key.projectId || key.isReserved) throw new BadRequestError({ message: "Key is not customer managed" });
|
|
|
|
const { permission } = await permissionService.getProjectPermission({
|
|
actor: actor.type,
|
|
actorId: actor.id,
|
|
projectId: key.projectId,
|
|
actorAuthMethod: actor.authMethod,
|
|
actorOrgId: actor.orgId
|
|
});
|
|
|
|
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionCmekActions.Read, ProjectPermissionSub.Cmek);
|
|
|
|
return key;
|
|
};
|
|
|
|
const findCmekByName = async (keyName: string, projectId: string, actor: OrgServiceActor) => {
|
|
const key = await kmsDAL.findCmekByName(keyName, projectId);
|
|
|
|
if (!key)
|
|
throw new NotFoundError({ message: `Key with name "${keyName}" not found for project with ID "${projectId}"` });
|
|
|
|
if (!key.projectId || key.isReserved) throw new BadRequestError({ message: "Key is not customer managed" });
|
|
|
|
const { permission } = await permissionService.getProjectPermission({
|
|
actor: actor.type,
|
|
actorId: actor.id,
|
|
projectId: key.projectId,
|
|
actorAuthMethod: actor.authMethod,
|
|
actorOrgId: actor.orgId
|
|
});
|
|
|
|
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionCmekActions.Read, ProjectPermissionSub.Cmek);
|
|
|
|
return key;
|
|
};
|
|
|
|
const cmekEncrypt = async ({ keyId, plaintext }: TCmekEncryptDTO, actor: OrgServiceActor) => {
|
|
const key = await kmsDAL.findById(keyId);
|
|
|
|
if (!key) throw new NotFoundError({ message: `Key with ID "${keyId}" not found` });
|
|
|
|
if (!key.projectId || key.isReserved) throw new BadRequestError({ message: "Key is not customer managed" });
|
|
|
|
if (key.isDisabled) throw new BadRequestError({ message: "Key is disabled" });
|
|
|
|
const { permission } = await permissionService.getProjectPermission({
|
|
actor: actor.type,
|
|
actorId: actor.id,
|
|
projectId: key.projectId,
|
|
actorAuthMethod: actor.authMethod,
|
|
actorOrgId: actor.orgId
|
|
});
|
|
|
|
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionCmekActions.Encrypt, ProjectPermissionSub.Cmek);
|
|
|
|
const encrypt = await kmsService.encryptWithKmsKey({ kmsId: keyId });
|
|
|
|
const { cipherTextBlob } = await encrypt({ plainText: Buffer.from(plaintext, "base64") });
|
|
|
|
return {
|
|
ciphertext: cipherTextBlob.toString("base64"),
|
|
projectId: key.projectId
|
|
};
|
|
};
|
|
|
|
const listSigningAlgorithms = async ({ keyId }: TCmekListSigningAlgorithmsDTO, actor: OrgServiceActor) => {
|
|
const key = await kmsDAL.findCmekById(keyId);
|
|
|
|
if (!key) throw new NotFoundError({ message: `Key with ID "${keyId}" not found` });
|
|
if (!key.projectId || key.isReserved) throw new BadRequestError({ message: "Key is not customer managed" });
|
|
if (key.isDisabled) throw new BadRequestError({ message: "Key is disabled" });
|
|
|
|
const { permission } = await permissionService.getProjectPermission({
|
|
actor: actor.type,
|
|
actorId: actor.id,
|
|
projectId: key.projectId,
|
|
actorAuthMethod: actor.authMethod,
|
|
actorOrgId: actor.orgId
|
|
});
|
|
|
|
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionCmekActions.Read, ProjectPermissionSub.Cmek);
|
|
|
|
if (key.keyUsage !== KmsKeyUsage.SIGN_VERIFY) {
|
|
throw new BadRequestError({ message: `Key with ID '${keyId}' is not intended for signing` });
|
|
}
|
|
|
|
const encryptionAlgorithm = key.encryptionAlgorithm as TCmekKeyEncryptionAlgorithm;
|
|
|
|
const algos = [
|
|
{
|
|
keyAlgorithm: "rsa",
|
|
signingAlgorithms: Object.values(SigningAlgorithm).filter((algorithm) =>
|
|
algorithm.toLowerCase().startsWith("rsa")
|
|
)
|
|
},
|
|
{
|
|
keyAlgorithm: "ecc",
|
|
signingAlgorithms: Object.values(SigningAlgorithm).filter((algorithm) =>
|
|
algorithm.toLowerCase().startsWith("ecdsa")
|
|
)
|
|
}
|
|
];
|
|
|
|
const selectedAlgorithm = algos.find((algo) => encryptionAlgorithm.toLowerCase().startsWith(algo.keyAlgorithm));
|
|
|
|
if (!selectedAlgorithm) {
|
|
throw new BadRequestError({ message: `Unsupported encryption algorithm: ${encryptionAlgorithm}` });
|
|
}
|
|
|
|
return { signingAlgorithms: selectedAlgorithm.signingAlgorithms, projectId: key.projectId };
|
|
};
|
|
|
|
const getPublicKey = async ({ keyId }: TCmekGetPublicKeyDTO, actor: OrgServiceActor) => {
|
|
const key = await kmsDAL.findCmekById(keyId);
|
|
|
|
if (!key) throw new NotFoundError({ message: `Key with ID "${keyId}" not found` });
|
|
if (!key.projectId || key.isReserved) throw new BadRequestError({ message: "Key is not customer managed" });
|
|
if (key.isDisabled) throw new BadRequestError({ message: "Key is disabled" });
|
|
|
|
const { permission } = await permissionService.getProjectPermission({
|
|
actor: actor.type,
|
|
actorId: actor.id,
|
|
projectId: key.projectId,
|
|
actorAuthMethod: actor.authMethod,
|
|
actorOrgId: actor.orgId
|
|
});
|
|
|
|
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionCmekActions.Read, ProjectPermissionSub.Cmek);
|
|
|
|
const publicKey = await kmsService.getPublicKey({ kmsId: keyId });
|
|
return { publicKey: publicKey.toString("base64"), projectId: key.projectId };
|
|
};
|
|
|
|
const cmekSign = async ({ keyId, data, signingAlgorithm, isDigest }: TCmekSignDTO, actor: OrgServiceActor) => {
|
|
const key = await kmsDAL.findCmekById(keyId);
|
|
|
|
if (!key) throw new NotFoundError({ message: `Key with ID "${keyId}" not found` });
|
|
|
|
if (!key.projectId || key.isReserved) throw new BadRequestError({ message: "Key is not customer managed" });
|
|
|
|
if (key.isDisabled) throw new BadRequestError({ message: "Key is disabled" });
|
|
|
|
const { permission } = await permissionService.getProjectPermission({
|
|
actor: actor.type,
|
|
actorId: actor.id,
|
|
projectId: key.projectId,
|
|
actorAuthMethod: actor.authMethod,
|
|
actorOrgId: actor.orgId
|
|
});
|
|
|
|
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionCmekActions.Sign, ProjectPermissionSub.Cmek);
|
|
|
|
const sign = await kmsService.signWithKmsKey({ kmsId: keyId });
|
|
|
|
const { signature, algorithm } = await sign({ data: Buffer.from(data, "base64"), signingAlgorithm, isDigest });
|
|
|
|
return {
|
|
signature: signature.toString("base64"),
|
|
keyId: key.id,
|
|
projectId: key.projectId,
|
|
signingAlgorithm: algorithm
|
|
};
|
|
};
|
|
|
|
const cmekVerify = async (
|
|
{ keyId, data, signature, signingAlgorithm, isDigest }: TCmekVerifyDTO,
|
|
actor: OrgServiceActor
|
|
) => {
|
|
const key = await kmsDAL.findCmekById(keyId);
|
|
|
|
if (!key) throw new NotFoundError({ message: `Key with ID "${keyId}" not found` });
|
|
|
|
if (!key.projectId || key.isReserved) throw new BadRequestError({ message: "Key is not customer managed" });
|
|
|
|
if (key.isDisabled) throw new BadRequestError({ message: "Key is disabled" });
|
|
|
|
const { permission } = await permissionService.getProjectPermission({
|
|
actor: actor.type,
|
|
actorId: actor.id,
|
|
projectId: key.projectId,
|
|
actorAuthMethod: actor.authMethod,
|
|
actorOrgId: actor.orgId
|
|
});
|
|
|
|
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionCmekActions.Verify, ProjectPermissionSub.Cmek);
|
|
|
|
const verify = await kmsService.verifyWithKmsKey({ kmsId: keyId, signingAlgorithm });
|
|
|
|
const { signatureValid, algorithm } = await verify({
|
|
isDigest,
|
|
data: Buffer.from(data, "base64"),
|
|
signature: Buffer.from(signature, "base64")
|
|
});
|
|
|
|
return {
|
|
signatureValid,
|
|
keyId: key.id,
|
|
projectId: key.projectId,
|
|
signingAlgorithm: algorithm
|
|
};
|
|
};
|
|
|
|
const cmekDecrypt = async ({ keyId, ciphertext }: TCmekDecryptDTO, actor: OrgServiceActor) => {
|
|
const key = await kmsDAL.findById(keyId);
|
|
|
|
if (!key) throw new NotFoundError({ message: `Key with ID "${keyId}" not found` });
|
|
|
|
if (!key.projectId || key.isReserved) throw new BadRequestError({ message: "Key is not customer managed" });
|
|
|
|
if (key.isDisabled) throw new BadRequestError({ message: "Key is disabled" });
|
|
|
|
const { permission } = await permissionService.getProjectPermission({
|
|
actor: actor.type,
|
|
actorId: actor.id,
|
|
projectId: key.projectId,
|
|
actorAuthMethod: actor.authMethod,
|
|
actorOrgId: actor.orgId
|
|
});
|
|
|
|
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionCmekActions.Decrypt, ProjectPermissionSub.Cmek);
|
|
|
|
const decrypt = await kmsService.decryptWithKmsKey({ kmsId: keyId });
|
|
|
|
const plaintextBlob = await decrypt({ cipherTextBlob: Buffer.from(ciphertext, "base64") });
|
|
|
|
return {
|
|
plaintext: plaintextBlob.toString("base64"),
|
|
projectId: key.projectId
|
|
};
|
|
};
|
|
|
|
return {
|
|
createCmek,
|
|
updateCmekById,
|
|
deleteCmekById,
|
|
listCmeksByProjectId,
|
|
cmekEncrypt,
|
|
cmekDecrypt,
|
|
findCmekById,
|
|
findCmekByName,
|
|
cmekSign,
|
|
cmekVerify,
|
|
listSigningAlgorithms,
|
|
getPublicKey
|
|
};
|
|
};
|