From 0187d3012b203400297fb452e8e9d6a923a4a703 Mon Sep 17 00:00:00 2001 From: Tuan Dang Date: Fri, 9 Jun 2023 21:20:12 +0100 Subject: [PATCH] Add non-e2ee option for getSecret, getSecrets, start createSecret --- .../src/controllers/v3/secretsController.ts | 14 +- backend/src/helpers/bot.ts | 20 ++- backend/src/helpers/secrets.ts | 152 +++++++++++++++++- .../interfaces/services/BotService/index.ts | 0 .../services/SecretService/index.ts | 14 +- backend/src/routes/v3/secrets.ts | 14 +- backend/src/services/BotService.ts | 20 ++- backend/src/services/SecretService.ts | 3 - 8 files changed, 202 insertions(+), 35 deletions(-) create mode 100644 backend/src/interfaces/services/BotService/index.ts diff --git a/backend/src/controllers/v3/secretsController.ts b/backend/src/controllers/v3/secretsController.ts index dac41b53d..321ab41d2 100644 --- a/backend/src/controllers/v3/secretsController.ts +++ b/backend/src/controllers/v3/secretsController.ts @@ -6,8 +6,6 @@ import { EventService } from '../../services'; import { eventPushSecrets } from '../../events'; -import { getAuthDataPayloadIdObj } from '../../utils/auth'; -import { BadRequestError } from '../../utils/errors'; /** * Get secrets for workspace with id [workspaceId] and environment @@ -68,9 +66,11 @@ export const createSecret = async (req: Request, res: Response) => { secretKeyCiphertext, secretKeyIV, secretKeyTag, + secretValue, secretValueCiphertext, secretValueIV, secretValueTag, + secretComment, secretCommentCiphertext, secretCommentIV, secretCommentTag @@ -85,14 +85,14 @@ export const createSecret = async (req: Request, res: Response) => { secretKeyCiphertext, secretKeyIV, secretKeyTag, + secretValue, secretValueCiphertext, secretValueIV, secretValueTag, - ...((secretCommentCiphertext && secretCommentIV && secretCommentTag) ? { - secretCommentCiphertext, - secretCommentIV, - secretCommentTag - } : {}) + secretComment, + secretCommentCiphertext, + secretCommentIV, + secretCommentTag }); await EventService.handleEvent({ diff --git a/backend/src/helpers/bot.ts b/backend/src/helpers/bot.ts index e93ab6a8b..439f7c531 100644 --- a/backend/src/helpers/bot.ts +++ b/backend/src/helpers/bot.ts @@ -86,6 +86,18 @@ export const createBot = async ({ }); }; +/** + * Return whether or not workspace with id [workspaceId] is end-to-end encrypted + * @param {Types.ObjectId} workspaceId - id of workspace to check + */ +export const getIsWorkspaceE2EEHelper = async (workspaceId: Types.ObjectId) => { + const botKey = await BotKey.exists({ + workspace: workspaceId + }); + + return botKey ? false : true; +} + /** * Return decrypted secrets for workspace with id [workspaceId] * and [environment] using bot @@ -101,7 +113,7 @@ export const getSecretsBotHelper = async ({ environment: string; }) => { const content = {} as any; - const key = await getKey({ workspaceId: workspaceId.toString() }); + const key = await getKey({ workspaceId: workspaceId }); const secrets = await Secret.find({ workspace: workspaceId, environment, @@ -136,7 +148,7 @@ export const getSecretsBotHelper = async ({ * @param {String} obj.workspaceId - id of workspace * @returns {String} key - decrypted workspace key */ -export const getKey = async ({ workspaceId }: { workspaceId: string }) => { +export const getKey = async ({ workspaceId }: { workspaceId: Types.ObjectId }) => { const encryptionKey = await getEncryptionKey(); const rootEncryptionKey = await getRootEncryptionKey(); @@ -201,7 +213,7 @@ export const encryptSymmetricHelper = async ({ workspaceId: Types.ObjectId; plaintext: string; }) => { - const key = await getKey({ workspaceId: workspaceId.toString() }); + const key = await getKey({ workspaceId: workspaceId }); const { ciphertext, iv, tag } = encryptSymmetric128BitHexKeyUTF8({ plaintext, key, @@ -233,7 +245,7 @@ export const decryptSymmetricHelper = async ({ iv: string; tag: string; }) => { - const key = await getKey({ workspaceId: workspaceId.toString() }); + const key = await getKey({ workspaceId: workspaceId }); const plaintext = decryptSymmetric128BitHexKeyUTF8({ ciphertext, iv, diff --git a/backend/src/helpers/secrets.ts b/backend/src/helpers/secrets.ts index 1c58119b1..baa8d292e 100644 --- a/backend/src/helpers/secrets.ts +++ b/backend/src/helpers/secrets.ts @@ -6,7 +6,12 @@ import { UpdateSecretParams, DeleteSecretParams, } from '../interfaces/services/SecretService'; -import { Secret, ISecret, SecretBlindIndexData } from '../models'; +import { + ISecret, + Secret, + SecretBlindIndexData, + BotKey +} from '../models'; import { SecretVersion } from '../ee/models'; import { BadRequestError, @@ -32,7 +37,7 @@ import { decryptSymmetric128BitHexKeyUTF8, } from '../utils/crypto'; import { getEncryptionKey, client, getRootEncryptionKey } from '../config'; -import { TelemetryService } from '../services'; +import { BotService, TelemetryService } from '../services'; import { EESecretService, EELogService } from '../ee/services'; import { getAuthDataPayloadIdObj, @@ -237,6 +242,23 @@ export const generateSecretBlindIndexHelper = async ({ }); }; +// secretName, +// workspaceId, +// environment, +// type, +// authData, +// secretKeyCiphertext, +// secretKeyIV, +// secretKeyTag, +// secretValue, +// secretValueCiphertext, +// secretValueIV, +// secretValueTag, +// secretCommentCiphertext, +// secretCommentIV, +// secretCommentTag, +// folderId, + /** * Create secret with name [secretName] * @param {Object} obj @@ -256,14 +278,17 @@ export const createSecretHelper = async ({ secretKeyCiphertext, secretKeyIV, secretKeyTag, + secretValue, secretValueCiphertext, secretValueIV, secretValueTag, + secretComment, secretCommentCiphertext, secretCommentIV, secretCommentTag, folderId, }: CreateSecretParams) => { + const secretBlindIndex = await generateSecretBlindIndexHelper({ secretName, workspaceId: new Types.ObjectId(workspaceId), @@ -298,6 +323,52 @@ export const createSecretHelper = async ({ }); } + // can generate secretKeyCiphertext etc. if not E2EE. + const isWorkspaceE2EE = await BotService.getIsWorkspaceE2EE(workspaceId); + + if (!isWorkspaceE2EE) { + // if workspace is not end-to-end encrypted, then decrypt + // secret and return it in plaintext + + const key = await BotService.getWorkspaceKeyWithBot({ + workspaceId + }); + + if (secretName) { + const encryptedSecretKey = encryptSymmetric128BitHexKeyUTF8({ + plaintext: secretName, + key + }); + + secretKeyCiphertext = encryptedSecretKey.ciphertext; + secretKeyIV = encryptedSecretKey.iv; + secretKeyTag = encryptedSecretKey.tag; + } + + if (secretValue) { + const encryptedSecretValue = encryptSymmetric128BitHexKeyUTF8({ + plaintext: secretValue, + key + }); + + secretValueCiphertext = encryptedSecretValue.ciphertext; + secretValueIV = encryptedSecretValue.iv; + secretValueTag = encryptedSecretValue.tag; + } + + if (secretComment) { + const encryptedSecretComment = encryptSymmetric128BitHexKeyUTF8({ + plaintext: secretComment, + key + }); + + secretCommentCiphertext = encryptedSecretComment.ciphertext; + secretCommentIV = encryptedSecretComment.iv; + secretCommentTag = encryptedSecretComment.tag; + } + } + + // create secret const secret = await new Secret({ version: 1, @@ -410,7 +481,7 @@ export const getSecretsHelper = async ({ environment, type: SECRET_PERSONAL, ...getAuthDataPayloadUserObj(authData), - }); + }).lean(); // concat with shared secrets secrets = secrets.concat( @@ -421,7 +492,7 @@ export const getSecretsHelper = async ({ secretBlindIndex: { $nin: secrets.map((secret) => secret.secretBlindIndex), }, - }) + }).lean() ); // (EE) create (audit) log @@ -458,8 +529,45 @@ export const getSecretsHelper = async ({ }, }); } + + const isWorkspaceE2EE = await BotService.getIsWorkspaceE2EE(workspaceId); - return secrets; + if (!isWorkspaceE2EE) { + // if workspace is not end-to-end encrypted, then decrypt + // secret and return it in plaintext + + const key = await BotService.getWorkspaceKeyWithBot({ + workspaceId + }); + + return secrets.map((secret) => { + const secretName = decryptSymmetric128BitHexKeyUTF8({ + ciphertext: secret.secretKeyCiphertext, + iv: secret.secretKeyIV, + tag: secret.secretKeyTag, + key + }); + + const secretValue = decryptSymmetric128BitHexKeyUTF8({ + ciphertext: secret.secretValueCiphertext, + iv: secret.secretValueIV, + tag: secret.secretValueTag, + key + }); + + return ({ + ...secret, + secretName, + secretValue + }); + }); + } + + return secrets.map((secret) => ({ + ...secret, + secretName: null, + secretValue: null + })); }; /** @@ -492,7 +600,7 @@ export const getSecretHelper = async ({ environment, type: type ?? SECRET_PERSONAL, ...(type === SECRET_PERSONAL ? getAuthDataPayloadUserObj(authData) : {}), - }); + }).lean(); if (!secret) { // case: failed to find personal secret matching criteria @@ -502,7 +610,7 @@ export const getSecretHelper = async ({ workspace: new Types.ObjectId(workspaceId), environment, type: SECRET_SHARED, - }); + }).lean(); } if (!secret) throw SecretNotFoundError(); @@ -541,8 +649,36 @@ export const getSecretHelper = async ({ }, }); } + + const isWorkspaceE2EE = await BotService.getIsWorkspaceE2EE(workspaceId); + + if (!isWorkspaceE2EE) { + // if workspace is not end-to-end encrypted, then decrypt + // secret and return it in plaintext - return secret; + const key = await BotService.getWorkspaceKeyWithBot({ + workspaceId + }); + + const secretValue = decryptSymmetric128BitHexKeyUTF8({ + ciphertext: secret.secretValueCiphertext, + iv: secret.secretValueIV, + tag: secret.secretValueTag, + key + }); + + return ({ + ...secret, + secretName, + secretValue + }); + } + + return ({ + ...secret, + secretName: null, + secretValue: null + }); }; /** diff --git a/backend/src/interfaces/services/BotService/index.ts b/backend/src/interfaces/services/BotService/index.ts new file mode 100644 index 000000000..e69de29bb diff --git a/backend/src/interfaces/services/SecretService/index.ts b/backend/src/interfaces/services/SecretService/index.ts index 2efd53f4e..920db69cd 100644 --- a/backend/src/interfaces/services/SecretService/index.ts +++ b/backend/src/interfaces/services/SecretService/index.ts @@ -8,12 +8,14 @@ export interface CreateSecretParams { folderId?: string; type: "shared" | "personal"; authData: AuthData; - secretKeyCiphertext: string; - secretKeyIV: string; - secretKeyTag: string; - secretValueCiphertext: string; - secretValueIV: string; - secretValueTag: string; + secretKeyCiphertext?: string; + secretKeyIV?: string; + secretKeyTag?: string; + secretValue?: string; + secretValueCiphertext?: string; + secretValueIV?: string; + secretValueTag?: string; + secretComment?: string; secretCommentCiphertext?: string; secretCommentIV?: string; secretCommentTag?: string; diff --git a/backend/src/routes/v3/secrets.ts b/backend/src/routes/v3/secrets.ts index 6a18fdf73..6d8b11c29 100644 --- a/backend/src/routes/v3/secrets.ts +++ b/backend/src/routes/v3/secrets.ts @@ -48,12 +48,14 @@ router.post( body('workspaceId').exists().isString().trim(), body('environment').exists().isString().trim(), body('type').exists().isIn([SECRET_SHARED, SECRET_PERSONAL]), - body('secretKeyCiphertext').exists().isString().trim(), - body('secretKeyIV').exists().isString().trim(), - body('secretKeyTag').exists().isString().trim(), - body('secretValueCiphertext').exists().isString().trim(), - body('secretValueIV').exists().isString().trim(), - body('secretValueTag').exists().isString().trim(), + body('secretKeyCiphertext').optional().isString().trim(), + body('secretKeyIV').optional().isString().trim(), + body('secretKeyTag').optional().isString().trim(), + body('secretValue').optional().isString().trim(), + body('secretValueCiphertext').optional().isString().trim(), + body('secretValueIV').optional().isString().trim(), + body('secretValueTag').optional().isString().trim(), + body('secretComment').optional().isString().trim(), body('secretCommentCiphertext').optional().isString().trim(), body('secretCommentIV').optional().isString().trim(), body('secretCommentTag').optional().isString().trim(), diff --git a/backend/src/services/BotService.ts b/backend/src/services/BotService.ts index 2c0db0355..ab6d8fbd4 100644 --- a/backend/src/services/BotService.ts +++ b/backend/src/services/BotService.ts @@ -2,13 +2,31 @@ import { Types } from 'mongoose'; import { getSecretsBotHelper, encryptSymmetricHelper, - decryptSymmetricHelper + decryptSymmetricHelper, + getKey, + getIsWorkspaceE2EEHelper } from '../helpers/bot'; +// rename the functions here +// refactor the interface situation here + /** * Class to handle bot actions */ class BotService { + static async getIsWorkspaceE2EE(workspaceId: Types.ObjectId) { + return await getIsWorkspaceE2EEHelper(workspaceId); + } + + static async getWorkspaceKeyWithBot({ + workspaceId + }: { + workspaceId: Types.ObjectId; + }) { + return await getKey({ + workspaceId + }); + } /** * Return decrypted secrets for workspace with id [workspaceId] and diff --git a/backend/src/services/SecretService.ts b/backend/src/services/SecretService.ts index 5b5b73e5f..9e430ab3d 100644 --- a/backend/src/services/SecretService.ts +++ b/backend/src/services/SecretService.ts @@ -1,7 +1,4 @@ import { Types } from 'mongoose'; -import { - ISecret -} from '../models'; import { CreateSecretParams, GetSecretsParams,