mirror of
https://github.com/awatertrevi/infisical.git
synced 2026-09-22 13:39:35 +00:00
fix: requested changes
This commit is contained in:
@@ -1,6 +1,6 @@
|
||||
import { Knex } from "knex";
|
||||
|
||||
import { KmsKeyIntent } from "@app/services/kms/kms-types";
|
||||
import { KmsKeyUsage } from "@app/services/kms/kms-types";
|
||||
|
||||
import { TableName } from "../schemas";
|
||||
|
||||
@@ -8,12 +8,12 @@ export async function up(knex: Knex): Promise<void> {
|
||||
const hasTypeColumn = await knex.schema.hasColumn(TableName.KmsKey, "type");
|
||||
|
||||
await knex.schema.alterTable(TableName.KmsKey, (t) => {
|
||||
if (!hasTypeColumn) t.string("type").notNullable().defaultTo(KmsKeyIntent.ENCRYPT_DECRYPT);
|
||||
if (!hasTypeColumn) t.string("keyUsage").notNullable().defaultTo(KmsKeyUsage.ENCRYPT_DECRYPT);
|
||||
});
|
||||
}
|
||||
|
||||
export async function down(knex: Knex): Promise<void> {
|
||||
await knex.schema.alterTable(TableName.KmsKey, (t) => {
|
||||
t.dropColumn("type");
|
||||
t.dropColumn("keyUsage");
|
||||
});
|
||||
}
|
||||
|
||||
@@ -17,7 +17,7 @@ export const KmsKeysSchema = z.object({
|
||||
createdAt: z.date(),
|
||||
updatedAt: z.date(),
|
||||
projectId: z.string().nullable().optional(),
|
||||
type: z.string().default("encrypt-decrypt")
|
||||
keyUsage: z.string().default("encrypt-decrypt")
|
||||
});
|
||||
|
||||
export type TKmsKeys = z.infer<typeof KmsKeysSchema>;
|
||||
|
||||
@@ -2,7 +2,7 @@ import z from "zod";
|
||||
|
||||
import { KmsKeysSchema } from "@app/db/schemas";
|
||||
import { EventType } from "@app/ee/services/audit-log/audit-log-types";
|
||||
import { SymmetricKeyEncryptDecrypt } from "@app/lib/crypto/cipher";
|
||||
import { SymmetricKeyAlgorithm } from "@app/lib/crypto/cipher";
|
||||
import { ForbiddenRequestError, NotFoundError } from "@app/lib/errors";
|
||||
import { writeLimit } from "@app/server/config/rateLimiter";
|
||||
import { verifyAuth } from "@app/server/plugins/auth/verify-auth";
|
||||
@@ -74,7 +74,7 @@ export const registerKmipSpecRouter = async (server: FastifyZodProvider) => {
|
||||
schema: {
|
||||
description: "KMIP endpoint for creating managed objects",
|
||||
body: z.object({
|
||||
algorithm: z.nativeEnum(SymmetricKeyEncryptDecrypt)
|
||||
algorithm: z.nativeEnum(SymmetricKeyAlgorithm)
|
||||
}),
|
||||
response: {
|
||||
200: KmsKeysSchema
|
||||
@@ -433,7 +433,7 @@ export const registerKmipSpecRouter = async (server: FastifyZodProvider) => {
|
||||
body: z.object({
|
||||
key: z.string(),
|
||||
name: z.string(),
|
||||
algorithm: z.nativeEnum(SymmetricKeyEncryptDecrypt)
|
||||
algorithm: z.nativeEnum(SymmetricKeyAlgorithm)
|
||||
}),
|
||||
response: {
|
||||
200: z.object({
|
||||
|
||||
@@ -4,8 +4,8 @@ import {
|
||||
} from "@app/ee/services/project-template/project-template-types";
|
||||
import { SshCaStatus, SshCertType } from "@app/ee/services/ssh/ssh-certificate-authority-types";
|
||||
import { SshCertTemplateStatus } from "@app/ee/services/ssh-certificate-template/ssh-certificate-template-types";
|
||||
import { SymmetricKeyEncryptDecrypt } from "@app/lib/crypto/cipher";
|
||||
import { AsymmetricKeySignVerify, SigningAlgorithm } from "@app/lib/crypto/sign/types";
|
||||
import { SymmetricKeyAlgorithm } from "@app/lib/crypto/cipher";
|
||||
import { AsymmetricKeyAlgorithm, SigningAlgorithm } from "@app/lib/crypto/sign/types";
|
||||
import { TProjectPermission } from "@app/lib/types";
|
||||
import { AppConnection } from "@app/services/app-connection/app-connection-enums";
|
||||
import { TCreateAppConnectionDTO, TUpdateAppConnectionDTO } from "@app/services/app-connection/app-connection-types";
|
||||
@@ -1903,7 +1903,7 @@ interface CreateCmekEvent {
|
||||
keyId: string;
|
||||
name: string;
|
||||
description?: string;
|
||||
encryptionAlgorithm: SymmetricKeyEncryptDecrypt | AsymmetricKeySignVerify;
|
||||
encryptionAlgorithm: SymmetricKeyAlgorithm | AsymmetricKeyAlgorithm;
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -7,7 +7,7 @@ import { BadRequestError, InternalServerError, NotFoundError } from "@app/lib/er
|
||||
import { alphaNumericNanoId } from "@app/lib/nanoid";
|
||||
import { TKmsKeyDALFactory } from "@app/services/kms/kms-key-dal";
|
||||
import { TKmsServiceFactory } from "@app/services/kms/kms-service";
|
||||
import { KmsDataKey, KmsKeyIntent } from "@app/services/kms/kms-types";
|
||||
import { KmsDataKey, KmsKeyUsage } from "@app/services/kms/kms-types";
|
||||
|
||||
import { TLicenseServiceFactory } from "../license/license-service";
|
||||
import { OrgPermissionActions, OrgPermissionSubjects } from "../permission/org-permission";
|
||||
@@ -115,7 +115,7 @@ export const externalKmsServiceFactory = ({
|
||||
{
|
||||
isReserved: false,
|
||||
description,
|
||||
type: KmsKeyIntent.ENCRYPT_DECRYPT,
|
||||
keyUsage: KmsKeyUsage.ENCRYPT_DECRYPT,
|
||||
name: kmsName,
|
||||
orgId: actorOrgId
|
||||
},
|
||||
|
||||
@@ -3,7 +3,7 @@ import { ForbiddenError } from "@casl/ability";
|
||||
import { BadRequestError, ForbiddenRequestError, NotFoundError } from "@app/lib/errors";
|
||||
import { TKmsKeyDALFactory } from "@app/services/kms/kms-key-dal";
|
||||
import { TKmsServiceFactory } from "@app/services/kms/kms-service";
|
||||
import { KmsKeyIntent } from "@app/services/kms/kms-types";
|
||||
import { KmsKeyUsage } from "@app/services/kms/kms-types";
|
||||
import { TProjectDALFactory } from "@app/services/project/project-dal";
|
||||
|
||||
import { OrgPermissionKmipActions, OrgPermissionSubjects } from "../permission/org-permission";
|
||||
@@ -404,7 +404,7 @@ export const kmipOperationServiceFactory = ({
|
||||
algorithm,
|
||||
isReserved: false,
|
||||
projectId,
|
||||
type: KmsKeyIntent.ENCRYPT_DECRYPT,
|
||||
keyUsage: KmsKeyUsage.ENCRYPT_DECRYPT,
|
||||
orgId: project.orgId
|
||||
});
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { SymmetricKeyEncryptDecrypt } from "@app/lib/crypto/cipher";
|
||||
import { SymmetricKeyAlgorithm } from "@app/lib/crypto/cipher";
|
||||
import { OrderByDirection, TOrgPermission, TProjectPermission } from "@app/lib/types";
|
||||
import { CertKeyAlgorithm } from "@app/services/certificate/certificate-types";
|
||||
|
||||
@@ -49,7 +49,7 @@ type KmipOperationBaseDTO = {
|
||||
} & Omit<TOrgPermission, "orgId">;
|
||||
|
||||
export type TKmipCreateDTO = {
|
||||
algorithm: SymmetricKeyEncryptDecrypt;
|
||||
algorithm: SymmetricKeyAlgorithm;
|
||||
} & KmipOperationBaseDTO;
|
||||
|
||||
export type TKmipGetDTO = {
|
||||
@@ -77,7 +77,7 @@ export type TKmipLocateDTO = KmipOperationBaseDTO;
|
||||
export type TKmipRegisterDTO = {
|
||||
name: string;
|
||||
key: string;
|
||||
algorithm: SymmetricKeyEncryptDecrypt;
|
||||
algorithm: SymmetricKeyAlgorithm;
|
||||
} & KmipOperationBaseDTO;
|
||||
|
||||
export type TSetupOrgKmipDTO = {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import crypto from "crypto";
|
||||
|
||||
import { SymmetricKeyEncryptDecrypt, TSymmetricEncryptionFns } from "./types";
|
||||
import { SymmetricKeyAlgorithm, TSymmetricEncryptionFns } from "./types";
|
||||
|
||||
const getIvLength = () => {
|
||||
return 12;
|
||||
@@ -11,7 +11,7 @@ const getTagLength = () => {
|
||||
};
|
||||
|
||||
export const symmetricCipherService = (
|
||||
type: SymmetricKeyEncryptDecrypt.AES_GCM_128 | SymmetricKeyEncryptDecrypt.AES_GCM_256
|
||||
type: SymmetricKeyAlgorithm.AES_GCM_128 | SymmetricKeyAlgorithm.AES_GCM_256
|
||||
): TSymmetricEncryptionFns => {
|
||||
const IV_LENGTH = getIvLength();
|
||||
const TAG_LENGTH = getTagLength();
|
||||
|
||||
@@ -1,2 +1,2 @@
|
||||
export { symmetricCipherService } from "./cipher";
|
||||
export { AllowedEncryptionKeyAlgorithms, SymmetricKeyEncryptDecrypt } from "./types";
|
||||
export { AllowedEncryptionKeyAlgorithms, SymmetricKeyAlgorithm } from "./types";
|
||||
|
||||
@@ -1,19 +1,17 @@
|
||||
import { z } from "zod";
|
||||
|
||||
import { AsymmetricKeySignVerify } from "../sign/types";
|
||||
import { AsymmetricKeyAlgorithm } from "../sign/types";
|
||||
|
||||
// Supported symmetric encrypt/decrypt algorithms
|
||||
export enum SymmetricKeyEncryptDecrypt {
|
||||
export enum SymmetricKeyAlgorithm {
|
||||
AES_GCM_256 = "aes-256-gcm",
|
||||
AES_GCM_128 = "aes-128-gcm"
|
||||
}
|
||||
export const SymmetricKeyEncryptDecryptEnum = z.enum(
|
||||
Object.values(SymmetricKeyEncryptDecrypt) as [string, ...string[]]
|
||||
).options;
|
||||
export const SymmetricKeyAlgorithmEnum = z.enum(Object.values(SymmetricKeyAlgorithm) as [string, ...string[]]).options;
|
||||
|
||||
export const AllowedEncryptionKeyAlgorithms = z.enum([
|
||||
...Object.values(SymmetricKeyEncryptDecrypt),
|
||||
...Object.values(AsymmetricKeySignVerify)
|
||||
...Object.values(SymmetricKeyAlgorithm),
|
||||
...Object.values(AsymmetricKeyAlgorithm)
|
||||
] as [string, ...string[]]).options;
|
||||
|
||||
export type TSymmetricEncryptionFns = {
|
||||
|
||||
@@ -1,2 +1,2 @@
|
||||
export { signingService } from "./signing";
|
||||
export { AsymmetricKeySignVerify, SigningAlgorithm } from "./types";
|
||||
export { AsymmetricKeyAlgorithm, SigningAlgorithm } from "./types";
|
||||
|
||||
@@ -3,7 +3,7 @@ import crypto from "crypto";
|
||||
import { BadRequestError } from "@app/lib/errors";
|
||||
import { logger } from "@app/lib/logger";
|
||||
|
||||
import { AsymmetricKeySignVerify, SigningAlgorithm, TAsymmetricSignVerifyFns } from "./types";
|
||||
import { AsymmetricKeyAlgorithm, SigningAlgorithm, TAsymmetricSignVerifyFns } from "./types";
|
||||
|
||||
// Map of signing algorithms to their parameters
|
||||
interface SigningParams {
|
||||
@@ -22,7 +22,7 @@ const SHA512_DIGEST_LENGTH = 64;
|
||||
* @param algorithm The signing algorithm to use
|
||||
* @returns Object with sign and verify functions
|
||||
*/
|
||||
export const signingService = (algorithm: AsymmetricKeySignVerify): TAsymmetricSignVerifyFns => {
|
||||
export const signingService = (algorithm: AsymmetricKeyAlgorithm): TAsymmetricSignVerifyFns => {
|
||||
const $getSigningParams = (signingAlgorithm: SigningAlgorithm): SigningParams => {
|
||||
switch (signingAlgorithm) {
|
||||
// RSA PSS
|
||||
@@ -76,10 +76,10 @@ export const signingService = (algorithm: AsymmetricKeySignVerify): TAsymmetricS
|
||||
};
|
||||
|
||||
// For ECC key generation, nodejs has some strange and hardly documented curve naming conventions
|
||||
const $getEcCurveName = (keyAlgorithm: AsymmetricKeySignVerify): string => {
|
||||
const $getEcCurveName = (keyAlgorithm: AsymmetricKeyAlgorithm): string => {
|
||||
// We will support more in the future
|
||||
switch (keyAlgorithm) {
|
||||
case AsymmetricKeySignVerify.ECC_NIST_P256:
|
||||
case AsymmetricKeyAlgorithm.ECC_NIST_P256:
|
||||
return "prime256v1";
|
||||
default:
|
||||
throw new Error(`Unsupported EC curve: ${keyAlgorithm}`);
|
||||
@@ -172,7 +172,6 @@ export const signingService = (algorithm: AsymmetricKeySignVerify): TAsymmetricS
|
||||
type: "pkcs8"
|
||||
});
|
||||
|
||||
// Return public key in PEM format for both RSA and EC
|
||||
const publicKey = crypto.createPublicKey(privateKeyObj).export({
|
||||
type: "spki",
|
||||
format: "pem"
|
||||
@@ -210,7 +209,8 @@ export const signingService = (algorithm: AsymmetricKeySignVerify): TAsymmetricS
|
||||
}
|
||||
// For PKCS1 v1.5 padding
|
||||
return signer.sign({
|
||||
key: privateKeyObject
|
||||
key: privateKeyObject,
|
||||
padding
|
||||
});
|
||||
}
|
||||
if (signingAlgorithm.startsWith("ECDSA")) {
|
||||
@@ -252,7 +252,8 @@ export const signingService = (algorithm: AsymmetricKeySignVerify): TAsymmetricS
|
||||
// For PKCS1 v1.5 padding
|
||||
return verifier.verify(
|
||||
{
|
||||
key: publicKey.toString()
|
||||
key: publicKey.toString(),
|
||||
padding
|
||||
},
|
||||
signature
|
||||
);
|
||||
|
||||
@@ -8,13 +8,13 @@ export type TAsymmetricSignVerifyFns = {
|
||||
};
|
||||
|
||||
// Supported asymmetric key types
|
||||
export enum AsymmetricKeySignVerify {
|
||||
export enum AsymmetricKeyAlgorithm {
|
||||
RSA_4096 = "rsa-4096",
|
||||
ECC_NIST_P256 = "ecc-nist-p256"
|
||||
}
|
||||
|
||||
export const AsymmetricKeySignVerifyEnum = z.enum(
|
||||
Object.values(AsymmetricKeySignVerify) as [string, ...string[]]
|
||||
export const AsymmetricKeyAlgorithmEnum = z.enum(
|
||||
Object.values(AsymmetricKeyAlgorithm) as [string, ...string[]]
|
||||
).options;
|
||||
|
||||
export enum SigningAlgorithm {
|
||||
|
||||
@@ -4,22 +4,20 @@ import { InternalKmsSchema, KmsKeysSchema } from "@app/db/schemas";
|
||||
import { EventType } from "@app/ee/services/audit-log/audit-log-types";
|
||||
import { KMS } from "@app/lib/api-docs";
|
||||
import { getBase64SizeInBytes, isBase64 } from "@app/lib/base64";
|
||||
import { AllowedEncryptionKeyAlgorithms, SymmetricKeyEncryptDecrypt } from "@app/lib/crypto/cipher";
|
||||
import { AsymmetricKeySignVerify, SigningAlgorithm } from "@app/lib/crypto/sign";
|
||||
import { AllowedEncryptionKeyAlgorithms, SymmetricKeyAlgorithm } from "@app/lib/crypto/cipher";
|
||||
import { AsymmetricKeyAlgorithm, SigningAlgorithm } from "@app/lib/crypto/sign";
|
||||
import { OrderByDirection } from "@app/lib/types";
|
||||
import { readLimit, writeLimit } from "@app/server/config/rateLimiter";
|
||||
import { slugSchema } from "@app/server/lib/schemas";
|
||||
import { verifyAuth } from "@app/server/plugins/auth/verify-auth";
|
||||
import { AuthMode } from "@app/services/auth/auth-type";
|
||||
import { CmekOrderBy, TCmekKeyEncryptionAlgorithm } from "@app/services/cmek/cmek-types";
|
||||
import { KmsKeyIntent } from "@app/services/kms/kms-types";
|
||||
import { KmsKeyUsage } from "@app/services/kms/kms-types";
|
||||
|
||||
const keyNameSchema = slugSchema({ min: 1, max: 32, field: "Name" });
|
||||
const keyDescriptionSchema = z.string().trim().max(500).optional();
|
||||
|
||||
const CmekSchema = KmsKeysSchema.merge(
|
||||
InternalKmsSchema.pick({ version: true, encryptionAlgorithm: true, type: true })
|
||||
).omit({
|
||||
const CmekSchema = KmsKeysSchema.merge(InternalKmsSchema.pick({ version: true, encryptionAlgorithm: true })).omit({
|
||||
isReserved: true
|
||||
});
|
||||
|
||||
@@ -54,37 +52,37 @@ export const registerCmekRouter = async (server: FastifyZodProvider) => {
|
||||
projectId: z.string().describe(KMS.CREATE_KEY.projectId),
|
||||
name: keyNameSchema.describe(KMS.CREATE_KEY.name),
|
||||
description: keyDescriptionSchema.describe(KMS.CREATE_KEY.description),
|
||||
type: z
|
||||
.nativeEnum(KmsKeyIntent)
|
||||
keyUsage: z
|
||||
.nativeEnum(KmsKeyUsage)
|
||||
.optional()
|
||||
.default(KmsKeyIntent.ENCRYPT_DECRYPT)
|
||||
.default(KmsKeyUsage.ENCRYPT_DECRYPT)
|
||||
.describe(KMS.CREATE_KEY.type),
|
||||
encryptionAlgorithm: z
|
||||
.enum(AllowedEncryptionKeyAlgorithms)
|
||||
.optional()
|
||||
.default(SymmetricKeyEncryptDecrypt.AES_GCM_256)
|
||||
.default(SymmetricKeyAlgorithm.AES_GCM_256)
|
||||
.describe(KMS.CREATE_KEY.encryptionAlgorithm)
|
||||
})
|
||||
.superRefine((data, ctx) => {
|
||||
if (
|
||||
data.type === KmsKeyIntent.ENCRYPT_DECRYPT &&
|
||||
!Object.values(SymmetricKeyEncryptDecrypt).includes(data.encryptionAlgorithm as SymmetricKeyEncryptDecrypt)
|
||||
data.keyUsage === KmsKeyUsage.ENCRYPT_DECRYPT &&
|
||||
!Object.values(SymmetricKeyAlgorithm).includes(data.encryptionAlgorithm as SymmetricKeyAlgorithm)
|
||||
) {
|
||||
ctx.addIssue({
|
||||
code: z.ZodIssueCode.custom,
|
||||
message: `encryptionAlgorithm must be a valid symmetric encryption algorithm. Valid options are: ${Object.values(
|
||||
SymmetricKeyEncryptDecrypt
|
||||
SymmetricKeyAlgorithm
|
||||
).join(", ")}`
|
||||
});
|
||||
}
|
||||
if (
|
||||
data.type === KmsKeyIntent.SIGN_VERIFY &&
|
||||
!Object.values(AsymmetricKeySignVerify).includes(data.encryptionAlgorithm as AsymmetricKeySignVerify)
|
||||
data.keyUsage === KmsKeyUsage.SIGN_VERIFY &&
|
||||
!Object.values(AsymmetricKeyAlgorithm).includes(data.encryptionAlgorithm as AsymmetricKeyAlgorithm)
|
||||
) {
|
||||
ctx.addIssue({
|
||||
code: z.ZodIssueCode.custom,
|
||||
message: `encryptionAlgorithm must be a valid asymmetric sign-verify algorithm. Valid options are: ${Object.values(
|
||||
AsymmetricKeySignVerify
|
||||
AsymmetricKeyAlgorithm
|
||||
).join(", ")}`
|
||||
});
|
||||
}
|
||||
@@ -98,7 +96,7 @@ export const registerCmekRouter = async (server: FastifyZodProvider) => {
|
||||
onRequest: verifyAuth([AuthMode.JWT, AuthMode.IDENTITY_ACCESS_TOKEN]),
|
||||
handler: async (req) => {
|
||||
const {
|
||||
body: { projectId, name, description, encryptionAlgorithm, type },
|
||||
body: { projectId, name, description, encryptionAlgorithm, keyUsage },
|
||||
permission
|
||||
} = req;
|
||||
|
||||
@@ -109,7 +107,7 @@ export const registerCmekRouter = async (server: FastifyZodProvider) => {
|
||||
name,
|
||||
description,
|
||||
encryptionAlgorithm: encryptionAlgorithm as TCmekKeyEncryptionAlgorithm,
|
||||
type
|
||||
keyUsage
|
||||
},
|
||||
permission
|
||||
);
|
||||
@@ -167,7 +165,7 @@ export const registerCmekRouter = async (server: FastifyZodProvider) => {
|
||||
|
||||
await server.services.auditLog.createAuditLog({
|
||||
...req.auditLogInfo,
|
||||
orgId: permission.orgId,
|
||||
projectId: cmek.projectId!,
|
||||
event: {
|
||||
type: EventType.UPDATE_CMEK,
|
||||
metadata: {
|
||||
@@ -210,7 +208,7 @@ export const registerCmekRouter = async (server: FastifyZodProvider) => {
|
||||
|
||||
await server.services.auditLog.createAuditLog({
|
||||
...req.auditLogInfo,
|
||||
orgId: permission.orgId,
|
||||
projectId: cmek.projectId!,
|
||||
event: {
|
||||
type: EventType.DELETE_CMEK,
|
||||
metadata: {
|
||||
@@ -390,11 +388,11 @@ export const registerCmekRouter = async (server: FastifyZodProvider) => {
|
||||
permission
|
||||
} = req;
|
||||
|
||||
const ciphertext = await server.services.cmek.cmekEncrypt({ keyId, plaintext }, permission);
|
||||
const { ciphertext, projectId } = await server.services.cmek.cmekEncrypt({ keyId, plaintext }, permission);
|
||||
|
||||
await server.services.auditLog.createAuditLog({
|
||||
...req.auditLogInfo,
|
||||
orgId: permission.orgId,
|
||||
projectId,
|
||||
event: {
|
||||
type: EventType.CMEK_ENCRYPT,
|
||||
metadata: {
|
||||
@@ -431,11 +429,11 @@ export const registerCmekRouter = async (server: FastifyZodProvider) => {
|
||||
permission
|
||||
} = req;
|
||||
|
||||
const publicKey = await server.services.cmek.getPublicKey({ keyId }, permission);
|
||||
const { publicKey, projectId } = await server.services.cmek.getPublicKey({ keyId }, permission);
|
||||
|
||||
await server.services.auditLog.createAuditLog({
|
||||
...req.auditLogInfo,
|
||||
orgId: permission.orgId,
|
||||
projectId,
|
||||
event: {
|
||||
type: EventType.CMEK_GET_PUBLIC_KEY,
|
||||
metadata: {
|
||||
@@ -444,7 +442,7 @@ export const registerCmekRouter = async (server: FastifyZodProvider) => {
|
||||
}
|
||||
});
|
||||
|
||||
return publicKey;
|
||||
return { publicKey };
|
||||
}
|
||||
});
|
||||
|
||||
@@ -469,11 +467,14 @@ export const registerCmekRouter = async (server: FastifyZodProvider) => {
|
||||
handler: async (req) => {
|
||||
const { keyId } = req.params;
|
||||
|
||||
const result = await server.services.cmek.listSigningAlgorithms({ keyId }, req.permission);
|
||||
const { signingAlgorithms, projectId } = await server.services.cmek.listSigningAlgorithms(
|
||||
{ keyId },
|
||||
req.permission
|
||||
);
|
||||
|
||||
await server.services.auditLog.createAuditLog({
|
||||
...req.auditLogInfo,
|
||||
orgId: req.permission.orgId,
|
||||
projectId,
|
||||
event: {
|
||||
type: EventType.CMEK_LIST_SIGNING_ALGORITHMS,
|
||||
metadata: {
|
||||
@@ -482,7 +483,7 @@ export const registerCmekRouter = async (server: FastifyZodProvider) => {
|
||||
}
|
||||
});
|
||||
|
||||
return result;
|
||||
return { signingAlgorithms };
|
||||
}
|
||||
});
|
||||
|
||||
@@ -527,11 +528,14 @@ export const registerCmekRouter = async (server: FastifyZodProvider) => {
|
||||
permission
|
||||
} = req;
|
||||
|
||||
const result = await server.services.cmek.cmekSign({ keyId: inputKeyId, data, signingAlgorithm }, permission);
|
||||
const { projectId, ...result } = await server.services.cmek.cmekSign(
|
||||
{ keyId: inputKeyId, data, signingAlgorithm },
|
||||
permission
|
||||
);
|
||||
|
||||
await server.services.auditLog.createAuditLog({
|
||||
...req.auditLogInfo,
|
||||
orgId: permission.orgId,
|
||||
projectId,
|
||||
event: {
|
||||
type: EventType.CMEK_SIGN,
|
||||
metadata: {
|
||||
@@ -597,11 +601,14 @@ export const registerCmekRouter = async (server: FastifyZodProvider) => {
|
||||
permission
|
||||
} = req;
|
||||
|
||||
const result = await server.services.cmek.cmekVerify({ keyId, data, signature, signingAlgorithm }, permission);
|
||||
const { projectId, ...result } = await server.services.cmek.cmekVerify(
|
||||
{ keyId, data, signature, signingAlgorithm },
|
||||
permission
|
||||
);
|
||||
|
||||
await server.services.auditLog.createAuditLog({
|
||||
...req.auditLogInfo,
|
||||
orgId: permission.orgId,
|
||||
projectId,
|
||||
event: {
|
||||
type: EventType.CMEK_VERIFY,
|
||||
metadata: {
|
||||
@@ -645,11 +652,11 @@ export const registerCmekRouter = async (server: FastifyZodProvider) => {
|
||||
permission
|
||||
} = req;
|
||||
|
||||
const plaintext = await server.services.cmek.cmekDecrypt({ keyId, ciphertext }, permission);
|
||||
const { plaintext, projectId } = await server.services.cmek.cmekDecrypt({ keyId, ciphertext }, permission);
|
||||
|
||||
await server.services.auditLog.createAuditLog({
|
||||
...req.auditLogInfo,
|
||||
orgId: permission.orgId,
|
||||
projectId,
|
||||
event: {
|
||||
type: EventType.CMEK_DECRYPT,
|
||||
metadata: {
|
||||
|
||||
@@ -22,7 +22,7 @@ import {
|
||||
import { TKmsKeyDALFactory } from "@app/services/kms/kms-key-dal";
|
||||
import { TKmsServiceFactory } from "@app/services/kms/kms-service";
|
||||
|
||||
import { KmsKeyIntent } from "../kms/kms-types";
|
||||
import { KmsKeyUsage } from "../kms/kms-types";
|
||||
import { TProjectDALFactory } from "../project/project-dal";
|
||||
|
||||
type TCmekServiceFactoryDep = {
|
||||
@@ -228,7 +228,10 @@ 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) => {
|
||||
@@ -249,7 +252,7 @@ export const cmekServiceFactory = ({ kmsService, kmsDAL, permissionService, proj
|
||||
|
||||
ForbiddenError.from(permission).throwUnlessCan(ProjectPermissionCmekActions.Read, ProjectPermissionSub.Cmek);
|
||||
|
||||
if (key.type !== KmsKeyIntent.SIGN_VERIFY) {
|
||||
if (key.keyUsage !== KmsKeyUsage.SIGN_VERIFY) {
|
||||
throw new BadRequestError({ message: `Key with ID '${keyId}' is not intended for signing` });
|
||||
}
|
||||
|
||||
@@ -276,7 +279,7 @@ export const cmekServiceFactory = ({ kmsService, kmsDAL, permissionService, proj
|
||||
throw new BadRequestError({ message: `Unsupported encryption algorithm: ${encryptionAlgorithm}` });
|
||||
}
|
||||
|
||||
return { signingAlgorithms: selectedAlgorithm.signingAlgorithms };
|
||||
return { signingAlgorithms: selectedAlgorithm.signingAlgorithms, projectId: key.projectId };
|
||||
};
|
||||
|
||||
const getPublicKey = async ({ keyId }: TCmekGetPublicKeyDTO, actor: OrgServiceActor) => {
|
||||
@@ -299,7 +302,7 @@ export const cmekServiceFactory = ({ kmsService, kmsDAL, permissionService, proj
|
||||
|
||||
const publicKey = await kmsService.getPublicKey({ kmsId: keyId });
|
||||
|
||||
return { publicKey };
|
||||
return { publicKey, projectId: key.projectId };
|
||||
};
|
||||
|
||||
const cmekSign = async ({ keyId, data, signingAlgorithm }: TCmekSignDTO, actor: OrgServiceActor) => {
|
||||
@@ -329,6 +332,7 @@ export const cmekServiceFactory = ({ kmsService, kmsDAL, permissionService, proj
|
||||
return {
|
||||
signature: signature.toString("base64"),
|
||||
keyId: key.id,
|
||||
projectId: key.projectId,
|
||||
signingAlgorithm: algorithm
|
||||
};
|
||||
};
|
||||
@@ -363,6 +367,7 @@ export const cmekServiceFactory = ({ kmsService, kmsDAL, permissionService, proj
|
||||
return {
|
||||
signatureValid,
|
||||
keyId: key.id,
|
||||
projectId: key.projectId,
|
||||
signingAlgorithm: algorithm
|
||||
};
|
||||
};
|
||||
@@ -391,7 +396,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 {
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
import { SymmetricKeyEncryptDecrypt } from "@app/lib/crypto/cipher";
|
||||
import { AsymmetricKeySignVerify, SigningAlgorithm } from "@app/lib/crypto/sign";
|
||||
import { SymmetricKeyAlgorithm } from "@app/lib/crypto/cipher";
|
||||
import { AsymmetricKeyAlgorithm, SigningAlgorithm } from "@app/lib/crypto/sign";
|
||||
import { OrderByDirection } from "@app/lib/types";
|
||||
|
||||
import { KmsKeyIntent } from "../kms/kms-types";
|
||||
import { KmsKeyUsage } from "../kms/kms-types";
|
||||
|
||||
export type TCmekKeyEncryptionAlgorithm = SymmetricKeyEncryptDecrypt | AsymmetricKeySignVerify;
|
||||
export type TCmekKeyEncryptionAlgorithm = SymmetricKeyAlgorithm | AsymmetricKeyAlgorithm;
|
||||
|
||||
export type TCreateCmekDTO = {
|
||||
orgId: string;
|
||||
@@ -12,7 +12,7 @@ export type TCreateCmekDTO = {
|
||||
name: string;
|
||||
description?: string;
|
||||
encryptionAlgorithm: TCmekKeyEncryptionAlgorithm;
|
||||
type: KmsKeyIntent;
|
||||
keyUsage: KmsKeyUsage;
|
||||
};
|
||||
|
||||
export type TUpdabteCmekByIdDTO = {
|
||||
|
||||
@@ -1,36 +1,36 @@
|
||||
import { SymmetricKeyEncryptDecrypt } from "@app/lib/crypto/cipher";
|
||||
import { AsymmetricKeySignVerify } from "@app/lib/crypto/sign";
|
||||
import { SymmetricKeyAlgorithm } from "@app/lib/crypto/cipher";
|
||||
import { AsymmetricKeyAlgorithm } from "@app/lib/crypto/sign";
|
||||
import { BadRequestError } from "@app/lib/errors";
|
||||
|
||||
import { KmsKeyIntent } from "./kms-types";
|
||||
import { KmsKeyUsage } from "./kms-types";
|
||||
|
||||
export const KMS_ROOT_CONFIG_UUID = "00000000-0000-0000-0000-000000000000";
|
||||
|
||||
export const getByteLengthForSymmetricEncryptionAlgorithm = (encryptionAlgorithm: SymmetricKeyEncryptDecrypt) => {
|
||||
export const getByteLengthForSymmetricEncryptionAlgorithm = (encryptionAlgorithm: SymmetricKeyAlgorithm) => {
|
||||
switch (encryptionAlgorithm) {
|
||||
case SymmetricKeyEncryptDecrypt.AES_GCM_128:
|
||||
case SymmetricKeyAlgorithm.AES_GCM_128:
|
||||
return 16;
|
||||
case SymmetricKeyEncryptDecrypt.AES_GCM_256:
|
||||
case SymmetricKeyAlgorithm.AES_GCM_256:
|
||||
default:
|
||||
return 32;
|
||||
}
|
||||
};
|
||||
|
||||
export const verifyKeyTypeAndAlgorithm = (
|
||||
type: KmsKeyIntent,
|
||||
algorithm: SymmetricKeyEncryptDecrypt | AsymmetricKeySignVerify,
|
||||
keyUsage: KmsKeyUsage,
|
||||
algorithm: SymmetricKeyAlgorithm | AsymmetricKeyAlgorithm,
|
||||
extra?: {
|
||||
forceType?: KmsKeyIntent;
|
||||
forceType?: KmsKeyUsage;
|
||||
}
|
||||
) => {
|
||||
if (extra?.forceType && type !== extra.forceType) {
|
||||
if (extra?.forceType && keyUsage !== extra.forceType) {
|
||||
throw new BadRequestError({
|
||||
message: `Unsupported key type, expected ${extra.forceType} but got ${type}`
|
||||
message: `Unsupported key type, expected ${extra.forceType} but got ${keyUsage}`
|
||||
});
|
||||
}
|
||||
|
||||
if (type === KmsKeyIntent.ENCRYPT_DECRYPT) {
|
||||
if (!Object.values(SymmetricKeyEncryptDecrypt).includes(algorithm as SymmetricKeyEncryptDecrypt)) {
|
||||
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}`
|
||||
});
|
||||
@@ -39,8 +39,8 @@ export const verifyKeyTypeAndAlgorithm = (
|
||||
return true;
|
||||
}
|
||||
|
||||
if (type === KmsKeyIntent.SIGN_VERIFY) {
|
||||
if (!Object.values(AsymmetricKeySignVerify).includes(algorithm as AsymmetricKeySignVerify)) {
|
||||
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}`
|
||||
});
|
||||
@@ -50,6 +50,6 @@ export const verifyKeyTypeAndAlgorithm = (
|
||||
}
|
||||
|
||||
throw new BadRequestError({
|
||||
message: `Unsupported key type: ${type as string}`
|
||||
message: `Unsupported key type: ${keyUsage as string}`
|
||||
});
|
||||
};
|
||||
|
||||
@@ -17,9 +17,9 @@ 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, SymmetricKeyEncryptDecrypt } from "@app/lib/crypto/cipher";
|
||||
import { symmetricCipherService, SymmetricKeyAlgorithm } from "@app/lib/crypto/cipher";
|
||||
import { generateHash } from "@app/lib/crypto/encryption";
|
||||
import { AsymmetricKeySignVerify, signingService } from "@app/lib/crypto/sign";
|
||||
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";
|
||||
@@ -36,7 +36,7 @@ import { TKmsKeyDALFactory } from "./kms-key-dal";
|
||||
import { TKmsRootConfigDALFactory } from "./kms-root-config-dal";
|
||||
import {
|
||||
KmsDataKey,
|
||||
KmsKeyIntent,
|
||||
KmsKeyUsage,
|
||||
KmsType,
|
||||
RootKeyEncryptionStrategy,
|
||||
TDecryptWithKeyDTO,
|
||||
@@ -94,21 +94,21 @@ export const kmsServiceFactory = ({
|
||||
tx,
|
||||
name,
|
||||
projectId,
|
||||
encryptionAlgorithm = SymmetricKeyEncryptDecrypt.AES_GCM_256,
|
||||
type = KmsKeyIntent.ENCRYPT_DECRYPT,
|
||||
encryptionAlgorithm = SymmetricKeyAlgorithm.AES_GCM_256,
|
||||
keyUsage = KmsKeyUsage.ENCRYPT_DECRYPT,
|
||||
description
|
||||
}: TGenerateKMSDTO) => {
|
||||
// daniel: ensure that the key type (sign/encrypt) and the encryption algorithm are compatible.
|
||||
verifyKeyTypeAndAlgorithm(type, encryptionAlgorithm);
|
||||
verifyKeyTypeAndAlgorithm(keyUsage, encryptionAlgorithm);
|
||||
|
||||
let kmsKeyMaterial: Buffer | null = null;
|
||||
if (type === KmsKeyIntent.ENCRYPT_DECRYPT) {
|
||||
if (keyUsage === KmsKeyUsage.ENCRYPT_DECRYPT) {
|
||||
kmsKeyMaterial = randomSecureBytes(
|
||||
getByteLengthForSymmetricEncryptionAlgorithm(encryptionAlgorithm as SymmetricKeyEncryptDecrypt)
|
||||
getByteLengthForSymmetricEncryptionAlgorithm(encryptionAlgorithm as SymmetricKeyAlgorithm)
|
||||
);
|
||||
} else if (type === KmsKeyIntent.SIGN_VERIFY) {
|
||||
} else if (keyUsage === KmsKeyUsage.SIGN_VERIFY) {
|
||||
const { generateAsymmetricPrivateKey, getPublicKeyFromPrivateKey } = signingService(
|
||||
encryptionAlgorithm as AsymmetricKeySignVerify
|
||||
encryptionAlgorithm as AsymmetricKeyAlgorithm
|
||||
);
|
||||
kmsKeyMaterial = await generateAsymmetricPrivateKey();
|
||||
|
||||
@@ -118,18 +118,18 @@ export const kmsServiceFactory = ({
|
||||
|
||||
if (!kmsKeyMaterial) {
|
||||
throw new BadRequestError({
|
||||
message: `Invalid KMS key type. No key material was created for key type '${type}' using algorithm '${encryptionAlgorithm}'`
|
||||
message: `Invalid KMS key type. No key material was created for key usage '${keyUsage}' using algorithm '${encryptionAlgorithm}'`
|
||||
});
|
||||
}
|
||||
|
||||
const cipher = symmetricCipherService(SymmetricKeyEncryptDecrypt.AES_GCM_256);
|
||||
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,
|
||||
type,
|
||||
keyUsage,
|
||||
orgId,
|
||||
isReserved,
|
||||
projectId,
|
||||
@@ -169,7 +169,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(SymmetricKeyEncryptDecrypt.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
|
||||
@@ -184,7 +184,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(SymmetricKeyEncryptDecrypt.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);
|
||||
@@ -262,7 +262,7 @@ export const kmsServiceFactory = ({
|
||||
};
|
||||
|
||||
const encryptWithRootKey = () => {
|
||||
const cipher = symmetricCipherService(SymmetricKeyEncryptDecrypt.AES_GCM_256);
|
||||
const cipher = symmetricCipherService(SymmetricKeyAlgorithm.AES_GCM_256);
|
||||
|
||||
return (plainTextBuffer: Buffer) => {
|
||||
const encryptedBuffer = cipher.encrypt(plainTextBuffer, ROOT_ENCRYPTION_KEY);
|
||||
@@ -271,7 +271,7 @@ export const kmsServiceFactory = ({
|
||||
};
|
||||
|
||||
const decryptWithRootKey = () => {
|
||||
const cipher = symmetricCipherService(SymmetricKeyEncryptDecrypt.AES_GCM_256);
|
||||
const cipher = symmetricCipherService(SymmetricKeyAlgorithm.AES_GCM_256);
|
||||
|
||||
return (cipherTextBuffer: Buffer) => {
|
||||
return cipher.decrypt(cipherTextBuffer, ROOT_ENCRYPTION_KEY);
|
||||
@@ -290,9 +290,9 @@ export const kmsServiceFactory = ({
|
||||
throw new NotFoundError({ message: `KMS with ID '${kmsId}' not found` });
|
||||
}
|
||||
|
||||
const encryptionAlgorithm = kmsDoc.internalKms?.encryptionAlgorithm as SymmetricKeyEncryptDecrypt;
|
||||
verifyKeyTypeAndAlgorithm(kmsDoc.type as KmsKeyIntent, encryptionAlgorithm, {
|
||||
forceType: KmsKeyIntent.ENCRYPT_DECRYPT
|
||||
const encryptionAlgorithm = kmsDoc.internalKms?.encryptionAlgorithm as SymmetricKeyAlgorithm;
|
||||
verifyKeyTypeAndAlgorithm(kmsDoc.keyUsage as KmsKeyUsage, encryptionAlgorithm, {
|
||||
forceType: KmsKeyUsage.ENCRYPT_DECRYPT
|
||||
});
|
||||
|
||||
if (kmsDoc.externalKms) {
|
||||
@@ -356,7 +356,7 @@ export const kmsServiceFactory = ({
|
||||
}
|
||||
|
||||
// internal KMS
|
||||
const keyCipher = symmetricCipherService(SymmetricKeyEncryptDecrypt.AES_GCM_256);
|
||||
const keyCipher = symmetricCipherService(SymmetricKeyAlgorithm.AES_GCM_256);
|
||||
const dataCipher = symmetricCipherService(encryptionAlgorithm);
|
||||
const kmsKey = keyCipher.decrypt(kmsDoc.internalKms?.encryptedKey as Buffer, ROOT_ENCRYPTION_KEY);
|
||||
|
||||
@@ -385,22 +385,22 @@ export const kmsServiceFactory = ({
|
||||
});
|
||||
}
|
||||
|
||||
const keyCipher = symmetricCipherService(SymmetricKeyEncryptDecrypt.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, type }: TImportKeyMaterialDTO,
|
||||
{ key, algorithm, name, isReserved, projectId, orgId, keyUsage }: TImportKeyMaterialDTO,
|
||||
tx?: Knex
|
||||
) => {
|
||||
// daniel: currently we only support imports for encrypt/decrypt keys
|
||||
verifyKeyTypeAndAlgorithm(type, algorithm, { forceType: KmsKeyIntent.ENCRYPT_DECRYPT });
|
||||
verifyKeyTypeAndAlgorithm(keyUsage, algorithm, { forceType: KmsKeyUsage.ENCRYPT_DECRYPT });
|
||||
|
||||
const cipher = symmetricCipherService(SymmetricKeyEncryptDecrypt.AES_GCM_256);
|
||||
const cipher = symmetricCipherService(SymmetricKeyAlgorithm.AES_GCM_256);
|
||||
|
||||
const expectedByteLength = getByteLengthForSymmetricEncryptionAlgorithm(algorithm as SymmetricKeyEncryptDecrypt);
|
||||
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`
|
||||
@@ -413,7 +413,7 @@ export const kmsServiceFactory = ({
|
||||
const kmsDoc = await kmsDAL.create(
|
||||
{
|
||||
name: sanitizedName,
|
||||
type: KmsKeyIntent.ENCRYPT_DECRYPT,
|
||||
keyUsage: KmsKeyUsage.ENCRYPT_DECRYPT,
|
||||
orgId,
|
||||
isReserved,
|
||||
projectId
|
||||
@@ -443,13 +443,13 @@ export const kmsServiceFactory = ({
|
||||
throw new NotFoundError({ message: `KMS with ID '${kmsId}' not found` });
|
||||
}
|
||||
|
||||
const encryptionAlgorithm = kmsDoc.internalKms?.encryptionAlgorithm as AsymmetricKeySignVerify;
|
||||
const encryptionAlgorithm = kmsDoc.internalKms?.encryptionAlgorithm as AsymmetricKeyAlgorithm;
|
||||
|
||||
verifyKeyTypeAndAlgorithm(kmsDoc.type as KmsKeyIntent, encryptionAlgorithm, {
|
||||
forceType: KmsKeyIntent.SIGN_VERIFY
|
||||
verifyKeyTypeAndAlgorithm(kmsDoc.keyUsage as KmsKeyUsage, encryptionAlgorithm, {
|
||||
forceType: KmsKeyUsage.SIGN_VERIFY
|
||||
});
|
||||
|
||||
const keyCipher = symmetricCipherService(SymmetricKeyEncryptDecrypt.AES_GCM_256);
|
||||
const keyCipher = symmetricCipherService(SymmetricKeyAlgorithm.AES_GCM_256);
|
||||
const kmsKey = keyCipher.decrypt(kmsDoc.internalKms?.encryptedKey as Buffer, ROOT_ENCRYPTION_KEY);
|
||||
|
||||
const publicKeyBuffer = signingService(encryptionAlgorithm).getPublicKeyFromPrivateKey(kmsKey);
|
||||
@@ -469,12 +469,12 @@ export const kmsServiceFactory = ({
|
||||
throw new NotFoundError({ message: `KMS with ID '${kmsId}' not found` });
|
||||
}
|
||||
|
||||
const encryptionAlgorithm = kmsDoc.internalKms?.encryptionAlgorithm as AsymmetricKeySignVerify;
|
||||
verifyKeyTypeAndAlgorithm(kmsDoc.type as KmsKeyIntent, encryptionAlgorithm, {
|
||||
forceType: KmsKeyIntent.SIGN_VERIFY
|
||||
const encryptionAlgorithm = kmsDoc.internalKms?.encryptionAlgorithm as AsymmetricKeyAlgorithm;
|
||||
verifyKeyTypeAndAlgorithm(kmsDoc.keyUsage as KmsKeyUsage, encryptionAlgorithm, {
|
||||
forceType: KmsKeyUsage.SIGN_VERIFY
|
||||
});
|
||||
|
||||
const keyCipher = symmetricCipherService(SymmetricKeyEncryptDecrypt.AES_GCM_256);
|
||||
const keyCipher = symmetricCipherService(SymmetricKeyAlgorithm.AES_GCM_256);
|
||||
const { sign } = signingService(encryptionAlgorithm);
|
||||
return ({ data, signingAlgorithm }: Pick<TSignWithKmsDTO, "data" | "signingAlgorithm">) => {
|
||||
const kmsKey = keyCipher.decrypt(kmsDoc.internalKms?.encryptedKey as Buffer, ROOT_ENCRYPTION_KEY);
|
||||
@@ -493,12 +493,12 @@ export const kmsServiceFactory = ({
|
||||
throw new NotFoundError({ message: `KMS with ID '${kmsId}' not found` });
|
||||
}
|
||||
|
||||
const encryptionAlgorithm = kmsDoc.internalKms?.encryptionAlgorithm as AsymmetricKeySignVerify;
|
||||
verifyKeyTypeAndAlgorithm(kmsDoc.type as KmsKeyIntent, encryptionAlgorithm, {
|
||||
forceType: KmsKeyIntent.SIGN_VERIFY
|
||||
const encryptionAlgorithm = kmsDoc.internalKms?.encryptionAlgorithm as AsymmetricKeyAlgorithm;
|
||||
verifyKeyTypeAndAlgorithm(kmsDoc.keyUsage as KmsKeyUsage, encryptionAlgorithm, {
|
||||
forceType: KmsKeyUsage.SIGN_VERIFY
|
||||
});
|
||||
|
||||
const keyCipher = symmetricCipherService(SymmetricKeyEncryptDecrypt.AES_GCM_256);
|
||||
const keyCipher = symmetricCipherService(SymmetricKeyAlgorithm.AES_GCM_256);
|
||||
const { verify, getPublicKeyFromPrivateKey } = signingService(encryptionAlgorithm);
|
||||
return ({ data, signature }: Pick<TVerifyWithKmsDTO, "data" | "signature">) => {
|
||||
const kmsKey = keyCipher.decrypt(kmsDoc.internalKms?.encryptedKey as Buffer, ROOT_ENCRYPTION_KEY);
|
||||
@@ -515,9 +515,9 @@ export const kmsServiceFactory = ({
|
||||
throw new NotFoundError({ message: `KMS with ID '${kmsId}' not found` });
|
||||
}
|
||||
|
||||
const encryptionAlgorithm = kmsDoc.internalKms?.encryptionAlgorithm as SymmetricKeyEncryptDecrypt;
|
||||
verifyKeyTypeAndAlgorithm(kmsDoc.type as KmsKeyIntent, encryptionAlgorithm, {
|
||||
forceType: KmsKeyIntent.ENCRYPT_DECRYPT
|
||||
const encryptionAlgorithm = kmsDoc.internalKms?.encryptionAlgorithm as SymmetricKeyAlgorithm;
|
||||
verifyKeyTypeAndAlgorithm(kmsDoc.keyUsage as KmsKeyUsage, encryptionAlgorithm, {
|
||||
forceType: KmsKeyUsage.ENCRYPT_DECRYPT
|
||||
});
|
||||
|
||||
if (kmsDoc.externalKms) {
|
||||
@@ -575,7 +575,7 @@ export const kmsServiceFactory = ({
|
||||
}
|
||||
|
||||
// internal KMS
|
||||
const keyCipher = symmetricCipherService(SymmetricKeyEncryptDecrypt.AES_GCM_256);
|
||||
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);
|
||||
@@ -850,7 +850,7 @@ export const kmsServiceFactory = ({
|
||||
|
||||
// case 2: root key is encrypted with software encryption
|
||||
if (kmsRootConfig.encryptionStrategy === RootKeyEncryptionStrategy.Software) {
|
||||
const cipher = symmetricCipherService(SymmetricKeyEncryptDecrypt.AES_GCM_256);
|
||||
const cipher = symmetricCipherService(SymmetricKeyAlgorithm.AES_GCM_256);
|
||||
const encryptionKeyBuffer = $getBasicEncryptionKey();
|
||||
|
||||
return cipher.decrypt(kmsRootConfig.encryptedRootKey, encryptionKeyBuffer);
|
||||
@@ -870,7 +870,7 @@ export const kmsServiceFactory = ({
|
||||
}
|
||||
|
||||
if (strategy === RootKeyEncryptionStrategy.Software) {
|
||||
const cipher = symmetricCipherService(SymmetricKeyEncryptDecrypt.AES_GCM_256);
|
||||
const cipher = symmetricCipherService(SymmetricKeyAlgorithm.AES_GCM_256);
|
||||
const encryptionKeyBuffer = $getBasicEncryptionKey();
|
||||
|
||||
return cipher.encrypt(plainKeyBuffer, encryptionKeyBuffer);
|
||||
@@ -886,7 +886,7 @@ export const kmsServiceFactory = ({
|
||||
const createCipherPairWithDataKey = async (encryptionContext: TEncryptWithKmsDataKeyDTO, trx?: Knex) => {
|
||||
const dataKey = await $getDataKey(encryptionContext, trx);
|
||||
|
||||
const cipher = symmetricCipherService(SymmetricKeyEncryptDecrypt.AES_GCM_256);
|
||||
const cipher = symmetricCipherService(SymmetricKeyAlgorithm.AES_GCM_256);
|
||||
|
||||
return {
|
||||
encryptor: ({ plainText }: Pick<TEncryptWithKmsDTO, "plainText">) => {
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { Knex } from "knex";
|
||||
|
||||
import { SymmetricKeyEncryptDecrypt } from "@app/lib/crypto/cipher";
|
||||
import { AsymmetricKeySignVerify, SigningAlgorithm } from "@app/lib/crypto/sign/types";
|
||||
import { SymmetricKeyAlgorithm } from "@app/lib/crypto/cipher";
|
||||
import { AsymmetricKeyAlgorithm, SigningAlgorithm } from "@app/lib/crypto/sign/types";
|
||||
|
||||
export enum KmsDataKey {
|
||||
Organization,
|
||||
@@ -14,7 +14,7 @@ export enum KmsType {
|
||||
Internal = "internal"
|
||||
}
|
||||
|
||||
export enum KmsKeyIntent {
|
||||
export enum KmsKeyUsage {
|
||||
ENCRYPT_DECRYPT = "encrypt-decrypt",
|
||||
SIGN_VERIFY = "sign-verify"
|
||||
}
|
||||
@@ -31,8 +31,8 @@ export type TEncryptWithKmsDataKeyDTO =
|
||||
export type TGenerateKMSDTO = {
|
||||
orgId: string;
|
||||
projectId?: string;
|
||||
encryptionAlgorithm?: SymmetricKeyEncryptDecrypt | AsymmetricKeySignVerify;
|
||||
type?: KmsKeyIntent;
|
||||
encryptionAlgorithm?: SymmetricKeyAlgorithm | AsymmetricKeyAlgorithm;
|
||||
keyUsage?: KmsKeyUsage;
|
||||
isReserved?: boolean;
|
||||
name?: string;
|
||||
description?: string;
|
||||
@@ -91,10 +91,10 @@ export type TGetKeyMaterialDTO = {
|
||||
|
||||
export type TImportKeyMaterialDTO = {
|
||||
key: Buffer;
|
||||
algorithm: SymmetricKeyEncryptDecrypt;
|
||||
algorithm: SymmetricKeyAlgorithm;
|
||||
name?: string;
|
||||
isReserved: boolean;
|
||||
projectId: string;
|
||||
orgId: string;
|
||||
type: KmsKeyIntent;
|
||||
keyUsage: KmsKeyUsage;
|
||||
};
|
||||
|
||||
@@ -1,21 +1,17 @@
|
||||
import {
|
||||
AsymmetricKeySignVerify,
|
||||
KmsKeyIntent,
|
||||
SymmetricKeyEncryptDecrypt
|
||||
} from "@app/hooks/api/cmeks";
|
||||
import { AsymmetricKeyAlgorithm, KmsKeyUsage, SymmetricKeyAlgorithm } from "@app/hooks/api/cmeks";
|
||||
|
||||
export const kmsKeyUsageOptions: Record<
|
||||
KmsKeyIntent,
|
||||
KmsKeyUsage,
|
||||
{
|
||||
label: string;
|
||||
tooltip: string;
|
||||
}
|
||||
> = {
|
||||
[KmsKeyIntent.ENCRYPT_DECRYPT]: {
|
||||
[KmsKeyUsage.ENCRYPT_DECRYPT]: {
|
||||
label: "Encrypt/Decrypt",
|
||||
tooltip: "Use the key only to encrypt and decrypt data."
|
||||
},
|
||||
[KmsKeyIntent.SIGN_VERIFY]: {
|
||||
[KmsKeyUsage.SIGN_VERIFY]: {
|
||||
label: "Sign/Verify",
|
||||
tooltip:
|
||||
"Key pairs for digital signing. Uses the private key for signing and the public key for verification."
|
||||
@@ -23,9 +19,9 @@ export const kmsKeyUsageOptions: Record<
|
||||
};
|
||||
|
||||
export const keyUsageDefaultOption: Record<
|
||||
KmsKeyIntent,
|
||||
SymmetricKeyEncryptDecrypt | AsymmetricKeySignVerify
|
||||
KmsKeyUsage,
|
||||
SymmetricKeyAlgorithm | AsymmetricKeyAlgorithm
|
||||
> = {
|
||||
[KmsKeyIntent.ENCRYPT_DECRYPT]: SymmetricKeyEncryptDecrypt.AES_GCM_256,
|
||||
[KmsKeyIntent.SIGN_VERIFY]: AsymmetricKeySignVerify.RSA_4096
|
||||
[KmsKeyUsage.ENCRYPT_DECRYPT]: SymmetricKeyAlgorithm.AES_GCM_256,
|
||||
[KmsKeyUsage.SIGN_VERIFY]: AsymmetricKeyAlgorithm.RSA_4096
|
||||
};
|
||||
|
||||
@@ -2,17 +2,17 @@ import { z } from "zod";
|
||||
|
||||
import { OrderByDirection } from "@app/hooks/api/generic/types";
|
||||
|
||||
export enum KmsKeyIntent {
|
||||
export enum KmsKeyUsage {
|
||||
ENCRYPT_DECRYPT = "encrypt-decrypt",
|
||||
SIGN_VERIFY = "sign-verify"
|
||||
}
|
||||
|
||||
export type TCmek = {
|
||||
id: string;
|
||||
type: KmsKeyIntent;
|
||||
keyUsage: KmsKeyUsage;
|
||||
name: string;
|
||||
description?: string;
|
||||
encryptionAlgorithm: AsymmetricKeySignVerify | SymmetricKeyEncryptDecrypt;
|
||||
encryptionAlgorithm: AsymmetricKeyAlgorithm | SymmetricKeyAlgorithm;
|
||||
projectId: string;
|
||||
isDisabled: boolean;
|
||||
isReserved: boolean;
|
||||
@@ -25,7 +25,7 @@ export type TCmek = {
|
||||
type ProjectRef = { projectId: string };
|
||||
type KeyRef = { keyId: string };
|
||||
|
||||
export type TCreateCmek = Pick<TCmek, "name" | "description" | "encryptionAlgorithm" | "type"> &
|
||||
export type TCreateCmek = Pick<TCmek, "name" | "description" | "encryptionAlgorithm" | "keyUsage"> &
|
||||
ProjectRef;
|
||||
export type TUpdateCmek = KeyRef &
|
||||
Partial<Pick<TCmek, "name" | "description" | "isDisabled">> &
|
||||
@@ -80,20 +80,20 @@ export enum CmekOrderBy {
|
||||
Name = "name"
|
||||
}
|
||||
|
||||
export enum AsymmetricKeySignVerify {
|
||||
export enum AsymmetricKeyAlgorithm {
|
||||
RSA_4096 = "rsa-4096",
|
||||
ECC_NIST_P256 = "ecc-nist-p256"
|
||||
}
|
||||
|
||||
// Supported symmetric encrypt/decrypt algorithms
|
||||
export enum SymmetricKeyEncryptDecrypt {
|
||||
export enum SymmetricKeyAlgorithm {
|
||||
AES_GCM_256 = "aes-256-gcm",
|
||||
AES_GCM_128 = "aes-128-gcm"
|
||||
}
|
||||
|
||||
export const AllowedEncryptionKeyAlgorithms = z.enum([
|
||||
...Object.values(SymmetricKeyEncryptDecrypt),
|
||||
...Object.values(AsymmetricKeySignVerify)
|
||||
...Object.values(SymmetricKeyAlgorithm),
|
||||
...Object.values(AsymmetricKeyAlgorithm)
|
||||
] as [string, ...string[]]).options;
|
||||
|
||||
export enum SigningAlgorithm {
|
||||
|
||||
@@ -18,9 +18,9 @@ import { useWorkspace } from "@app/context";
|
||||
import { keyUsageDefaultOption, kmsKeyUsageOptions } from "@app/helpers/kms";
|
||||
import {
|
||||
AllowedEncryptionKeyAlgorithms,
|
||||
AsymmetricKeySignVerify,
|
||||
KmsKeyIntent,
|
||||
SymmetricKeyEncryptDecrypt,
|
||||
AsymmetricKeyAlgorithm,
|
||||
KmsKeyUsage,
|
||||
SymmetricKeyAlgorithm,
|
||||
TCmek,
|
||||
useCreateCmek,
|
||||
useUpdateCmek
|
||||
@@ -31,7 +31,7 @@ const formSchema = z.object({
|
||||
name: slugSchema({ min: 1, max: 32, field: "Name" }),
|
||||
description: z.string().max(500).optional(),
|
||||
encryptionAlgorithm: z.enum(AllowedEncryptionKeyAlgorithms),
|
||||
type: z.nativeEnum(KmsKeyIntent)
|
||||
keyUsage: z.nativeEnum(KmsKeyUsage)
|
||||
});
|
||||
|
||||
export type FormData = z.infer<typeof formSchema>;
|
||||
@@ -65,22 +65,25 @@ const CmekForm = ({ onComplete, cmek }: FormProps) => {
|
||||
defaultValues: {
|
||||
name: cmek?.name,
|
||||
description: cmek?.description,
|
||||
encryptionAlgorithm: SymmetricKeyEncryptDecrypt.AES_GCM_256,
|
||||
type: KmsKeyIntent.ENCRYPT_DECRYPT
|
||||
encryptionAlgorithm: SymmetricKeyAlgorithm.AES_GCM_256,
|
||||
keyUsage: KmsKeyUsage.ENCRYPT_DECRYPT
|
||||
}
|
||||
});
|
||||
|
||||
const handleCreateCmek = async ({ encryptionAlgorithm, name, description, type }: FormData) => {
|
||||
const handleCreateCmek = async ({
|
||||
encryptionAlgorithm,
|
||||
name,
|
||||
description,
|
||||
keyUsage
|
||||
}: FormData) => {
|
||||
const mutation = isUpdate
|
||||
? updateCmek.mutateAsync({ keyId: cmek.id, projectId, name, description })
|
||||
: createCmek.mutateAsync({
|
||||
projectId,
|
||||
name,
|
||||
description,
|
||||
type,
|
||||
encryptionAlgorithm: encryptionAlgorithm as
|
||||
| AsymmetricKeySignVerify
|
||||
| SymmetricKeyEncryptDecrypt
|
||||
keyUsage,
|
||||
encryptionAlgorithm: encryptionAlgorithm as AsymmetricKeyAlgorithm | SymmetricKeyAlgorithm
|
||||
});
|
||||
|
||||
try {
|
||||
@@ -99,7 +102,7 @@ const CmekForm = ({ onComplete, cmek }: FormProps) => {
|
||||
}
|
||||
};
|
||||
|
||||
const selectedType = watch("type");
|
||||
const selectedKeyUsage = watch("keyUsage");
|
||||
|
||||
return (
|
||||
<form onSubmit={handleSubmit(handleCreateCmek)}>
|
||||
@@ -116,13 +119,13 @@ const CmekForm = ({ onComplete, cmek }: FormProps) => {
|
||||
<>
|
||||
<Controller
|
||||
control={control}
|
||||
name="type"
|
||||
name="keyUsage"
|
||||
render={({ field: { onChange, ...field }, fieldState: { error } }) => (
|
||||
<FormControl
|
||||
className="w-full"
|
||||
tooltipText={
|
||||
<div className="space-y-4">
|
||||
{Object.entries(KmsKeyIntent).map(([key, value]) => (
|
||||
{Object.entries(KmsKeyUsage).map(([key, value]) => (
|
||||
<div key={`key-usage-${key}`}>
|
||||
<p className="font-bold">{kmsKeyUsageOptions[value].label}</p>
|
||||
<p>{kmsKeyUsageOptions[value].tooltip}</p>
|
||||
@@ -137,8 +140,8 @@ const CmekForm = ({ onComplete, cmek }: FormProps) => {
|
||||
<Select
|
||||
defaultValue={field.value}
|
||||
onValueChange={(e) => {
|
||||
if (keyUsageDefaultOption[e as KmsKeyIntent]) {
|
||||
setValue("encryptionAlgorithm", keyUsageDefaultOption[e as KmsKeyIntent], {
|
||||
if (keyUsageDefaultOption[e as KmsKeyUsage]) {
|
||||
setValue("encryptionAlgorithm", keyUsageDefaultOption[e as KmsKeyUsage], {
|
||||
shouldDirty: true,
|
||||
shouldValidate: true
|
||||
});
|
||||
@@ -148,7 +151,7 @@ const CmekForm = ({ onComplete, cmek }: FormProps) => {
|
||||
}}
|
||||
className="w-full"
|
||||
>
|
||||
{Object.entries(KmsKeyIntent)?.map(([key, value]) => (
|
||||
{Object.entries(KmsKeyUsage)?.map(([key, value]) => (
|
||||
<SelectItem value={value} key={`key-usage-${key}`}>
|
||||
{kmsKeyUsageOptions[value].label}
|
||||
</SelectItem>
|
||||
@@ -176,14 +179,14 @@ const CmekForm = ({ onComplete, cmek }: FormProps) => {
|
||||
{Object.entries(AllowedEncryptionKeyAlgorithms)
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
?.filter(([_, value]) => {
|
||||
if (selectedType === KmsKeyIntent.ENCRYPT_DECRYPT) {
|
||||
return Object.values(SymmetricKeyEncryptDecrypt).includes(
|
||||
value as unknown as SymmetricKeyEncryptDecrypt
|
||||
if (selectedKeyUsage === KmsKeyUsage.ENCRYPT_DECRYPT) {
|
||||
return Object.values(SymmetricKeyAlgorithm).includes(
|
||||
value as unknown as SymmetricKeyAlgorithm
|
||||
);
|
||||
}
|
||||
if (selectedType === KmsKeyIntent.SIGN_VERIFY) {
|
||||
return Object.values(AsymmetricKeySignVerify).includes(
|
||||
value as unknown as AsymmetricKeySignVerify
|
||||
if (selectedKeyUsage === KmsKeyUsage.SIGN_VERIFY) {
|
||||
return Object.values(AsymmetricKeyAlgorithm).includes(
|
||||
value as unknown as AsymmetricKeyAlgorithm
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -55,7 +55,7 @@ import {
|
||||
import { kmsKeyUsageOptions } from "@app/helpers/kms";
|
||||
import { usePagination, usePopUp, useResetPageHelper, useTimedReset } from "@app/hooks";
|
||||
import { useGetCmeksByProjectId, useUpdateCmek } from "@app/hooks/api/cmeks";
|
||||
import { CmekOrderBy, KmsKeyIntent, TCmek } from "@app/hooks/api/cmeks/types";
|
||||
import { CmekOrderBy, KmsKeyUsage, TCmek } from "@app/hooks/api/cmeks/types";
|
||||
import { OrderByDirection } from "@app/hooks/api/generic/types";
|
||||
|
||||
import { CmekDecryptModal } from "./CmekDecryptModal";
|
||||
@@ -273,8 +273,15 @@ export const CmekTable = () => {
|
||||
{!isPending &&
|
||||
keys.length > 0 &&
|
||||
keys.map((cmek) => {
|
||||
const { name, id, version, description, encryptionAlgorithm, isDisabled, type } =
|
||||
cmek;
|
||||
const {
|
||||
name,
|
||||
id,
|
||||
version,
|
||||
description,
|
||||
encryptionAlgorithm,
|
||||
isDisabled,
|
||||
keyUsage
|
||||
} = cmek;
|
||||
const { variant, label } = getStatusBadgeProps(isDisabled);
|
||||
|
||||
return (
|
||||
@@ -314,8 +321,8 @@ export const CmekTable = () => {
|
||||
</Td>
|
||||
<Td>
|
||||
<div className="flex items-center gap-2">
|
||||
{kmsKeyUsageOptions[type].label}
|
||||
<Tooltip content={kmsKeyUsageOptions[type].tooltip}>
|
||||
{kmsKeyUsageOptions[keyUsage].label}
|
||||
<Tooltip content={kmsKeyUsageOptions[keyUsage].tooltip}>
|
||||
<FontAwesomeIcon icon={faInfoCircle} className="text-mineshaft-400" />
|
||||
</Tooltip>
|
||||
</div>
|
||||
@@ -339,7 +346,7 @@ export const CmekTable = () => {
|
||||
</IconButton>
|
||||
</DropdownMenuTrigger>
|
||||
<DropdownMenuContent className="min-w-[160px]">
|
||||
{type === KmsKeyIntent.ENCRYPT_DECRYPT && (
|
||||
{keyUsage === KmsKeyUsage.ENCRYPT_DECRYPT && (
|
||||
<>
|
||||
<Tooltip
|
||||
content={
|
||||
@@ -388,7 +395,7 @@ export const CmekTable = () => {
|
||||
</>
|
||||
)}
|
||||
|
||||
{type === KmsKeyIntent.SIGN_VERIFY && (
|
||||
{keyUsage === KmsKeyUsage.SIGN_VERIFY && (
|
||||
<>
|
||||
<Tooltip
|
||||
content={
|
||||
|
||||
Reference in New Issue
Block a user