diff --git a/backend/src/lib/secret/index.ts b/backend/src/lib/secret/index.ts index 4292c7945..036b49970 100644 --- a/backend/src/lib/secret/index.ts +++ b/backend/src/lib/secret/index.ts @@ -1,46 +1,45 @@ import crypto from "crypto"; import { z } from "zod"; -import { TProjectKeys } from "@app/db/schemas"; -import { logger } from "@app/lib/logger"; +import { + SecretApprovalRequestsSecretsSchema, + SecretsSchema, + SecretVersionsSchema, + TProjectKeys, + TSecretApprovalRequestsSecrets, + TSecrets, + TSecretVersions +} from "@app/db/schemas"; import { decryptAsymmetric } from "../crypto"; -export enum SecretDocType { - Secret = "secret", - SecretVersion = "secretVersion", - ApprovalSecret = "approvalSecret" -} - -export interface TPartialSecret { - id: string; - secretKeyCiphertext: string; - secretKeyIV: string; - secretKeyTag: string; - - secretValueCiphertext: string; - secretValueIV: string; - secretValueTag: string; - - secretCommentCiphertext?: string | null; - secretCommentIV?: string | null; - secretCommentTag?: string | null; - - docType: SecretDocType; - keyEncoding: string; -} - -const PartialDecryptedSecretSchema = z.object({ +const DecryptedValuesSchema = z.object({ id: z.string(), secretKey: z.string(), secretValue: z.string(), - secretComment: z.string().optional(), - - docType: z.nativeEnum(SecretDocType) + secretComment: z.string().optional() }); -export type TPartialDecryptedSecret = z.infer; -const decryptSecret = ({ +const DecryptedSecretSchema = z.object({ + decrypted: DecryptedValuesSchema, + original: SecretsSchema +}); + +const DecryptedSecretVersionsSchema = z.object({ + decrypted: DecryptedValuesSchema, + original: SecretVersionsSchema +}); + +export const DecryptedSecretApprovalsSchema = z.object({ + decrypted: DecryptedValuesSchema, + original: SecretApprovalRequestsSecretsSchema +}); + +export type DecryptedSecret = z.infer; +export type DecryptedSecretVersions = z.infer; +export type DecryptedSecretApprovals = z.infer; + +const decryptCipher = ({ ciphertext, iv, tag, @@ -60,8 +59,62 @@ const decryptSecret = ({ return cleartext; }; +const getDecryptedValues = ({ + secretKeyCiphertext, + secretKeyIV, + secretKeyTag, + secretValueCiphertext, + secretValueIV, + secretValueTag, + + secretCommentCiphertext, + secretCommentIV, + secretCommentTag, + key +}: { + secretKeyCiphertext: string; + secretKeyIV: string; + secretKeyTag: string; + secretValueCiphertext: string; + secretValueIV: string; + secretValueTag: string; + secretCommentCiphertext?: string | null; + secretCommentIV?: string | null; + secretCommentTag?: string | null; + key: string | Buffer; +}) => { + const secretKey = decryptCipher({ + ciphertext: secretKeyCiphertext, + iv: secretKeyIV, + tag: secretKeyTag, + key + }); + + const secretValue = decryptCipher({ + ciphertext: secretValueCiphertext, + iv: secretValueIV, + tag: secretValueTag, + key + }); + + const secretComment = + secretCommentCiphertext && secretCommentIV && secretCommentTag + ? decryptCipher({ + ciphertext: secretCommentCiphertext, + iv: secretCommentIV, + tag: secretCommentTag, + key + }) + : ""; + + return { + secretKey, + secretValue, + secretComment + }; +}; export const decryptSecrets = ( - encryptedSecrets: TPartialSecret[], + encryptedSecrets: TSecrets[], privateKey: string, latestKey: TProjectKeys & { sender: { @@ -76,47 +129,123 @@ export const decryptSecrets = ( privateKey }); - const decryptedSecrets: TPartialDecryptedSecret[] = []; + const decryptedSecrets: DecryptedSecret[] = []; encryptedSecrets.forEach((encSecret) => { - try { - const secretKey = decryptSecret({ - ciphertext: encSecret.secretKeyCiphertext, - iv: encSecret.secretKeyIV, - tag: encSecret.secretKeyTag, - key - }); + const decrypted = getDecryptedValues({ + secretKeyCiphertext: encSecret.secretKeyCiphertext, + secretKeyIV: encSecret.secretKeyIV, + secretKeyTag: encSecret.secretKeyTag, + secretValueCiphertext: encSecret.secretValueCiphertext, + secretValueIV: encSecret.secretValueIV, + secretValueTag: encSecret.secretValueTag, + secretCommentCiphertext: encSecret.secretCommentCiphertext, + secretCommentIV: encSecret.secretCommentIV, + secretCommentTag: encSecret.secretCommentTag, + key + }); - const secretValue = decryptSecret({ - ciphertext: encSecret.secretValueCiphertext, - iv: encSecret.secretValueIV, - tag: encSecret.secretValueTag, - key - }); + const decryptedSecret: DecryptedSecret = { + decrypted: { + ...decrypted, + id: encSecret.id + }, + original: encSecret + }; - const secretComment = - encSecret.secretCommentCiphertext && encSecret.secretCommentIV && encSecret.secretCommentTag - ? decryptSecret({ - ciphertext: encSecret.secretCommentCiphertext, - iv: encSecret.secretCommentIV, - tag: encSecret.secretCommentTag, - key - }) - : ""; - - const decryptedSecret: TPartialDecryptedSecret = { - id: encSecret.id, - secretKey, - secretValue, - secretComment, - docType: encSecret.docType - }; - - decryptedSecrets.push(PartialDecryptedSecretSchema.parse(decryptedSecret)); - } catch (err) { - // This is ok, because we check that the decrypted secrets array length is the same as the encrypted secrets input array length. - logger.error(`[${encSecret.id}] - failed to decrypt`, err); - } + decryptedSecrets.push(DecryptedSecretSchema.parse(decryptedSecret)); + }); + + return decryptedSecrets; +}; + +export const decryptSecretVersions = ( + encryptedSecretVersions: TSecretVersions[], + privateKey: string, + latestKey: TProjectKeys & { + sender: { + publicKey: string; + }; + } +) => { + const key = decryptAsymmetric({ + ciphertext: latestKey.encryptedKey, + nonce: latestKey.nonce, + publicKey: latestKey.sender.publicKey, + privateKey + }); + + const decryptedSecrets: DecryptedSecretVersions[] = []; + + encryptedSecretVersions.forEach((encSecret) => { + const decrypted = getDecryptedValues({ + secretKeyCiphertext: encSecret.secretKeyCiphertext, + secretKeyIV: encSecret.secretKeyIV, + secretKeyTag: encSecret.secretKeyTag, + secretValueCiphertext: encSecret.secretValueCiphertext, + secretValueIV: encSecret.secretValueIV, + secretValueTag: encSecret.secretValueTag, + secretCommentCiphertext: encSecret.secretCommentCiphertext, + secretCommentIV: encSecret.secretCommentIV, + secretCommentTag: encSecret.secretCommentTag, + key + }); + + const decryptedSecret: DecryptedSecretVersions = { + decrypted: { + ...decrypted, + id: encSecret.id + }, + original: encSecret + }; + + decryptedSecrets.push(DecryptedSecretVersionsSchema.parse(decryptedSecret)); + }); + + return decryptedSecrets; +}; + +export const decryptSecretApprovals = ( + encryptedSecretApprovals: TSecretApprovalRequestsSecrets[], + privateKey: string, + latestKey: TProjectKeys & { + sender: { + publicKey: string; + }; + } +) => { + const key = decryptAsymmetric({ + ciphertext: latestKey.encryptedKey, + nonce: latestKey.nonce, + publicKey: latestKey.sender.publicKey, + privateKey + }); + + const decryptedSecrets: DecryptedSecretApprovals[] = []; + + encryptedSecretApprovals.forEach((encSecret) => { + const decrypted = getDecryptedValues({ + secretKeyCiphertext: encSecret.secretKeyCiphertext, + secretKeyIV: encSecret.secretKeyIV, + secretKeyTag: encSecret.secretKeyTag, + secretValueCiphertext: encSecret.secretValueCiphertext, + secretValueIV: encSecret.secretValueIV, + secretValueTag: encSecret.secretValueTag, + secretCommentCiphertext: encSecret.secretCommentCiphertext, + secretCommentIV: encSecret.secretCommentIV, + secretCommentTag: encSecret.secretCommentTag, + key + }); + + const decryptedSecret: DecryptedSecretApprovals = { + decrypted: { + ...decrypted, + id: encSecret.id + }, + original: encSecret + }; + + decryptedSecrets.push(DecryptedSecretApprovalsSchema.parse(decryptedSecret)); }); return decryptedSecrets; diff --git a/backend/src/services/project/project-queue.ts b/backend/src/services/project/project-queue.ts index 7bf9f0e51..15ee9abec 100644 --- a/backend/src/services/project/project-queue.ts +++ b/backend/src/services/project/project-queue.ts @@ -504,6 +504,21 @@ export const projectQueueFactory = ({ throw new Error("Parts of the upgrade failed. Some secrets were not updated"); } + const secretUpdates = await secretDAL.bulkUpdateNoVersionIncrement(updatedSecrets, tx); + const secretVersionUpdates = await secretVersionDAL.bulkUpdateNoVersionIncrement(updatedSecretVersions, tx); + const secretApprovalUpdates = await secretApprovalSecretDAL.bulkUpdateNoVersionIncrement( + updatedSecretApprovals, + tx + ); + + if ( + secretUpdates.length !== updatedSecrets.length || + secretVersionUpdates.length !== updatedSecretVersions.length || + secretApprovalUpdates.length !== updatedSecretApprovals.length + ) { + throw new Error("Parts of the upgrade failed. Some secrets were not updated"); + } + await projectDAL.setProjectUpgradeStatus(data.projectId, null, tx); // await new Promise((resolve) => setTimeout(resolve, 15_000));