From d9afe9088573b0c22af66c5dd2149380da3cc1b1 Mon Sep 17 00:00:00 2001 From: Tuan Dang Date: Sat, 15 Apr 2023 17:39:30 +0300 Subject: [PATCH] Begin frontend for blinded indices --- .../src/controllers/v3/secretsController.ts | 51 +++++++++---- backend/src/helpers/secrets.ts | 75 ++++++++++++++++++- backend/src/models/workspace.ts | 15 ++++ backend/src/services/SecretService.ts | 34 +++++++-- backend/src/services/index.ts | 6 +- backend/src/variables/index.ts | 10 ++- backend/src/variables/workspace.ts | 9 +++ .../ProjectSettingsPage.tsx | 5 +- .../ProjectEncryptionModeSection.tsx | 8 ++ .../ProjectEncryptionModeSection/index.tsx | 1 + .../ProjectSettingsPage/components/index.tsx | 1 + 11 files changed, 186 insertions(+), 29 deletions(-) create mode 100644 backend/src/variables/workspace.ts create mode 100644 frontend/src/views/Settings/ProjectSettingsPage/components/ProjectEncryptionModeSection/ProjectEncryptionModeSection.tsx create mode 100644 frontend/src/views/Settings/ProjectSettingsPage/components/ProjectEncryptionModeSection/index.tsx diff --git a/backend/src/controllers/v3/secretsController.ts b/backend/src/controllers/v3/secretsController.ts index 7c390fab3..b10e98cbc 100644 --- a/backend/src/controllers/v3/secretsController.ts +++ b/backend/src/controllers/v3/secretsController.ts @@ -4,6 +4,8 @@ import { Secret } from '../../models'; import crypto from 'crypto'; +import { SecretService } from '../../services'; + // TODO: modularize argon2id import * as argon2 from 'argon2'; @@ -50,30 +52,47 @@ export const createSecret = async (req: Request, res: Response) => { workspaceId, environment, value, - type + type, + secretKeyCiphertext, + secretKeyIV, + secretKeyTag, + secretValueCiphertext, + secretValueIV, + secretValueTag } = req.body; - // use workspace salt - const randomBytes = crypto.randomBytes(16); + const secretBlindIndex = await SecretService.createSecretBlindIndex({ + secretName, + workspaceId: new Types.ObjectId(workspaceId) + }); - // generate blind index - // TODO 1: abstract away into create blind index function - // TODO 2: create a get blind index function - const secretBlindIndex = (await argon2.hash(secretName, { - type: argon2.argon2id, - salt: randomBytes, - saltLength: 16, // default 16 bytes - memoryCost: 65536, // default pool of 64 MiB per thread. - hashLength: 32, - parallelism: 1, - raw: true - })).toString('base64'); + // // use workspace salt + // const randomBytes = crypto.randomBytes(16); + + // // generate blind index + // // TODO 1: abstract away into create blind index function + // // TODO 2: create a get blind index function + // const secretBlindIndex = (await argon2.hash(secretName, { + // type: argon2.argon2id, + // salt: randomBytes, + // saltLength: 16, // default 16 bytes + // memoryCost: 65536, // default pool of 64 MiB per thread. + // hashLength: 32, + // parallelism: 1, + // raw: true + // })).toString('base64'); // const secret = await new Secret({ // workspace: new Types.ObjectId(workspaceId), // environment, // type, - // secretBlindIndex + // secretBlindIndex, + // secretKeyCiphertext, + // secretKeyIV, + // secretKeyTag, + // secretValueCiphertext, + // secretValueIV, + // secretValueTag // }).save(); return res.status(200).send({ diff --git a/backend/src/helpers/secrets.ts b/backend/src/helpers/secrets.ts index 7d3e54105..f48855aef 100644 --- a/backend/src/helpers/secrets.ts +++ b/backend/src/helpers/secrets.ts @@ -7,7 +7,8 @@ import { ServiceTokenData, IServiceTokenData, Secret, - ISecret + ISecret, + SecretBlindIndexData, } from '../models'; import { validateMembership @@ -34,6 +35,9 @@ import { AUTH_MODE_SERVICE_TOKEN, AUTH_MODE_API_KEY } from '../variables'; +import crypto from 'crypto'; +import * as argon2 from 'argon2'; + /** * Validate authenticated clients for secrets with id [secretId] based @@ -192,7 +196,74 @@ const validateClientForSecrets = async ({ }); } +/** + * Create and return blind index for secret with + * name [name] part of workspace with id [workspaceId] + * @param {Object} obj + * @param {Object} obj.secretName - name of secret to generate blind index for + * @param {Object} obj.workspaceId - id of workspace that secret belongs to + */ +const createSecretBlindIndexHelper = async ({ + secretName, + workspaceId +}: { + secretName: string; + workspaceId: Types.ObjectId; +}) => { + + // check if workspace blind index data exists + // const secretBlindIndexData = await SecretBlindIndexData.findOne({ + // workspace: workspaceId + // }); + + // if (!secretBlindIndexData) { + // // case: workspace blind index data has not been enabled + // } + + // TODO: randomBytes should come from the decrypted secretBlindIndexData + const randomBytes = crypto.randomBytes(16); + + const secretBlindIndex = (await argon2.hash(secretName, { + type: argon2.argon2id, + salt: randomBytes, + saltLength: 16, // default 16 bytes + memoryCost: 65536, // default pool of 64 MiB per thread. + hashLength: 32, + parallelism: 1, + raw: true + })).toString('base64'); + + return secretBlindIndex; +} + +/** + * Return the blind index for the secret with + * name [name] part of workspace with id [workspaceId] + * @param {Object} obj + * @param {Object} obj.secretName - name of secret to generate blind index for + * @param {Object} obj.workspaceId - id of workspace that secret belongs to + */ +const getSecretBlindIndexHelper = async ({ + secretName, + workspaceId +}: { + secretName: string; + workspaceId: Types.ObjectId; +}) => { + + // check if workspace blind index data exists + const secretBlindIndexData = await SecretBlindIndexData.findOne({ + workspace: workspaceId + }); + + if (!secretBlindIndexData) { + // case: workspace blind index data has not been enabled + } +} + export { validateClientForSecret, - validateClientForSecrets + validateClientForSecrets, + createSecretBlindIndexHelper, + getSecretBlindIndexHelper } \ No newline at end of file diff --git a/backend/src/models/workspace.ts b/backend/src/models/workspace.ts index d68ae9416..e6f1a69d3 100644 --- a/backend/src/models/workspace.ts +++ b/backend/src/models/workspace.ts @@ -1,8 +1,14 @@ import { Schema, model, Types } from 'mongoose'; +import { + WORKSPACE_ENCRYPTION_MODE_E2EE, + WORKSPACE_ENCRYPTION_MODE_BLIND_INDEXED_E2EE, + WORKSPACE_ENCRYPTION_MODE_NOT_E2EE +} from '../variables'; export interface IWorkspace { _id: Types.ObjectId; name: string; + encryptionMode: string; organization: Types.ObjectId; environments: Array<{ name: string; @@ -16,6 +22,15 @@ const workspaceSchema = new Schema({ type: String, required: true }, + encryptionMode: { + type: String, + default: 'e2ee', + enum: [ + WORKSPACE_ENCRYPTION_MODE_E2EE, + WORKSPACE_ENCRYPTION_MODE_BLIND_INDEXED_E2EE, + WORKSPACE_ENCRYPTION_MODE_NOT_E2EE + ] + }, autoCapitalization: { type: Boolean, default: true, diff --git a/backend/src/services/SecretService.ts b/backend/src/services/SecretService.ts index 21ecf1124..0c7132327 100644 --- a/backend/src/services/SecretService.ts +++ b/backend/src/services/SecretService.ts @@ -1,7 +1,18 @@ // WIP import { Types } from 'mongoose'; +import { + createSecretBlindIndexHelper, + getSecretBlindIndexHelper +} from '../helpers/secrets'; class SecretService { + /** + * Create and return blind index for secret with + * name [name] part of workspace with id [workspaceId] + * @param {Object} obj + * @param {Object} obj.secretName - name of secret to generate blind index for + * @param {Object} obj.workspaceId - id of workspace that secret belongs to + */ static async createSecretBlindIndex({ secretName, workspaceId, @@ -9,10 +20,19 @@ class SecretService { secretName: string; workspaceId: Types.ObjectId; }) { - // TODO - return; + return await createSecretBlindIndexHelper({ + secretName, + workspaceId + }); } + /** + * Return the blind index for the secret with + * name [name] part of workspace with id [workspaceId] + * @param {Object} obj + * @param {Object} obj.secretName - name of secret to generate blind index for + * @param {Object} obj.workspaceId - id of workspace that secret belongs to + */ static async getSecretBlindIndex({ secretName, workspaceId @@ -20,7 +40,11 @@ class SecretService { secretName: string; workspaceId: Types.ObjectId; }) { - // TODO - return; + return await getSecretBlindIndexHelper({ + secretName, + workspaceId + }); } -} \ No newline at end of file +} + +export default SecretService; \ No newline at end of file diff --git a/backend/src/services/index.ts b/backend/src/services/index.ts index f93f45f58..db0bd39a3 100644 --- a/backend/src/services/index.ts +++ b/backend/src/services/index.ts @@ -5,14 +5,14 @@ import BotService from './BotService'; import EventService from './EventService'; import IntegrationService from './IntegrationService'; import TokenService from './TokenService'; +import SecretService from './SecretService'; export { TelemetryService, - // logTelemetryMessage, - // getPostHogClient, DatabaseService, BotService, EventService, IntegrationService, - TokenService + TokenService, + SecretService } \ No newline at end of file diff --git a/backend/src/variables/index.ts b/backend/src/variables/index.ts index 979de21ab..92f76ac8e 100644 --- a/backend/src/variables/index.ts +++ b/backend/src/variables/index.ts @@ -77,6 +77,11 @@ import { AUTH_MODE_SERVICE_TOKEN, AUTH_MODE_API_KEY } from './authentication'; +import { + WORKSPACE_ENCRYPTION_MODE_E2EE, + WORKSPACE_ENCRYPTION_MODE_BLIND_INDEXED_E2EE, + WORKSPACE_ENCRYPTION_MODE_NOT_E2EE +} from './workspace'; export { OWNER, @@ -148,5 +153,8 @@ export { AUTH_MODE_JWT, AUTH_MODE_SERVICE_ACCOUNT, AUTH_MODE_SERVICE_TOKEN, - AUTH_MODE_API_KEY + AUTH_MODE_API_KEY, + WORKSPACE_ENCRYPTION_MODE_E2EE, + WORKSPACE_ENCRYPTION_MODE_BLIND_INDEXED_E2EE, + WORKSPACE_ENCRYPTION_MODE_NOT_E2EE }; diff --git a/backend/src/variables/workspace.ts b/backend/src/variables/workspace.ts new file mode 100644 index 000000000..c0be34fcb --- /dev/null +++ b/backend/src/variables/workspace.ts @@ -0,0 +1,9 @@ +const WORKSPACE_ENCRYPTION_MODE_E2EE = 'e2ee'; +const WORKSPACE_ENCRYPTION_MODE_BLIND_INDEXED_E2EE = 'blind-indexed-e2ee'; +const WORKSPACE_ENCRYPTION_MODE_NOT_E2EE = 'not-e2ee'; + +export { + WORKSPACE_ENCRYPTION_MODE_E2EE, + WORKSPACE_ENCRYPTION_MODE_BLIND_INDEXED_E2EE, + WORKSPACE_ENCRYPTION_MODE_NOT_E2EE +} \ No newline at end of file diff --git a/frontend/src/views/Settings/ProjectSettingsPage/ProjectSettingsPage.tsx b/frontend/src/views/Settings/ProjectSettingsPage/ProjectSettingsPage.tsx index af812c6aa..1684f6c1a 100644 --- a/frontend/src/views/Settings/ProjectSettingsPage/ProjectSettingsPage.tsx +++ b/frontend/src/views/Settings/ProjectSettingsPage/ProjectSettingsPage.tsx @@ -39,9 +39,9 @@ import { CreateUpdateEnvFormData, CreateWsTag, EnvironmentSection, + ProjectEncryptionModeSection, ProjectNameChangeSection, - ServiceTokenSection -} from './components'; + ServiceTokenSection} from './components'; export const ProjectSettingsPage = () => { const { t } = useTranslation(); @@ -349,6 +349,7 @@ export const ProjectSettingsPage = () => { workspaceAutoCapitalization={currentWorkspace?.autoCapitalization} onAutoCapitalizationChange={onAutoCapitalizationToggle} /> +

