mirror of
https://github.com/awatertrevi/infisical.git
synced 2026-10-15 15:41:27 +00:00
Merge pull request #3357 from Infisical/daniel/kms-sign-verify
feat(kms): sign & verify data
This commit is contained in:
@@ -3,12 +3,18 @@ import { ForbiddenError } from "@casl/ability";
|
||||
import { ActionProjectType, ProjectType } from "@app/db/schemas";
|
||||
import { TPermissionServiceFactory } from "@app/ee/services/permission/permission-service";
|
||||
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
|
||||
@@ -16,6 +22,7 @@ import {
|
||||
import { TKmsKeyDALFactory } from "@app/services/kms/kms-key-dal";
|
||||
import { TKmsServiceFactory } from "@app/services/kms/kms-service";
|
||||
|
||||
import { KmsKeyUsage } from "../kms/kms-types";
|
||||
import { TProjectDALFactory } from "../project/project-dal";
|
||||
|
||||
type TCmekServiceFactoryDep = {
|
||||
@@ -221,7 +228,151 @@ export const cmekServiceFactory = ({ kmsService, kmsDAL, permissionService, proj
|
||||
|
||||
const { cipherTextBlob } = await encrypt({ plainText: Buffer.from(plaintext, "base64") });
|
||||
|
||||
return cipherTextBlob.toString("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,
|
||||
actionProjectType: ActionProjectType.KMS
|
||||
});
|
||||
|
||||
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,
|
||||
actionProjectType: ActionProjectType.KMS
|
||||
});
|
||||
|
||||
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,
|
||||
actionProjectType: ActionProjectType.KMS
|
||||
});
|
||||
|
||||
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,
|
||||
actionProjectType: ActionProjectType.KMS
|
||||
});
|
||||
|
||||
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) => {
|
||||
@@ -248,7 +399,10 @@ export const cmekServiceFactory = ({ kmsService, kmsDAL, permissionService, proj
|
||||
|
||||
const plaintextBlob = await decrypt({ cipherTextBlob: Buffer.from(ciphertext, "base64") });
|
||||
|
||||
return plaintextBlob.toString("base64");
|
||||
return {
|
||||
plaintext: plaintextBlob.toString("base64"),
|
||||
projectId: key.projectId
|
||||
};
|
||||
};
|
||||
|
||||
return {
|
||||
@@ -259,6 +413,10 @@ export const cmekServiceFactory = ({ kmsService, kmsDAL, permissionService, proj
|
||||
cmekEncrypt,
|
||||
cmekDecrypt,
|
||||
findCmekById,
|
||||
findCmekByName
|
||||
findCmekByName,
|
||||
cmekSign,
|
||||
cmekVerify,
|
||||
listSigningAlgorithms,
|
||||
getPublicKey
|
||||
};
|
||||
};
|
||||
|
||||
@@ -1,12 +1,18 @@
|
||||
import { SymmetricEncryption } from "@app/lib/crypto/cipher";
|
||||
import { SymmetricKeyAlgorithm } from "@app/lib/crypto/cipher";
|
||||
import { AsymmetricKeyAlgorithm, SigningAlgorithm } from "@app/lib/crypto/sign";
|
||||
import { OrderByDirection } from "@app/lib/types";
|
||||
|
||||
import { KmsKeyUsage } from "../kms/kms-types";
|
||||
|
||||
export type TCmekKeyEncryptionAlgorithm = SymmetricKeyAlgorithm | AsymmetricKeyAlgorithm;
|
||||
|
||||
export type TCreateCmekDTO = {
|
||||
orgId: string;
|
||||
projectId: string;
|
||||
name: string;
|
||||
description?: string;
|
||||
encryptionAlgorithm: SymmetricEncryption;
|
||||
encryptionAlgorithm: TCmekKeyEncryptionAlgorithm;
|
||||
keyUsage: KmsKeyUsage;
|
||||
};
|
||||
|
||||
export type TUpdabteCmekByIdDTO = {
|
||||
@@ -38,3 +44,26 @@ export type TCmekDecryptDTO = {
|
||||
export enum CmekOrderBy {
|
||||
Name = "name"
|
||||
}
|
||||
|
||||
export type TCmekListSigningAlgorithmsDTO = {
|
||||
keyId: string;
|
||||
};
|
||||
|
||||
export type TCmekGetPublicKeyDTO = {
|
||||
keyId: string;
|
||||
};
|
||||
|
||||
export type TCmekSignDTO = {
|
||||
keyId: string;
|
||||
data: string;
|
||||
signingAlgorithm: SigningAlgorithm;
|
||||
isDigest: boolean;
|
||||
};
|
||||
|
||||
export type TCmekVerifyDTO = {
|
||||
keyId: string;
|
||||
data: string;
|
||||
signature: string;
|
||||
signingAlgorithm: SigningAlgorithm;
|
||||
isDigest: boolean;
|
||||
};
|
||||
|
||||
@@ -1,13 +1,55 @@
|
||||
import { SymmetricEncryption } from "@app/lib/crypto/cipher";
|
||||
import { SymmetricKeyAlgorithm } from "@app/lib/crypto/cipher";
|
||||
import { AsymmetricKeyAlgorithm } from "@app/lib/crypto/sign";
|
||||
import { BadRequestError } from "@app/lib/errors";
|
||||
|
||||
import { KmsKeyUsage } from "./kms-types";
|
||||
|
||||
export const KMS_ROOT_CONFIG_UUID = "00000000-0000-0000-0000-000000000000";
|
||||
|
||||
export const getByteLengthForAlgorithm = (encryptionAlgorithm: SymmetricEncryption) => {
|
||||
export const getByteLengthForSymmetricEncryptionAlgorithm = (encryptionAlgorithm: SymmetricKeyAlgorithm) => {
|
||||
switch (encryptionAlgorithm) {
|
||||
case SymmetricEncryption.AES_GCM_128:
|
||||
case SymmetricKeyAlgorithm.AES_GCM_128:
|
||||
return 16;
|
||||
case SymmetricEncryption.AES_GCM_256:
|
||||
case SymmetricKeyAlgorithm.AES_GCM_256:
|
||||
default:
|
||||
return 32;
|
||||
}
|
||||
};
|
||||
|
||||
export const verifyKeyTypeAndAlgorithm = (
|
||||
keyUsage: KmsKeyUsage,
|
||||
algorithm: SymmetricKeyAlgorithm | AsymmetricKeyAlgorithm,
|
||||
extra?: {
|
||||
forceType?: KmsKeyUsage;
|
||||
}
|
||||
) => {
|
||||
if (extra?.forceType && keyUsage !== extra.forceType) {
|
||||
throw new BadRequestError({
|
||||
message: `Unsupported key type, expected ${extra.forceType} but got ${keyUsage}`
|
||||
});
|
||||
}
|
||||
|
||||
if (keyUsage === KmsKeyUsage.ENCRYPT_DECRYPT) {
|
||||
if (!Object.values(SymmetricKeyAlgorithm).includes(algorithm as SymmetricKeyAlgorithm)) {
|
||||
throw new BadRequestError({
|
||||
message: `Unsupported encryption algorithm for encrypt/decrypt key: ${algorithm as string}`
|
||||
});
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
if (keyUsage === KmsKeyUsage.SIGN_VERIFY) {
|
||||
if (!Object.values(AsymmetricKeyAlgorithm).includes(algorithm as AsymmetricKeyAlgorithm)) {
|
||||
throw new BadRequestError({
|
||||
message: `Unsupported sign/verify algorithm for sign/verify key: ${algorithm as string}`
|
||||
});
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
throw new BadRequestError({
|
||||
message: `Unsupported key type: ${keyUsage as string}`
|
||||
});
|
||||
};
|
||||
|
||||
@@ -15,12 +15,17 @@ import { THsmServiceFactory } from "@app/ee/services/hsm/hsm-service";
|
||||
import { KeyStorePrefixes, PgSqlLock, TKeyStoreFactory } from "@app/keystore/keystore";
|
||||
import { TEnvConfig } from "@app/lib/config/env";
|
||||
import { randomSecureBytes } from "@app/lib/crypto";
|
||||
import { symmetricCipherService, SymmetricEncryption } from "@app/lib/crypto/cipher";
|
||||
import { symmetricCipherService, SymmetricKeyAlgorithm } from "@app/lib/crypto/cipher";
|
||||
import { generateHash } from "@app/lib/crypto/encryption";
|
||||
import { AsymmetricKeyAlgorithm, signingService } from "@app/lib/crypto/sign";
|
||||
import { BadRequestError, ForbiddenRequestError, NotFoundError } from "@app/lib/errors";
|
||||
import { logger } from "@app/lib/logger";
|
||||
import { alphaNumericNanoId } from "@app/lib/nanoid";
|
||||
import { getByteLengthForAlgorithm, KMS_ROOT_CONFIG_UUID } from "@app/services/kms/kms-fns";
|
||||
import {
|
||||
getByteLengthForSymmetricEncryptionAlgorithm,
|
||||
KMS_ROOT_CONFIG_UUID,
|
||||
verifyKeyTypeAndAlgorithm
|
||||
} from "@app/services/kms/kms-fns";
|
||||
|
||||
import { TOrgDALFactory } from "../org/org-dal";
|
||||
import { TProjectDALFactory } from "../project/project-dal";
|
||||
@@ -29,6 +34,7 @@ import { TKmsKeyDALFactory } from "./kms-key-dal";
|
||||
import { TKmsRootConfigDALFactory } from "./kms-root-config-dal";
|
||||
import {
|
||||
KmsDataKey,
|
||||
KmsKeyUsage,
|
||||
KmsType,
|
||||
RootKeyEncryptionStrategy,
|
||||
TDecryptWithKeyDTO,
|
||||
@@ -38,8 +44,11 @@ import {
|
||||
TEncryptWithKmsDTO,
|
||||
TGenerateKMSDTO,
|
||||
TGetKeyMaterialDTO,
|
||||
TGetPublicKeyDTO,
|
||||
TImportKeyMaterialDTO,
|
||||
TUpdateProjectSecretManagerKmsKeyDTO
|
||||
TSignWithKmsDTO,
|
||||
TUpdateProjectSecretManagerKmsKeyDTO,
|
||||
TVerifyWithKmsDTO
|
||||
} from "./kms-types";
|
||||
|
||||
type TKmsServiceFactoryDep = {
|
||||
@@ -83,19 +92,42 @@ export const kmsServiceFactory = ({
|
||||
tx,
|
||||
name,
|
||||
projectId,
|
||||
encryptionAlgorithm = SymmetricEncryption.AES_GCM_256,
|
||||
encryptionAlgorithm = SymmetricKeyAlgorithm.AES_GCM_256,
|
||||
keyUsage = KmsKeyUsage.ENCRYPT_DECRYPT,
|
||||
description
|
||||
}: TGenerateKMSDTO) => {
|
||||
const cipher = symmetricCipherService(SymmetricEncryption.AES_GCM_256);
|
||||
// daniel: ensure that the key type (sign/encrypt) and the encryption algorithm are compatible.
|
||||
verifyKeyTypeAndAlgorithm(keyUsage, encryptionAlgorithm);
|
||||
|
||||
const kmsKeyMaterial = randomSecureBytes(getByteLengthForAlgorithm(encryptionAlgorithm));
|
||||
let kmsKeyMaterial: Buffer | null = null;
|
||||
if (keyUsage === KmsKeyUsage.ENCRYPT_DECRYPT) {
|
||||
kmsKeyMaterial = randomSecureBytes(
|
||||
getByteLengthForSymmetricEncryptionAlgorithm(encryptionAlgorithm as SymmetricKeyAlgorithm)
|
||||
);
|
||||
} else if (keyUsage === KmsKeyUsage.SIGN_VERIFY) {
|
||||
const { generateAsymmetricPrivateKey, getPublicKeyFromPrivateKey } = signingService(
|
||||
encryptionAlgorithm as AsymmetricKeyAlgorithm
|
||||
);
|
||||
kmsKeyMaterial = await generateAsymmetricPrivateKey();
|
||||
|
||||
// daniel: safety check to ensure we're able to extract the public key from the private key before we proceed to key creation
|
||||
getPublicKeyFromPrivateKey(kmsKeyMaterial);
|
||||
}
|
||||
|
||||
if (!kmsKeyMaterial) {
|
||||
throw new BadRequestError({
|
||||
message: `Invalid KMS key type. No key material was created for key usage '${keyUsage}' using algorithm '${encryptionAlgorithm}'`
|
||||
});
|
||||
}
|
||||
|
||||
const cipher = symmetricCipherService(SymmetricKeyAlgorithm.AES_GCM_256);
|
||||
const encryptedKeyMaterial = cipher.encrypt(kmsKeyMaterial, ROOT_ENCRYPTION_KEY);
|
||||
const sanitizedName = name ? slugify(name) : slugify(alphaNumericNanoId(8).toLowerCase());
|
||||
const dbQuery = async (db: Knex) => {
|
||||
const kmsDoc = await kmsDAL.create(
|
||||
{
|
||||
name: sanitizedName,
|
||||
keyUsage,
|
||||
orgId,
|
||||
isReserved,
|
||||
projectId,
|
||||
@@ -115,6 +147,7 @@ export const kmsServiceFactory = ({
|
||||
);
|
||||
return kmsDoc;
|
||||
};
|
||||
|
||||
if (tx) return dbQuery(tx);
|
||||
const doc = await kmsDAL.transaction(async (tx2) => dbQuery(tx2));
|
||||
return doc;
|
||||
@@ -134,7 +167,7 @@ export const kmsServiceFactory = ({
|
||||
*/
|
||||
const encryptWithInputKey = async ({ key }: Omit<TEncryptionWithKeyDTO, "plainText">) => {
|
||||
// akhilmhdh: as more encryption are added do a check here on kmsDoc.encryptionAlgorithm
|
||||
const cipher = symmetricCipherService(SymmetricEncryption.AES_GCM_256);
|
||||
const cipher = symmetricCipherService(SymmetricKeyAlgorithm.AES_GCM_256);
|
||||
return ({ plainText }: Pick<TEncryptWithKmsDTO, "plainText">) => {
|
||||
const encryptedPlainTextBlob = cipher.encrypt(plainText, key);
|
||||
// Buffer#1 encrypted text + Buffer#2 version number
|
||||
@@ -149,7 +182,7 @@ export const kmsServiceFactory = ({
|
||||
* This can be even later exposed directly as api for encryption as function
|
||||
*/
|
||||
const decryptWithInputKey = async ({ key }: Omit<TDecryptWithKeyDTO, "cipherTextBlob">) => {
|
||||
const cipher = symmetricCipherService(SymmetricEncryption.AES_GCM_256);
|
||||
const cipher = symmetricCipherService(SymmetricKeyAlgorithm.AES_GCM_256);
|
||||
|
||||
return ({ cipherTextBlob: versionedCipherTextBlob }: Pick<TDecryptWithKeyDTO, "cipherTextBlob">) => {
|
||||
const cipherTextBlob = versionedCipherTextBlob.subarray(0, -KMS_VERSION_BLOB_LENGTH);
|
||||
@@ -227,7 +260,7 @@ export const kmsServiceFactory = ({
|
||||
};
|
||||
|
||||
const encryptWithRootKey = () => {
|
||||
const cipher = symmetricCipherService(SymmetricEncryption.AES_GCM_256);
|
||||
const cipher = symmetricCipherService(SymmetricKeyAlgorithm.AES_GCM_256);
|
||||
|
||||
return (plainTextBuffer: Buffer) => {
|
||||
const encryptedBuffer = cipher.encrypt(plainTextBuffer, ROOT_ENCRYPTION_KEY);
|
||||
@@ -236,7 +269,7 @@ export const kmsServiceFactory = ({
|
||||
};
|
||||
|
||||
const decryptWithRootKey = () => {
|
||||
const cipher = symmetricCipherService(SymmetricEncryption.AES_GCM_256);
|
||||
const cipher = symmetricCipherService(SymmetricKeyAlgorithm.AES_GCM_256);
|
||||
|
||||
return (cipherTextBuffer: Buffer) => {
|
||||
return cipher.decrypt(cipherTextBuffer, ROOT_ENCRYPTION_KEY);
|
||||
@@ -255,6 +288,11 @@ export const kmsServiceFactory = ({
|
||||
throw new NotFoundError({ message: `KMS with ID '${kmsId}' not found` });
|
||||
}
|
||||
|
||||
const encryptionAlgorithm = kmsDoc.internalKms?.encryptionAlgorithm as SymmetricKeyAlgorithm;
|
||||
verifyKeyTypeAndAlgorithm(kmsDoc.keyUsage as KmsKeyUsage, encryptionAlgorithm, {
|
||||
forceType: KmsKeyUsage.ENCRYPT_DECRYPT
|
||||
});
|
||||
|
||||
if (kmsDoc.externalKms) {
|
||||
let externalKms: TExternalKmsProviderFns;
|
||||
|
||||
@@ -316,8 +354,8 @@ export const kmsServiceFactory = ({
|
||||
}
|
||||
|
||||
// internal KMS
|
||||
const keyCipher = symmetricCipherService(SymmetricEncryption.AES_GCM_256);
|
||||
const dataCipher = symmetricCipherService(kmsDoc.internalKms?.encryptionAlgorithm as SymmetricEncryption);
|
||||
const keyCipher = symmetricCipherService(SymmetricKeyAlgorithm.AES_GCM_256);
|
||||
const dataCipher = symmetricCipherService(encryptionAlgorithm);
|
||||
const kmsKey = keyCipher.decrypt(kmsDoc.internalKms?.encryptedKey as Buffer, ROOT_ENCRYPTION_KEY);
|
||||
|
||||
return ({ cipherTextBlob: versionedCipherTextBlob }: Pick<TDecryptWithKmsDTO, "cipherTextBlob">) => {
|
||||
@@ -345,19 +383,22 @@ export const kmsServiceFactory = ({
|
||||
});
|
||||
}
|
||||
|
||||
const keyCipher = symmetricCipherService(SymmetricEncryption.AES_GCM_256);
|
||||
const keyCipher = symmetricCipherService(SymmetricKeyAlgorithm.AES_GCM_256);
|
||||
const kmsKey = keyCipher.decrypt(kmsDoc.internalKms?.encryptedKey as Buffer, ROOT_ENCRYPTION_KEY);
|
||||
|
||||
return kmsKey;
|
||||
};
|
||||
|
||||
const importKeyMaterial = async (
|
||||
{ key, algorithm, name, isReserved, projectId, orgId }: TImportKeyMaterialDTO,
|
||||
{ key, algorithm, name, isReserved, projectId, orgId, keyUsage }: TImportKeyMaterialDTO,
|
||||
tx?: Knex
|
||||
) => {
|
||||
const cipher = symmetricCipherService(SymmetricEncryption.AES_GCM_256);
|
||||
// daniel: currently we only support imports for encrypt/decrypt keys
|
||||
verifyKeyTypeAndAlgorithm(keyUsage, algorithm, { forceType: KmsKeyUsage.ENCRYPT_DECRYPT });
|
||||
|
||||
const expectedByteLength = getByteLengthForAlgorithm(algorithm);
|
||||
const cipher = symmetricCipherService(SymmetricKeyAlgorithm.AES_GCM_256);
|
||||
|
||||
const expectedByteLength = getByteLengthForSymmetricEncryptionAlgorithm(algorithm as SymmetricKeyAlgorithm);
|
||||
if (key.byteLength !== expectedByteLength) {
|
||||
throw new BadRequestError({
|
||||
message: `Invalid key length for ${algorithm}. Expected ${expectedByteLength} bytes but got ${key.byteLength} bytes`
|
||||
@@ -370,6 +411,7 @@ export const kmsServiceFactory = ({
|
||||
const kmsDoc = await kmsDAL.create(
|
||||
{
|
||||
name: sanitizedName,
|
||||
keyUsage: KmsKeyUsage.ENCRYPT_DECRYPT,
|
||||
orgId,
|
||||
isReserved,
|
||||
projectId
|
||||
@@ -393,12 +435,85 @@ export const kmsServiceFactory = ({
|
||||
return doc;
|
||||
};
|
||||
|
||||
const getPublicKey = async ({ kmsId }: TGetPublicKeyDTO) => {
|
||||
const kmsDoc = await kmsDAL.findByIdWithAssociatedKms(kmsId);
|
||||
if (!kmsDoc) {
|
||||
throw new NotFoundError({ message: `KMS with ID '${kmsId}' not found` });
|
||||
}
|
||||
|
||||
const encryptionAlgorithm = kmsDoc.internalKms?.encryptionAlgorithm as AsymmetricKeyAlgorithm;
|
||||
|
||||
verifyKeyTypeAndAlgorithm(kmsDoc.keyUsage as KmsKeyUsage, encryptionAlgorithm, {
|
||||
forceType: KmsKeyUsage.SIGN_VERIFY
|
||||
});
|
||||
|
||||
const keyCipher = symmetricCipherService(SymmetricKeyAlgorithm.AES_GCM_256);
|
||||
const kmsKey = keyCipher.decrypt(kmsDoc.internalKms?.encryptedKey as Buffer, ROOT_ENCRYPTION_KEY);
|
||||
|
||||
return signingService(encryptionAlgorithm).getPublicKeyFromPrivateKey(kmsKey);
|
||||
};
|
||||
|
||||
const signWithKmsKey = async ({ kmsId }: Pick<TSignWithKmsDTO, "kmsId">) => {
|
||||
const kmsDoc = await kmsDAL.findByIdWithAssociatedKms(kmsId);
|
||||
if (!kmsDoc) {
|
||||
throw new NotFoundError({ message: `KMS with ID '${kmsId}' not found` });
|
||||
}
|
||||
|
||||
const encryptionAlgorithm = kmsDoc.internalKms?.encryptionAlgorithm as AsymmetricKeyAlgorithm;
|
||||
verifyKeyTypeAndAlgorithm(kmsDoc.keyUsage as KmsKeyUsage, encryptionAlgorithm, {
|
||||
forceType: KmsKeyUsage.SIGN_VERIFY
|
||||
});
|
||||
|
||||
const keyCipher = symmetricCipherService(SymmetricKeyAlgorithm.AES_GCM_256);
|
||||
const { sign } = signingService(encryptionAlgorithm);
|
||||
return async ({
|
||||
data,
|
||||
signingAlgorithm,
|
||||
isDigest
|
||||
}: Pick<TSignWithKmsDTO, "data" | "signingAlgorithm" | "isDigest">) => {
|
||||
const kmsKey = keyCipher.decrypt(kmsDoc.internalKms?.encryptedKey as Buffer, ROOT_ENCRYPTION_KEY);
|
||||
const signature = await sign(data, kmsKey, signingAlgorithm, isDigest);
|
||||
|
||||
return Promise.resolve({ signature, algorithm: signingAlgorithm });
|
||||
};
|
||||
};
|
||||
|
||||
const verifyWithKmsKey = async ({
|
||||
kmsId,
|
||||
signingAlgorithm
|
||||
}: Pick<TVerifyWithKmsDTO, "kmsId" | "signingAlgorithm">) => {
|
||||
const kmsDoc = await kmsDAL.findByIdWithAssociatedKms(kmsId);
|
||||
if (!kmsDoc) {
|
||||
throw new NotFoundError({ message: `KMS with ID '${kmsId}' not found` });
|
||||
}
|
||||
|
||||
const encryptionAlgorithm = kmsDoc.internalKms?.encryptionAlgorithm as AsymmetricKeyAlgorithm;
|
||||
verifyKeyTypeAndAlgorithm(kmsDoc.keyUsage as KmsKeyUsage, encryptionAlgorithm, {
|
||||
forceType: KmsKeyUsage.SIGN_VERIFY
|
||||
});
|
||||
|
||||
const keyCipher = symmetricCipherService(SymmetricKeyAlgorithm.AES_GCM_256);
|
||||
const { verify, getPublicKeyFromPrivateKey } = signingService(encryptionAlgorithm);
|
||||
return async ({ data, signature, isDigest }: Pick<TVerifyWithKmsDTO, "data" | "signature" | "isDigest">) => {
|
||||
const kmsKey = keyCipher.decrypt(kmsDoc.internalKms?.encryptedKey as Buffer, ROOT_ENCRYPTION_KEY);
|
||||
|
||||
const publicKey = getPublicKeyFromPrivateKey(kmsKey);
|
||||
const signatureValid = await verify(data, signature, publicKey, signingAlgorithm, isDigest);
|
||||
return Promise.resolve({ signatureValid, algorithm: signingAlgorithm });
|
||||
};
|
||||
};
|
||||
|
||||
const encryptWithKmsKey = async ({ kmsId }: Omit<TEncryptWithKmsDTO, "plainText">, tx?: Knex) => {
|
||||
const kmsDoc = await kmsDAL.findByIdWithAssociatedKms(kmsId, tx);
|
||||
if (!kmsDoc) {
|
||||
throw new NotFoundError({ message: `KMS with ID '${kmsId}' not found` });
|
||||
}
|
||||
|
||||
const encryptionAlgorithm = kmsDoc.internalKms?.encryptionAlgorithm as SymmetricKeyAlgorithm;
|
||||
verifyKeyTypeAndAlgorithm(kmsDoc.keyUsage as KmsKeyUsage, encryptionAlgorithm, {
|
||||
forceType: KmsKeyUsage.ENCRYPT_DECRYPT
|
||||
});
|
||||
|
||||
if (kmsDoc.externalKms) {
|
||||
let externalKms: TExternalKmsProviderFns;
|
||||
if (!kmsDoc.orgKms.id || !kmsDoc.orgKms.encryptedDataKey) {
|
||||
@@ -454,8 +569,8 @@ export const kmsServiceFactory = ({
|
||||
}
|
||||
|
||||
// internal KMS
|
||||
const keyCipher = symmetricCipherService(SymmetricEncryption.AES_GCM_256);
|
||||
const dataCipher = symmetricCipherService(kmsDoc.internalKms?.encryptionAlgorithm as SymmetricEncryption);
|
||||
const keyCipher = symmetricCipherService(SymmetricKeyAlgorithm.AES_GCM_256);
|
||||
const dataCipher = symmetricCipherService(encryptionAlgorithm);
|
||||
return ({ plainText }: Pick<TEncryptWithKmsDTO, "plainText">) => {
|
||||
const kmsKey = keyCipher.decrypt(kmsDoc.internalKms?.encryptedKey as Buffer, ROOT_ENCRYPTION_KEY);
|
||||
const encryptedPlainTextBlob = dataCipher.encrypt(plainText, kmsKey);
|
||||
@@ -729,7 +844,7 @@ export const kmsServiceFactory = ({
|
||||
|
||||
// case 2: root key is encrypted with software encryption
|
||||
if (kmsRootConfig.encryptionStrategy === RootKeyEncryptionStrategy.Software) {
|
||||
const cipher = symmetricCipherService(SymmetricEncryption.AES_GCM_256);
|
||||
const cipher = symmetricCipherService(SymmetricKeyAlgorithm.AES_GCM_256);
|
||||
const encryptionKeyBuffer = $getBasicEncryptionKey();
|
||||
|
||||
return cipher.decrypt(kmsRootConfig.encryptedRootKey, encryptionKeyBuffer);
|
||||
@@ -749,7 +864,7 @@ export const kmsServiceFactory = ({
|
||||
}
|
||||
|
||||
if (strategy === RootKeyEncryptionStrategy.Software) {
|
||||
const cipher = symmetricCipherService(SymmetricEncryption.AES_GCM_256);
|
||||
const cipher = symmetricCipherService(SymmetricKeyAlgorithm.AES_GCM_256);
|
||||
const encryptionKeyBuffer = $getBasicEncryptionKey();
|
||||
|
||||
return cipher.encrypt(plainKeyBuffer, encryptionKeyBuffer);
|
||||
@@ -765,7 +880,7 @@ export const kmsServiceFactory = ({
|
||||
const createCipherPairWithDataKey = async (encryptionContext: TEncryptWithKmsDataKeyDTO, trx?: Knex) => {
|
||||
const dataKey = await $getDataKey(encryptionContext, trx);
|
||||
|
||||
const cipher = symmetricCipherService(SymmetricEncryption.AES_GCM_256);
|
||||
const cipher = symmetricCipherService(SymmetricKeyAlgorithm.AES_GCM_256);
|
||||
|
||||
return {
|
||||
encryptor: ({ plainText }: Pick<TEncryptWithKmsDTO, "plainText">) => {
|
||||
@@ -966,6 +1081,7 @@ export const kmsServiceFactory = ({
|
||||
const decryptedRootKey = await $decryptRootKey(kmsRootConfig);
|
||||
|
||||
logger.info("KMS: Loading ROOT Key into Memory.");
|
||||
|
||||
ROOT_ENCRYPTION_KEY = decryptedRootKey;
|
||||
};
|
||||
|
||||
@@ -1014,6 +1130,9 @@ export const kmsServiceFactory = ({
|
||||
getKmsById,
|
||||
createCipherPairWithDataKey,
|
||||
getKeyMaterial,
|
||||
importKeyMaterial
|
||||
importKeyMaterial,
|
||||
signWithKmsKey,
|
||||
verifyWithKmsKey,
|
||||
getPublicKey
|
||||
};
|
||||
};
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { Knex } from "knex";
|
||||
|
||||
import { SymmetricEncryption } from "@app/lib/crypto/cipher";
|
||||
import { SymmetricKeyAlgorithm } from "@app/lib/crypto/cipher";
|
||||
import { AsymmetricKeyAlgorithm, SigningAlgorithm } from "@app/lib/crypto/sign/types";
|
||||
|
||||
export enum KmsDataKey {
|
||||
Organization,
|
||||
@@ -13,6 +14,11 @@ export enum KmsType {
|
||||
Internal = "internal"
|
||||
}
|
||||
|
||||
export enum KmsKeyUsage {
|
||||
ENCRYPT_DECRYPT = "encrypt-decrypt",
|
||||
SIGN_VERIFY = "sign-verify"
|
||||
}
|
||||
|
||||
export type TEncryptWithKmsDataKeyDTO =
|
||||
| { type: KmsDataKey.Organization; orgId: string }
|
||||
| { type: KmsDataKey.SecretManager; projectId: string };
|
||||
@@ -25,7 +31,8 @@ export type TEncryptWithKmsDataKeyDTO =
|
||||
export type TGenerateKMSDTO = {
|
||||
orgId: string;
|
||||
projectId?: string;
|
||||
encryptionAlgorithm?: SymmetricEncryption;
|
||||
encryptionAlgorithm?: SymmetricKeyAlgorithm | AsymmetricKeyAlgorithm;
|
||||
keyUsage?: KmsKeyUsage;
|
||||
isReserved?: boolean;
|
||||
name?: string;
|
||||
description?: string;
|
||||
@@ -37,6 +44,25 @@ export type TEncryptWithKmsDTO = {
|
||||
plainText: Buffer;
|
||||
};
|
||||
|
||||
export type TGetPublicKeyDTO = {
|
||||
kmsId: string;
|
||||
};
|
||||
|
||||
export type TSignWithKmsDTO = {
|
||||
kmsId: string;
|
||||
data: Buffer;
|
||||
signingAlgorithm: SigningAlgorithm;
|
||||
isDigest: boolean;
|
||||
};
|
||||
|
||||
export type TVerifyWithKmsDTO = {
|
||||
kmsId: string;
|
||||
data: Buffer;
|
||||
signature: Buffer;
|
||||
signingAlgorithm: SigningAlgorithm;
|
||||
isDigest: boolean;
|
||||
};
|
||||
|
||||
export type TEncryptionWithKeyDTO = {
|
||||
key: Buffer;
|
||||
plainText: Buffer;
|
||||
@@ -67,9 +93,10 @@ export type TGetKeyMaterialDTO = {
|
||||
|
||||
export type TImportKeyMaterialDTO = {
|
||||
key: Buffer;
|
||||
algorithm: SymmetricEncryption;
|
||||
algorithm: SymmetricKeyAlgorithm;
|
||||
name?: string;
|
||||
isReserved: boolean;
|
||||
projectId: string;
|
||||
orgId: string;
|
||||
keyUsage: KmsKeyUsage;
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user