Add non-e2ee option for getSecret, getSecrets, start createSecret

This commit is contained in:
Tuan Dang
2023-06-09 21:20:12 +01:00
parent 00fae0023a
commit 0187d3012b
8 changed files with 202 additions and 35 deletions
@@ -6,8 +6,6 @@ import {
EventService EventService
} from '../../services'; } from '../../services';
import { eventPushSecrets } from '../../events'; import { eventPushSecrets } from '../../events';
import { getAuthDataPayloadIdObj } from '../../utils/auth';
import { BadRequestError } from '../../utils/errors';
/** /**
* Get secrets for workspace with id [workspaceId] and environment * Get secrets for workspace with id [workspaceId] and environment
@@ -68,9 +66,11 @@ export const createSecret = async (req: Request, res: Response) => {
secretKeyCiphertext, secretKeyCiphertext,
secretKeyIV, secretKeyIV,
secretKeyTag, secretKeyTag,
secretValue,
secretValueCiphertext, secretValueCiphertext,
secretValueIV, secretValueIV,
secretValueTag, secretValueTag,
secretComment,
secretCommentCiphertext, secretCommentCiphertext,
secretCommentIV, secretCommentIV,
secretCommentTag secretCommentTag
@@ -85,14 +85,14 @@ export const createSecret = async (req: Request, res: Response) => {
secretKeyCiphertext, secretKeyCiphertext,
secretKeyIV, secretKeyIV,
secretKeyTag, secretKeyTag,
secretValue,
secretValueCiphertext, secretValueCiphertext,
secretValueIV, secretValueIV,
secretValueTag, secretValueTag,
...((secretCommentCiphertext && secretCommentIV && secretCommentTag) ? { secretComment,
secretCommentCiphertext, secretCommentCiphertext,
secretCommentIV, secretCommentIV,
secretCommentTag secretCommentTag
} : {})
}); });
await EventService.handleEvent({ await EventService.handleEvent({
+16 -4
View File
@@ -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] * Return decrypted secrets for workspace with id [workspaceId]
* and [environment] using bot * and [environment] using bot
@@ -101,7 +113,7 @@ export const getSecretsBotHelper = async ({
environment: string; environment: string;
}) => { }) => {
const content = {} as any; const content = {} as any;
const key = await getKey({ workspaceId: workspaceId.toString() }); const key = await getKey({ workspaceId: workspaceId });
const secrets = await Secret.find({ const secrets = await Secret.find({
workspace: workspaceId, workspace: workspaceId,
environment, environment,
@@ -136,7 +148,7 @@ export const getSecretsBotHelper = async ({
* @param {String} obj.workspaceId - id of workspace * @param {String} obj.workspaceId - id of workspace
* @returns {String} key - decrypted workspace key * @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 encryptionKey = await getEncryptionKey();
const rootEncryptionKey = await getRootEncryptionKey(); const rootEncryptionKey = await getRootEncryptionKey();
@@ -201,7 +213,7 @@ export const encryptSymmetricHelper = async ({
workspaceId: Types.ObjectId; workspaceId: Types.ObjectId;
plaintext: string; plaintext: string;
}) => { }) => {
const key = await getKey({ workspaceId: workspaceId.toString() }); const key = await getKey({ workspaceId: workspaceId });
const { ciphertext, iv, tag } = encryptSymmetric128BitHexKeyUTF8({ const { ciphertext, iv, tag } = encryptSymmetric128BitHexKeyUTF8({
plaintext, plaintext,
key, key,
@@ -233,7 +245,7 @@ export const decryptSymmetricHelper = async ({
iv: string; iv: string;
tag: string; tag: string;
}) => { }) => {
const key = await getKey({ workspaceId: workspaceId.toString() }); const key = await getKey({ workspaceId: workspaceId });
const plaintext = decryptSymmetric128BitHexKeyUTF8({ const plaintext = decryptSymmetric128BitHexKeyUTF8({
ciphertext, ciphertext,
iv, iv,
+144 -8
View File
@@ -6,7 +6,12 @@ import {
UpdateSecretParams, UpdateSecretParams,
DeleteSecretParams, DeleteSecretParams,
} from '../interfaces/services/SecretService'; } from '../interfaces/services/SecretService';
import { Secret, ISecret, SecretBlindIndexData } from '../models'; import {
ISecret,
Secret,
SecretBlindIndexData,
BotKey
} from '../models';
import { SecretVersion } from '../ee/models'; import { SecretVersion } from '../ee/models';
import { import {
BadRequestError, BadRequestError,
@@ -32,7 +37,7 @@ import {
decryptSymmetric128BitHexKeyUTF8, decryptSymmetric128BitHexKeyUTF8,
} from '../utils/crypto'; } from '../utils/crypto';
import { getEncryptionKey, client, getRootEncryptionKey } from '../config'; import { getEncryptionKey, client, getRootEncryptionKey } from '../config';
import { TelemetryService } from '../services'; import { BotService, TelemetryService } from '../services';
import { EESecretService, EELogService } from '../ee/services'; import { EESecretService, EELogService } from '../ee/services';
import { import {
getAuthDataPayloadIdObj, 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] * Create secret with name [secretName]
* @param {Object} obj * @param {Object} obj
@@ -256,14 +278,17 @@ export const createSecretHelper = async ({
secretKeyCiphertext, secretKeyCiphertext,
secretKeyIV, secretKeyIV,
secretKeyTag, secretKeyTag,
secretValue,
secretValueCiphertext, secretValueCiphertext,
secretValueIV, secretValueIV,
secretValueTag, secretValueTag,
secretComment,
secretCommentCiphertext, secretCommentCiphertext,
secretCommentIV, secretCommentIV,
secretCommentTag, secretCommentTag,
folderId, folderId,
}: CreateSecretParams) => { }: CreateSecretParams) => {
const secretBlindIndex = await generateSecretBlindIndexHelper({ const secretBlindIndex = await generateSecretBlindIndexHelper({
secretName, secretName,
workspaceId: new Types.ObjectId(workspaceId), 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 // create secret
const secret = await new Secret({ const secret = await new Secret({
version: 1, version: 1,
@@ -410,7 +481,7 @@ export const getSecretsHelper = async ({
environment, environment,
type: SECRET_PERSONAL, type: SECRET_PERSONAL,
...getAuthDataPayloadUserObj(authData), ...getAuthDataPayloadUserObj(authData),
}); }).lean();
// concat with shared secrets // concat with shared secrets
secrets = secrets.concat( secrets = secrets.concat(
@@ -421,7 +492,7 @@ export const getSecretsHelper = async ({
secretBlindIndex: { secretBlindIndex: {
$nin: secrets.map((secret) => secret.secretBlindIndex), $nin: secrets.map((secret) => secret.secretBlindIndex),
}, },
}) }).lean()
); );
// (EE) create (audit) log // (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, environment,
type: type ?? SECRET_PERSONAL, type: type ?? SECRET_PERSONAL,
...(type === SECRET_PERSONAL ? getAuthDataPayloadUserObj(authData) : {}), ...(type === SECRET_PERSONAL ? getAuthDataPayloadUserObj(authData) : {}),
}); }).lean();
if (!secret) { if (!secret) {
// case: failed to find personal secret matching criteria // case: failed to find personal secret matching criteria
@@ -502,7 +610,7 @@ export const getSecretHelper = async ({
workspace: new Types.ObjectId(workspaceId), workspace: new Types.ObjectId(workspaceId),
environment, environment,
type: SECRET_SHARED, type: SECRET_SHARED,
}); }).lean();
} }
if (!secret) throw SecretNotFoundError(); 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
});
}; };
/** /**
@@ -8,12 +8,14 @@ export interface CreateSecretParams {
folderId?: string; folderId?: string;
type: "shared" | "personal"; type: "shared" | "personal";
authData: AuthData; authData: AuthData;
secretKeyCiphertext: string; secretKeyCiphertext?: string;
secretKeyIV: string; secretKeyIV?: string;
secretKeyTag: string; secretKeyTag?: string;
secretValueCiphertext: string; secretValue?: string;
secretValueIV: string; secretValueCiphertext?: string;
secretValueTag: string; secretValueIV?: string;
secretValueTag?: string;
secretComment?: string;
secretCommentCiphertext?: string; secretCommentCiphertext?: string;
secretCommentIV?: string; secretCommentIV?: string;
secretCommentTag?: string; secretCommentTag?: string;
+8 -6
View File
@@ -48,12 +48,14 @@ router.post(
body('workspaceId').exists().isString().trim(), body('workspaceId').exists().isString().trim(),
body('environment').exists().isString().trim(), body('environment').exists().isString().trim(),
body('type').exists().isIn([SECRET_SHARED, SECRET_PERSONAL]), body('type').exists().isIn([SECRET_SHARED, SECRET_PERSONAL]),
body('secretKeyCiphertext').exists().isString().trim(), body('secretKeyCiphertext').optional().isString().trim(),
body('secretKeyIV').exists().isString().trim(), body('secretKeyIV').optional().isString().trim(),
body('secretKeyTag').exists().isString().trim(), body('secretKeyTag').optional().isString().trim(),
body('secretValueCiphertext').exists().isString().trim(), body('secretValue').optional().isString().trim(),
body('secretValueIV').exists().isString().trim(), body('secretValueCiphertext').optional().isString().trim(),
body('secretValueTag').exists().isString().trim(), body('secretValueIV').optional().isString().trim(),
body('secretValueTag').optional().isString().trim(),
body('secretComment').optional().isString().trim(),
body('secretCommentCiphertext').optional().isString().trim(), body('secretCommentCiphertext').optional().isString().trim(),
body('secretCommentIV').optional().isString().trim(), body('secretCommentIV').optional().isString().trim(),
body('secretCommentTag').optional().isString().trim(), body('secretCommentTag').optional().isString().trim(),
+19 -1
View File
@@ -2,13 +2,31 @@ import { Types } from 'mongoose';
import { import {
getSecretsBotHelper, getSecretsBotHelper,
encryptSymmetricHelper, encryptSymmetricHelper,
decryptSymmetricHelper decryptSymmetricHelper,
getKey,
getIsWorkspaceE2EEHelper
} from '../helpers/bot'; } from '../helpers/bot';
// rename the functions here
// refactor the interface situation here
/** /**
* Class to handle bot actions * Class to handle bot actions
*/ */
class BotService { 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 * Return decrypted secrets for workspace with id [workspaceId] and
-3
View File
@@ -1,7 +1,4 @@
import { Types } from 'mongoose'; import { Types } from 'mongoose';
import {
ISecret
} from '../models';
import { import {
CreateSecretParams, CreateSecretParams,
GetSecretsParams, GetSecretsParams,