{t('settings-project:danger-zone')}

{t('settings-project:danger-zone-note')}

diff --git a/frontend/src/views/Settings/ProjectSettingsPage/components/ProjectEncryptionModeSection/ProjectEncryptionModeSection.tsx b/frontend/src/views/Settings/ProjectSettingsPage/components/ProjectEncryptionModeSection/ProjectEncryptionModeSection.tsx new file mode 100644 index 000000000..94c1f2037 --- /dev/null +++ b/frontend/src/views/Settings/ProjectSettingsPage/components/ProjectEncryptionModeSection/ProjectEncryptionModeSection.tsx @@ -0,0 +1,8 @@ + +export const ProjectEncryptionModeSection = () => { + return ( +
+ Project encryption mode section +
+ ); +} \ No newline at end of file diff --git a/frontend/src/views/Settings/ProjectSettingsPage/components/ProjectEncryptionModeSection/index.tsx b/frontend/src/views/Settings/ProjectSettingsPage/components/ProjectEncryptionModeSection/index.tsx new file mode 100644 index 000000000..981819f70 --- /dev/null +++ b/frontend/src/views/Settings/ProjectSettingsPage/components/ProjectEncryptionModeSection/index.tsx @@ -0,0 +1 @@ +export { ProjectEncryptionModeSection } from './ProjectEncryptionModeSection'; \ No newline at end of file diff --git a/frontend/src/views/Settings/ProjectSettingsPage/components/index.tsx b/frontend/src/views/Settings/ProjectSettingsPage/components/index.tsx index 98160b5b7..83a4b3a95 100644 --- a/frontend/src/views/Settings/ProjectSettingsPage/components/index.tsx +++ b/frontend/src/views/Settings/ProjectSettingsPage/components/index.tsx @@ -1,6 +1,7 @@ export { CopyProjectIDSection } from './CopyProjectIDSection'; export { EnvironmentSection } from './EnvironmentSection'; export type { CreateUpdateEnvFormData } from './EnvironmentSection/EnvironmentSection'; +export { ProjectEncryptionModeSection } from './ProjectEncryptionModeSection'; export { ProjectNameChangeSection } from './ProjectNameChangeSection'; export type { CreateWsTag } from './SecretTagsSection/SecretTagsSection'; export { ServiceTokenSection } from './ServiceTokenSection';