diff --git a/web/apps/photos/src/services/collectionService.ts b/web/apps/photos/src/services/collectionService.ts index b002c42b0e..94e60e4613 100644 --- a/web/apps/photos/src/services/collectionService.ts +++ b/web/apps/photos/src/services/collectionService.ts @@ -70,7 +70,7 @@ const createCollection = async ( const cryptoWorker = await sharedCryptoWorker(); const encryptionKey = await getActualKey(); const token = getToken(); - const collectionKey = await cryptoWorker.generateEncryptionKey(); + const collectionKey = await cryptoWorker.generateKey(); const { encryptedData: encryptedKey, nonce: keyDecryptionNonce } = await cryptoWorker.encryptToB64(collectionKey, encryptionKey); const { encryptedData: encryptedName, nonce: nameDecryptionNonce } = diff --git a/web/docs/dependencies.md b/web/docs/dependencies.md index 92cf8acd2d..14d59c5bfc 100644 --- a/web/docs/dependencies.md +++ b/web/docs/dependencies.md @@ -205,6 +205,9 @@ via [@fontsource-variable/inter](https://fontsource.org/fonts/inter/install). [pDebounce](https://github.com/sindresorhus/p-debounce) are used for debouncing operations (See also: `[Note: Throttle and debounce]`). +- [bip39](https://github.com/bitcoinjs/bip39) is used for generating the 24-word + recovery key mnemonic. + - [zxcvbn](https://github.com/dropbox/zxcvbn) is used for password strength estimation. diff --git a/web/packages/accounts/components/RecoveryKey.tsx b/web/packages/accounts/components/RecoveryKey.tsx index 45722ac170..9e1b5da3a0 100644 --- a/web/packages/accounts/components/RecoveryKey.tsx +++ b/web/packages/accounts/components/RecoveryKey.tsx @@ -6,7 +6,6 @@ import { Stack, Typography, } from "@mui/material"; -import * as bip39 from "bip39"; import { type MiniDialogAttributes } from "ente-base/components/MiniDialog"; import { SpacedRow } from "ente-base/components/containers"; import { DialogCloseIconButton } from "ente-base/components/mui/DialogCloseIconButton"; @@ -16,14 +15,14 @@ import { useIsSmallWidth } from "ente-base/components/utils/hooks"; import type { ModalVisibilityProps } from "ente-base/components/utils/modal"; import log from "ente-base/log"; import { downloadString } from "ente-base/utils/web"; -import { getRecoveryKey } from "ente-shared/crypto/helpers"; import { t } from "i18next"; import { useCallback, useEffect, useState } from "react"; +import { + getUserRecoveryKeyB64, + recoveryKeyB64ToMnemonic, +} from "../services/recovery-key"; import { CodeBlock } from "./CodeBlock"; -// mobile client library only supports english. -bip39.setDefaultWordlist("english"); - type RecoveryKeyProps = ModalVisibilityProps & { showMiniDialog: (attributes: MiniDialogAttributes) => void; }; @@ -116,7 +115,7 @@ export const RecoveryKey: React.FC = ({ }; const getRecoveryKeyMnemonic = async () => - bip39.entropyToMnemonic(await getRecoveryKey()); + recoveryKeyB64ToMnemonic(await getUserRecoveryKeyB64()); const downloadRecoveryKeyMnemonic = (key: string) => downloadString(key, "ente-recovery-key.txt"); diff --git a/web/packages/accounts/pages/recover.tsx b/web/packages/accounts/pages/recover.tsx index 179edbc155..7d4569795d 100644 --- a/web/packages/accounts/pages/recover.tsx +++ b/web/packages/accounts/pages/recover.tsx @@ -3,11 +3,12 @@ import { AccountsPageFooter, AccountsPageTitle, } from "ente-accounts/components/layouts/centered-paper"; +import { recoveryKeyB64FromMnemonic } from "ente-accounts/services/recovery-key"; import { appHomeRoute, stashRedirect } from "ente-accounts/services/redirect"; import { sendOTT } from "ente-accounts/services/user"; import { LinkButton } from "ente-base/components/LinkButton"; import { useBaseContext } from "ente-base/context"; -import { sharedCryptoWorker } from "ente-base/crypto"; +import { decryptBoxB64 } from "ente-base/crypto"; import log from "ente-base/log"; import SingleInputForm, { type SingleInputFormProps, @@ -23,11 +24,6 @@ import { t } from "i18next"; import { useRouter } from "next/router"; import { useEffect, useState } from "react"; -// eslint-disable-next-line @typescript-eslint/no-require-imports -const bip39 = require("bip39"); -// mobile client library only supports english. -bip39.setDefaultWordlist("english"); - const Page: React.FC = () => { const { showMiniDialog } = useBaseContext(); @@ -65,25 +61,13 @@ const Page: React.FC = () => { setFieldError, ) => { try { - recoveryKey = recoveryKey - .trim() - .split(" ") - .map((part) => part.trim()) - .filter((part) => !!part) - .join(" "); - // check if user is entering mnemonic recovery key - if (recoveryKey.indexOf(" ") > 0) { - if (recoveryKey.split(" ").length !== 24) { - throw new Error("recovery code should have 24 words"); - } - recoveryKey = bip39.mnemonicToEntropy(recoveryKey); - } - const cryptoWorker = await sharedCryptoWorker(); const keyAttr = keyAttributes!; - const masterKey = await cryptoWorker.decryptB64( - keyAttr.masterKeyEncryptedWithRecoveryKey!, - keyAttr.masterKeyDecryptionNonce!, - await cryptoWorker.fromHex(recoveryKey), + const masterKey = await decryptBoxB64( + { + encryptedData: keyAttr.masterKeyEncryptedWithRecoveryKey!, + nonce: keyAttr.masterKeyDecryptionNonce!, + }, + await recoveryKeyB64FromMnemonic(recoveryKey), ); await saveKeyInSessionStore("encryptionKey", masterKey); await decryptAndStoreToken(keyAttr, masterKey); diff --git a/web/packages/accounts/pages/two-factor/recover.tsx b/web/packages/accounts/pages/two-factor/recover.tsx index f0aba30440..df55ea2682 100644 --- a/web/packages/accounts/pages/two-factor/recover.tsx +++ b/web/packages/accounts/pages/two-factor/recover.tsx @@ -5,6 +5,7 @@ import { AccountsPageFooter, AccountsPageTitle, } from "ente-accounts/components/layouts/centered-paper"; +import { recoveryKeyB64FromMnemonic } from "ente-accounts/services/recovery-key"; import { recoverTwoFactor, removeTwoFactor, @@ -13,7 +14,7 @@ import { import { LinkButton } from "ente-base/components/LinkButton"; import type { MiniDialogAttributes } from "ente-base/components/MiniDialog"; import { useBaseContext } from "ente-base/context"; -import { sharedCryptoWorker } from "ente-base/crypto"; +import { decryptBoxB64 } from "ente-base/crypto"; import type { B64EncryptionResult } from "ente-base/crypto/libsodium"; import log from "ente-base/log"; import SingleInputForm, { @@ -26,11 +27,6 @@ import { useRouter } from "next/router"; import { useEffect, useState } from "react"; import { Trans } from "react-i18next"; -// eslint-disable-next-line @typescript-eslint/no-require-imports -const bip39 = require("bip39"); -// mobile client library only supports english. -bip39.setDefaultWordlist("english"); - export interface RecoverPageProps { twoFactorType: TwoFactorType; } @@ -95,25 +91,10 @@ const Page: React.FC = ({ twoFactorType }) => { setFieldError, ) => { try { - recoveryKey = recoveryKey - .trim() - .split(" ") - .map((part) => part.trim()) - .filter((part) => !!part) - .join(" "); - // check if user is entering mnemonic recovery key - if (recoveryKey.indexOf(" ") > 0) { - if (recoveryKey.split(" ").length !== 24) { - throw new Error("recovery code should have 24 words"); - } - recoveryKey = bip39.mnemonicToEntropy(recoveryKey); - } - const cryptoWorker = await sharedCryptoWorker(); const { encryptedData, nonce } = encryptedTwoFactorSecret!; - const twoFactorSecret = await cryptoWorker.decryptB64( - encryptedData, - nonce, - await cryptoWorker.fromHex(recoveryKey), + const twoFactorSecret = await decryptBoxB64( + { encryptedData, nonce }, + await recoveryKeyB64FromMnemonic(recoveryKey), ); const resp = await removeTwoFactor( sessionID!, diff --git a/web/packages/accounts/pages/two-factor/setup.tsx b/web/packages/accounts/pages/two-factor/setup.tsx index ec61ccacfe..2f128d35da 100644 --- a/web/packages/accounts/pages/two-factor/setup.tsx +++ b/web/packages/accounts/pages/two-factor/setup.tsx @@ -8,11 +8,12 @@ import { CenteredFill } from "ente-base/components/containers"; import { LinkButton } from "ente-base/components/LinkButton"; import { ActivityIndicator } from "ente-base/components/mui/ActivityIndicator"; import { FocusVisibleButton } from "ente-base/components/mui/FocusVisibleButton"; -import { encryptWithRecoveryKey } from "ente-shared/crypto/helpers"; +import { encryptBoxB64 } from "ente-base/crypto"; import { getData, setLSUser } from "ente-shared/storage/localStorage"; import { t } from "i18next"; import { useRouter } from "next/router"; import React, { useEffect, useState } from "react"; +import { getUserRecoveryKeyB64 } from "../../services/recovery-key"; const Page: React.FC = () => { const [twoFactorSecret, setTwoFactorSecret] = useState< @@ -26,14 +27,14 @@ const Page: React.FC = () => { }, []); const handleSubmit = async (otp: string) => { - const { - encryptedData: encryptedTwoFactorSecret, - nonce: twoFactorSecretDecryptionNonce, - } = await encryptWithRecoveryKey(twoFactorSecret!.secretCode); + const box = await encryptBoxB64( + twoFactorSecret!.secretCode, + await getUserRecoveryKeyB64(), + ); await enableTwoFactor({ code: otp, - encryptedTwoFactorSecret, - twoFactorSecretDecryptionNonce, + encryptedTwoFactorSecret: box.encryptedData, + twoFactorSecretDecryptionNonce: box.nonce, }); await setLSUser({ ...getData("user"), isTwoFactorEnabled: true }); await router.push(appHomeRoute); diff --git a/web/packages/accounts/services/passkey.ts b/web/packages/accounts/services/passkey.ts index 3e7cd99683..e2a749a93e 100644 --- a/web/packages/accounts/services/passkey.ts +++ b/web/packages/accounts/services/passkey.ts @@ -1,10 +1,6 @@ import { TwoFactorAuthorizationResponse } from "ente-accounts/services/user"; import { clientPackageName, isDesktop } from "ente-base/app"; -import { sharedCryptoWorker } from "ente-base/crypto"; -import { - encryptToB64, - generateEncryptionKey, -} from "ente-base/crypto/libsodium"; +import { encryptBoxB64, generateKey } from "ente-base/crypto"; import { authenticatedRequestHeaders, ensureOk, @@ -13,11 +9,11 @@ import { } from "ente-base/http"; import log from "ente-base/log"; import { apiURL } from "ente-base/origins"; -import { getRecoveryKey } from "ente-shared/crypto/helpers"; import HTTPService from "ente-shared/network/HTTPService"; import { getData, setData, setLSUser } from "ente-shared/storage/localStorage"; import { getToken } from "ente-shared/storage/localStorage/helpers"; import { z } from "zod"; +import { getUserRecoveryKeyB64 } from "./recovery-key"; import { unstashRedirect } from "./redirect"; /** @@ -111,20 +107,15 @@ export const openAccountsManagePasskeysPage = async () => { if (!recoveryEnabled) { // If not, enable it for them by creating the necessary recovery // information to prevent them from getting locked out. - const recoveryKey = await getRecoveryKey(); - - const resetSecret = await generateEncryptionKey(); - - const cryptoWorker = await sharedCryptoWorker(); - const encryptionResult = await encryptToB64( + const resetSecret = await generateKey(); + const box = await encryptBoxB64( resetSecret, - await cryptoWorker.fromHex(recoveryKey), + await getUserRecoveryKeyB64(), ); - await configurePasskeyRecovery( resetSecret, - encryptionResult.encryptedData, - encryptionResult.nonce, + box.encryptedData, + box.nonce, ); } diff --git a/web/packages/accounts/services/recovery-key.ts b/web/packages/accounts/services/recovery-key.ts new file mode 100644 index 0000000000..dcb1011337 --- /dev/null +++ b/web/packages/accounts/services/recovery-key.ts @@ -0,0 +1,122 @@ +import * as bip39 from "bip39"; +import { + decryptBoxB64, + fromHex, + sharedCryptoWorker, + toHex, +} from "ente-base/crypto"; +import { masterKeyFromSession } from "ente-base/session"; +import { getData, setData } from "ente-shared/storage/localStorage"; +import type { KeyAttributes } from "ente-shared/user/types"; +import { putUserRecoveryKeyAttributes } from "./user"; + +// Mobile client library only supports English. +bip39.setDefaultWordlist("english"); + +/** + * Convert the provided BIP-39 mnemonic string into its base64 representation. + * + * @param recoveryKeyMnemonicOrHex The BIP-39 mnemonic (24 word) string + * representing the recovery key. For legacy compatibility, the function also + * works if provided the hex representation of the recovery key. + * + * @returns A base64 string representing the underlying bytes of the recovery key. + */ +export const recoveryKeyB64FromMnemonic = ( + recoveryKeyMnemonicOrHex: string, +) => { + const trimmedInput = recoveryKeyMnemonicOrHex + .trim() + .split(" ") + .map((part) => part.trim()) + .filter((part) => !!part) + .join(" "); + + let recoveryKeyHex: string; + // Check if user is entering mnemonic recovery key. + if (trimmedInput.indexOf(" ") > 0) { + if (trimmedInput.split(" ").length != 24) { + throw new Error("recovery code should have 24 words"); + } + recoveryKeyHex = bip39.mnemonicToEntropy(trimmedInput); + } else { + recoveryKeyHex = trimmedInput; + } + + return fromHex(recoveryKeyHex); +}; + +/** + * Convert the provided base64 encoded recovery key into its BIP-39 mnemonic. + * + * @param recoveryKeyB64 The base64 encoded recovery key to mnemonize. + * + * @returns A 24-word mnemonic that serves as the user visible recovery key. + */ +export const recoveryKeyB64ToMnemonic = async (recoveryKeyB64: string) => + bip39.entropyToMnemonic(await toHex(recoveryKeyB64)); + +/** + * Return the (decrypted) recovery key of the logged in user. + * + * @returns The user's base64 encoded recovery key. + */ +export const getUserRecoveryKeyB64 = async () => { + const masterKey = await masterKeyFromSession(); + + const keyAttributes: KeyAttributes = getData("keyAttributes"); + const { recoveryKeyEncryptedWithMasterKey, recoveryKeyDecryptionNonce } = + keyAttributes; + + if (recoveryKeyEncryptedWithMasterKey && recoveryKeyDecryptionNonce) { + return decryptBoxB64( + { + encryptedData: recoveryKeyEncryptedWithMasterKey, + nonce: recoveryKeyDecryptionNonce, + }, + masterKey, + ); + } else { + return createNewRecoveryKey(masterKey); + } +}; + +/** + * Generate a new recovery key, tell remote about it, update our local state, + * and then return it. + * + * This function will be used only for legacy users for whom we did not generate + * recovery keys during sign up. + * + * @returns a new base64 encoded recovery key. + */ +const createNewRecoveryKey = async (masterKey: Uint8Array) => { + const existingAttributes = getData("keyAttributes"); + + const cryptoWorker = await sharedCryptoWorker(); + const recoveryKey = await cryptoWorker.generateKey(); + const encryptedMasterKey = await cryptoWorker.encryptBoxB64( + masterKey, + recoveryKey, + ); + const encryptedRecoveryKey = await cryptoWorker.encryptBoxB64( + recoveryKey, + masterKey, + ); + + const recoveryKeyAttributes = { + masterKeyEncryptedWithRecoveryKey: encryptedMasterKey.encryptedData, + masterKeyDecryptionNonce: encryptedMasterKey.nonce, + recoveryKeyEncryptedWithMasterKey: encryptedRecoveryKey.encryptedData, + recoveryKeyDecryptionNonce: encryptedRecoveryKey.nonce, + }; + + await putUserRecoveryKeyAttributes(recoveryKeyAttributes); + + setData("keyAttributes", { + ...existingAttributes, + ...recoveryKeyAttributes, + }); + + return recoveryKey; +}; diff --git a/web/packages/accounts/services/srp.ts b/web/packages/accounts/services/srp.ts index fad07bb1a4..30face4208 100644 --- a/web/packages/accounts/services/srp.ts +++ b/web/packages/accounts/services/srp.ts @@ -171,8 +171,8 @@ export async function generateKeyAndSRPAttributes( srpSetupAttributes: SRPSetupAttributes; }> { const cryptoWorker = await sharedCryptoWorker(); - const masterKey = await cryptoWorker.generateEncryptionKey(); - const recoveryKey = await cryptoWorker.generateEncryptionKey(); + const masterKey = await cryptoWorker.generateKey(); + const recoveryKey = await cryptoWorker.generateKey(); const kekSalt = await cryptoWorker.generateSaltToDeriveKey(); const kek = await cryptoWorker.deriveSensitiveKey(passphrase, kekSalt); diff --git a/web/packages/accounts/services/user.ts b/web/packages/accounts/services/user.ts index 6f5d55f85e..f02eabf71c 100644 --- a/web/packages/accounts/services/user.ts +++ b/web/packages/accounts/services/user.ts @@ -61,13 +61,6 @@ export interface UpdatedKey { opsLimit: number; } -export interface RecoveryKey { - masterKeyEncryptedWithRecoveryKey: string; - masterKeyDecryptionNonce: string; - recoveryKeyEncryptedWithMasterKey: string; - recoveryKeyDecryptionNonce: string; -} - /** * Ask remote to send a OTP / OTT to the given email to verify that the user has * access to it. Subsequent the app will pass this OTT back via the @@ -311,10 +304,28 @@ export const enableTwoFactor = async (req: EnableTwoFactorRequest) => }), ); -export const setRecoveryKey = async (token: string, recoveryKey: RecoveryKey) => - HTTPService.put( - await apiURL("/users/recovery-key"), - recoveryKey, - undefined, - { "X-Auth-Token": token }, +export interface RecoveryKeyAttributes { + masterKeyEncryptedWithRecoveryKey: string; + masterKeyDecryptionNonce: string; + recoveryKeyEncryptedWithMasterKey: string; + recoveryKeyDecryptionNonce: string; +} + +/** + * Update the encrypted recovery key attributes for the logged in user. + * + * In practice, this is not expected to be called and is meant as a rare + * fallback for very old accounts created prior to recovery key related + * attributes being assigned on account setup. Even for these, it'll be called + * only once. + */ +export const putUserRecoveryKeyAttributes = async ( + recoveryKeyAttributes: RecoveryKeyAttributes, +) => + ensureOk( + await fetch(await apiURL("/users/recovery-key"), { + method: "PUT", + headers: await authenticatedRequestHeaders(), + body: JSON.stringify(recoveryKeyAttributes), + }), ); diff --git a/web/packages/base/crypto/ente-impl.ts b/web/packages/base/crypto/ente-impl.ts index e719a57d32..39bc8eac9a 100644 --- a/web/packages/base/crypto/ente-impl.ts +++ b/web/packages/base/crypto/ente-impl.ts @@ -12,7 +12,7 @@ export const _toHex = libsodium.toHex; export const _fromHex = libsodium.fromHex; -export const _generateBoxKey = libsodium.generateBoxKey; +export const _generateKey = libsodium.generateKey; export const _generateBlobOrStreamKey = libsodium.generateBlobOrStreamKey; diff --git a/web/packages/base/crypto/index.ts b/web/packages/base/crypto/index.ts index 214f4d119e..d7abbb7a13 100644 --- a/web/packages/base/crypto/index.ts +++ b/web/packages/base/crypto/index.ts @@ -137,13 +137,15 @@ export const fromHex = (hexString: string) => : sharedWorker().then((w) => w.fromHex(hexString)); /** - * Return a new randomly generated 256-bit key (as a base64 string) suitable for - * use with the *Box encryption functions. + * Return a new randomly generated 256-bit key (as a base64 string). + * + * The returned key is suitable for use with the *Box encryption functions, and + * as a general encryption key (e.g. as the user's master key or recovery key). */ -export const generateBoxKey = () => +export const generateKey = () => inWorker() - ? ei._generateBoxKey() - : sharedWorker().then((w) => w.generateBoxKey()); + ? ei._generateKey() + : sharedWorker().then((w) => w.generateKey()); /** * Return a new randomly generated 256-bit key (as a base64 string) suitable for diff --git a/web/packages/base/crypto/libsodium.ts b/web/packages/base/crypto/libsodium.ts index 62d8fe1860..1f58fccbab 100644 --- a/web/packages/base/crypto/libsodium.ts +++ b/web/packages/base/crypto/libsodium.ts @@ -130,13 +130,29 @@ const bytes = async (bob: BytesOrB64) => typeof bob == "string" ? fromB64(bob) : bob; /** - * Generate a new key for use with the *Box encryption functions, and return its - * base64 string representation. + * Generate a new randomly generated 256-bit key for use as a general encryption + * key and return its base64 string representation. * - * This returns a new randomly generated 256-bit key suitable for being used - * with libsodium's secretbox APIs. + * From the architecture docs: + * + * > [`crypto_secretbox_keygen`](https://libsodium.gitbook.io/doc/public-key_cryptography/sealed_boxes) + * > is used to generate all random keys within the application. Your + * > `masterKey`, `recoveryKey`, `collectionKey`, `fileKey` are all 256-bit keys + * > generated using this API. + * + * {@link generateKey} can be contrasted with {@link generateBlobOrStreamKey} + * and can be thought of as a hypothetical "generateBoxKey". That is, the key + * returned by this function is suitable for being used with the *Box encryption + * functions (which eventually delegate to the libsodium's secretbox APIs). + * + * While this is a reasonable semantic distinction, in terms of implementation + * there is no difference: currently both {@link generateKey} (or the + * hypothetical "generateBoxKey") and {@link generateBlobOrStreamKey} produce + * 256-bits of entropy that does not have any ties to a particular algorithm. + * + * @returns A new randomly generated 256-bit key. */ -export const generateBoxKey = async () => { +export const generateKey = async () => { await sodium.ready; return toB64(sodium.crypto_secretbox_keygen()); }; @@ -863,11 +879,6 @@ export const deriveInteractiveKey = async ( return { key, opsLimit, memLimit }; }; -export async function generateEncryptionKey() { - await sodium.ready; - return await toB64(sodium.crypto_kdf_keygen()); -} - export async function generateSaltToDeriveKey() { await sodium.ready; return await toB64(sodium.randombytes_buf(sodium.crypto_pwhash_SALTBYTES)); diff --git a/web/packages/base/crypto/worker.ts b/web/packages/base/crypto/worker.ts index 8e6db10341..720b993d1d 100644 --- a/web/packages/base/crypto/worker.ts +++ b/web/packages/base/crypto/worker.ts @@ -18,7 +18,7 @@ export class CryptoWorker { fromB64 = ei._fromB64; toHex = ei._toHex; fromHex = ei._fromHex; - generateBoxKey = ei._generateBoxKey; + generateKey = ei._generateKey; generateBlobOrStreamKey = ei._generateBlobOrStreamKey; encryptBoxB64 = ei._encryptBoxB64; encryptThumbnail = ei._encryptThumbnail; @@ -70,10 +70,6 @@ export class CryptoWorker { return libsodium.encryptUTF8(data, key); } - async generateEncryptionKey() { - return libsodium.generateEncryptionKey(); - } - async generateSaltToDeriveKey() { return libsodium.generateSaltToDeriveKey(); } diff --git a/web/packages/new/photos/components/gallery/helpers.ts b/web/packages/new/photos/components/gallery/helpers.ts index afce9688d9..81596bf6ef 100644 --- a/web/packages/new/photos/components/gallery/helpers.ts +++ b/web/packages/new/photos/components/gallery/helpers.ts @@ -10,10 +10,10 @@ * is a needed for fast refresh to work. */ +import { getUserRecoveryKeyB64 } from "ente-accounts/services/recovery-key"; import log from "ente-base/log"; import type { Collection } from "ente-media/collection"; import type { FamilyData } from "ente-new/photos/services/user-details"; -import { getRecoveryKey } from "ente-shared/crypto/helpers"; import type { User } from "ente-shared/user/types"; /** @@ -28,7 +28,7 @@ import type { User } from "ente-shared/user/types"; */ export const validateKey = async () => { try { - await getRecoveryKey(); + await getUserRecoveryKeyB64(); return true; } catch (e) { log.warn("Failed to validate key" /*, caller will logout */, e); diff --git a/web/packages/shared/crypto/helpers.ts b/web/packages/shared/crypto/helpers.ts index af5fd71d29..7cb9a5a1c8 100644 --- a/web/packages/shared/crypto/helpers.ts +++ b/web/packages/shared/crypto/helpers.ts @@ -1,11 +1,7 @@ -import { setRecoveryKey } from "ente-accounts/services/user"; import { sharedCryptoWorker } from "ente-base/crypto"; -import log from "ente-base/log"; import { masterKeyFromSession } from "ente-base/session"; import { getData, setData, setLSUser } from "ente-shared/storage/localStorage"; -import { getToken } from "ente-shared/storage/localStorage/helpers"; import { type SessionKey, setKey } from "ente-shared/storage/sessionStorage"; -import { getActualKey } from "ente-shared/user"; import type { KeyAttributes } from "ente-shared/user/types"; const LOGIN_SUB_KEY_LENGTH = 32; @@ -108,75 +104,6 @@ export const saveKeyInSessionStore = async ( } }; -export const encryptWithRecoveryKey = async (data: string) => { - const cryptoWorker = await sharedCryptoWorker(); - const hexRecoveryKey = await getRecoveryKey(); - const recoveryKey = await cryptoWorker.fromHex(hexRecoveryKey); - return cryptoWorker.encryptBoxB64(data, recoveryKey); -}; - -export const getRecoveryKey = async () => { - try { - const cryptoWorker = await sharedCryptoWorker(); - - const keyAttributes: KeyAttributes = getData("keyAttributes"); - const { - recoveryKeyEncryptedWithMasterKey, - recoveryKeyDecryptionNonce, - } = keyAttributes; - const masterKey = await getActualKey(); - let recoveryKey: string; - if (recoveryKeyEncryptedWithMasterKey) { - recoveryKey = await cryptoWorker.decryptB64( - recoveryKeyEncryptedWithMasterKey!, - recoveryKeyDecryptionNonce!, - masterKey, - ); - } else { - recoveryKey = await createNewRecoveryKey(); - } - recoveryKey = await cryptoWorker.toHex(recoveryKey); - return recoveryKey; - } catch (e) { - log.error("getRecoveryKey failed", e); - throw e; - } -}; - -// Used only for legacy users for whom we did not generate recovery keys during -// sign up -async function createNewRecoveryKey() { - const masterKey = await getActualKey(); - const existingAttributes = getData("keyAttributes"); - - const cryptoWorker = await sharedCryptoWorker(); - - const recoveryKey = await cryptoWorker.generateEncryptionKey(); - const encryptedMasterKey = await cryptoWorker.encryptToB64( - masterKey, - recoveryKey, - ); - const encryptedRecoveryKey = await cryptoWorker.encryptToB64( - recoveryKey, - masterKey, - ); - const recoveryKeyAttributes = { - masterKeyEncryptedWithRecoveryKey: encryptedMasterKey.encryptedData, - masterKeyDecryptionNonce: encryptedMasterKey.nonce, - recoveryKeyEncryptedWithMasterKey: encryptedRecoveryKey.encryptedData, - recoveryKeyDecryptionNonce: encryptedRecoveryKey.nonce, - }; - await setRecoveryKey(getToken(), recoveryKeyAttributes); - - const updatedKeyAttributes = Object.assign( - existingAttributes, - recoveryKeyAttributes, - ); - setData("keyAttributes", updatedKeyAttributes); - - return recoveryKey; -} - /** * Decrypt the {@link encryptedChallenge} sent by remote during the delete * account flow ({@link getAccountDeleteChallenge}), returning a value that can