From d6060e119458dde1d0c1e269080b0a1f84ba081a Mon Sep 17 00:00:00 2001 From: Manav Rathi Date: Tue, 3 Jun 2025 12:50:39 +0530 Subject: [PATCH 01/19] Extract --- web/packages/accounts/utils/recovery-key.ts | 49 +++++++++++++++++++++ 1 file changed, 49 insertions(+) create mode 100644 web/packages/accounts/utils/recovery-key.ts diff --git a/web/packages/accounts/utils/recovery-key.ts b/web/packages/accounts/utils/recovery-key.ts new file mode 100644 index 0000000000..15bcd13e90 --- /dev/null +++ b/web/packages/accounts/utils/recovery-key.ts @@ -0,0 +1,49 @@ +import { sharedCryptoWorker } from "ente-base/crypto"; + +// eslint-disable-next-line @typescript-eslint/no-require-imports +const bip39 = require("bip39"); +// mobile client library only supports english. +bip39.setDefaultWordlist("english"); + +/** + * Decrypt the provided data that was encrypted with the user's recovery key + * using the recovery key derived from the provided mnemonic string. + * + * @param recoveryKey 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. + * + * @param encryptedData The data to decrypt. The data should've been encrypted + * using the same recovery key otherwise the decryption would fail. + * + * @param decryptionNonce The nonce that was using encryption. + * + * @returns A base64 string representing the decrypted data. + */ +export const decryptUsingRecoveryKeyMnemonic = async ( + recoveryKey: string, + encryptedData: any, + decryptionNonce: any, +) => { + 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(); + return cryptoWorker.decryptB64( + encryptedData, + decryptionNonce, + await cryptoWorker.fromHex(recoveryKey), + ); +}; From 54b5100e89d66762b95d5efeb1bae35894b29215 Mon Sep 17 00:00:00 2001 From: Manav Rathi Date: Tue, 3 Jun 2025 12:55:36 +0530 Subject: [PATCH 02/19] Use --- web/packages/accounts/pages/recover.tsx | 25 +++------------------ web/packages/accounts/utils/recovery-key.ts | 10 ++++----- 2 files changed, 8 insertions(+), 27 deletions(-) diff --git a/web/packages/accounts/pages/recover.tsx b/web/packages/accounts/pages/recover.tsx index 179edbc155..3b3410d3b7 100644 --- a/web/packages/accounts/pages/recover.tsx +++ b/web/packages/accounts/pages/recover.tsx @@ -7,7 +7,6 @@ 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 log from "ente-base/log"; import SingleInputForm, { type SingleInputFormProps, @@ -22,11 +21,7 @@ import type { KeyAttributes, User } from "ente-shared/user/types"; 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"); +import { decryptUsingRecoveryKeyMnemonic } from "../utils/recovery-key"; const Page: React.FC = () => { const { showMiniDialog } = useBaseContext(); @@ -65,25 +60,11 @@ 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( + const masterKey = await decryptUsingRecoveryKeyMnemonic( keyAttr.masterKeyEncryptedWithRecoveryKey!, keyAttr.masterKeyDecryptionNonce!, - await cryptoWorker.fromHex(recoveryKey), + recoveryKey, ); await saveKeyInSessionStore("encryptionKey", masterKey); await decryptAndStoreToken(keyAttr, masterKey); diff --git a/web/packages/accounts/utils/recovery-key.ts b/web/packages/accounts/utils/recovery-key.ts index 15bcd13e90..bff8084035 100644 --- a/web/packages/accounts/utils/recovery-key.ts +++ b/web/packages/accounts/utils/recovery-key.ts @@ -9,21 +9,21 @@ bip39.setDefaultWordlist("english"); * Decrypt the provided data that was encrypted with the user's recovery key * using the recovery key derived from the provided mnemonic string. * - * @param recoveryKey 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. - * * @param encryptedData The data to decrypt. The data should've been encrypted * using the same recovery key otherwise the decryption would fail. * * @param decryptionNonce The nonce that was using encryption. * + * @param recoveryKey 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 decrypted data. */ export const decryptUsingRecoveryKeyMnemonic = async ( - recoveryKey: string, encryptedData: any, decryptionNonce: any, + recoveryKey: string, ) => { recoveryKey = recoveryKey .trim() From c92141b9dc45b692d1bf65f934e082c813d0f09f Mon Sep 17 00:00:00 2001 From: Manav Rathi Date: Tue, 3 Jun 2025 12:59:01 +0530 Subject: [PATCH 03/19] Use --- web/packages/accounts/pages/recover.tsx | 2 +- .../accounts/pages/two-factor/recover.tsx | 26 +++---------------- 2 files changed, 5 insertions(+), 23 deletions(-) diff --git a/web/packages/accounts/pages/recover.tsx b/web/packages/accounts/pages/recover.tsx index 3b3410d3b7..24ac4c328a 100644 --- a/web/packages/accounts/pages/recover.tsx +++ b/web/packages/accounts/pages/recover.tsx @@ -5,6 +5,7 @@ import { } from "ente-accounts/components/layouts/centered-paper"; import { appHomeRoute, stashRedirect } from "ente-accounts/services/redirect"; import { sendOTT } from "ente-accounts/services/user"; +import { decryptUsingRecoveryKeyMnemonic } from "ente-accounts/utils/recovery-key"; import { LinkButton } from "ente-base/components/LinkButton"; import { useBaseContext } from "ente-base/context"; import log from "ente-base/log"; @@ -21,7 +22,6 @@ import type { KeyAttributes, User } from "ente-shared/user/types"; import { t } from "i18next"; import { useRouter } from "next/router"; import { useEffect, useState } from "react"; -import { decryptUsingRecoveryKeyMnemonic } from "../utils/recovery-key"; const Page: React.FC = () => { const { showMiniDialog } = useBaseContext(); diff --git a/web/packages/accounts/pages/two-factor/recover.tsx b/web/packages/accounts/pages/two-factor/recover.tsx index f0aba30440..f24f2f64b2 100644 --- a/web/packages/accounts/pages/two-factor/recover.tsx +++ b/web/packages/accounts/pages/two-factor/recover.tsx @@ -10,10 +10,10 @@ import { removeTwoFactor, type TwoFactorType, } from "ente-accounts/services/user"; +import { decryptUsingRecoveryKeyMnemonic } from "ente-accounts/utils/recovery-key"; 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 type { B64EncryptionResult } from "ente-base/crypto/libsodium"; import log from "ente-base/log"; import SingleInputForm, { @@ -26,11 +26,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,26 +90,13 @@ 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( + const twoFactorSecret = await decryptUsingRecoveryKeyMnemonic( encryptedData, nonce, - await cryptoWorker.fromHex(recoveryKey), + recoveryKey, ); + const resp = await removeTwoFactor( sessionID!, twoFactorSecret, From 8c99a3e5af2d7e9e5591404d5a5a4bd76e3d8c10 Mon Sep 17 00:00:00 2001 From: Manav Rathi Date: Tue, 3 Jun 2025 13:11:54 +0530 Subject: [PATCH 04/19] Other way --- web/packages/accounts/components/RecoveryKey.tsx | 7 ++----- web/packages/accounts/utils/recovery-key.ts | 14 +++++++++++--- 2 files changed, 13 insertions(+), 8 deletions(-) diff --git a/web/packages/accounts/components/RecoveryKey.tsx b/web/packages/accounts/components/RecoveryKey.tsx index 45722ac170..329ee0c9e2 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"; @@ -19,11 +18,9 @@ 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 { convertRecoveryKeyToMnemonic } from "../utils/recovery-key"; import { CodeBlock } from "./CodeBlock"; -// mobile client library only supports english. -bip39.setDefaultWordlist("english"); - type RecoveryKeyProps = ModalVisibilityProps & { showMiniDialog: (attributes: MiniDialogAttributes) => void; }; @@ -116,7 +113,7 @@ export const RecoveryKey: React.FC = ({ }; const getRecoveryKeyMnemonic = async () => - bip39.entropyToMnemonic(await getRecoveryKey()); + convertRecoveryKeyToMnemonic(await getRecoveryKey()); const downloadRecoveryKeyMnemonic = (key: string) => downloadString(key, "ente-recovery-key.txt"); diff --git a/web/packages/accounts/utils/recovery-key.ts b/web/packages/accounts/utils/recovery-key.ts index bff8084035..1951bd0d87 100644 --- a/web/packages/accounts/utils/recovery-key.ts +++ b/web/packages/accounts/utils/recovery-key.ts @@ -1,8 +1,7 @@ +import * as bip39 from "bip39"; import { sharedCryptoWorker } from "ente-base/crypto"; -// eslint-disable-next-line @typescript-eslint/no-require-imports -const bip39 = require("bip39"); -// mobile client library only supports english. +// Mobile client library only supports English. bip39.setDefaultWordlist("english"); /** @@ -47,3 +46,12 @@ export const decryptUsingRecoveryKeyMnemonic = async ( await cryptoWorker.fromHex(recoveryKey), ); }; + +/** + * Convert the provided recovery key into its BIP-39 mnemonic (24-word) string. + * + * @returns A string containing 24-words that serves as the user visible + * recovery key. + */ +export const convertRecoveryKeyToMnemonic = (recoveryKeyHex: string) => + bip39.entropyToMnemonic(recoveryKeyHex); From 241577739a7ea3f76c944b58745b199c757a06e3 Mon Sep 17 00:00:00 2001 From: Manav Rathi Date: Tue, 3 Jun 2025 13:21:18 +0530 Subject: [PATCH 05/19] doc --- web/docs/dependencies.md | 3 +++ 1 file changed, 3 insertions(+) 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. From 0e0044693c63d96e320777f36ad5f8212e819e9c Mon Sep 17 00:00:00 2001 From: Manav Rathi Date: Tue, 3 Jun 2025 13:51:14 +0530 Subject: [PATCH 06/19] Remove unnecessary roundtrip --- web/packages/accounts/components/RecoveryKey.tsx | 4 ++-- web/packages/accounts/services/passkey.ts | 9 ++------- web/packages/accounts/utils/recovery-key.ts | 14 +++++++++----- web/packages/shared/crypto/helpers.ts | 7 +++---- 4 files changed, 16 insertions(+), 18 deletions(-) diff --git a/web/packages/accounts/components/RecoveryKey.tsx b/web/packages/accounts/components/RecoveryKey.tsx index 329ee0c9e2..5d9f25c7b6 100644 --- a/web/packages/accounts/components/RecoveryKey.tsx +++ b/web/packages/accounts/components/RecoveryKey.tsx @@ -18,7 +18,7 @@ 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 { convertRecoveryKeyToMnemonic } from "../utils/recovery-key"; +import { recoveryKeyB64ToMnemonic } from "../utils/recovery-key"; import { CodeBlock } from "./CodeBlock"; type RecoveryKeyProps = ModalVisibilityProps & { @@ -113,7 +113,7 @@ export const RecoveryKey: React.FC = ({ }; const getRecoveryKeyMnemonic = async () => - convertRecoveryKeyToMnemonic(await getRecoveryKey()); + recoveryKeyB64ToMnemonic(await getRecoveryKey()); const downloadRecoveryKeyMnemonic = (key: string) => downloadString(key, "ente-recovery-key.txt"); diff --git a/web/packages/accounts/services/passkey.ts b/web/packages/accounts/services/passkey.ts index 3e7cd99683..e7175c316a 100644 --- a/web/packages/accounts/services/passkey.ts +++ b/web/packages/accounts/services/passkey.ts @@ -1,6 +1,5 @@ import { TwoFactorAuthorizationResponse } from "ente-accounts/services/user"; import { clientPackageName, isDesktop } from "ente-base/app"; -import { sharedCryptoWorker } from "ente-base/crypto"; import { encryptToB64, generateEncryptionKey, @@ -111,16 +110,12 @@ 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 recoveryKeyB64 = await getRecoveryKey(); const resetSecret = await generateEncryptionKey(); - - const cryptoWorker = await sharedCryptoWorker(); const encryptionResult = await encryptToB64( resetSecret, - await cryptoWorker.fromHex(recoveryKey), + recoveryKeyB64, ); - await configurePasskeyRecovery( resetSecret, encryptionResult.encryptedData, diff --git a/web/packages/accounts/utils/recovery-key.ts b/web/packages/accounts/utils/recovery-key.ts index 1951bd0d87..36eab04786 100644 --- a/web/packages/accounts/utils/recovery-key.ts +++ b/web/packages/accounts/utils/recovery-key.ts @@ -48,10 +48,14 @@ export const decryptUsingRecoveryKeyMnemonic = async ( }; /** - * Convert the provided recovery key into its BIP-39 mnemonic (24-word) string. + * Convert the provided base64 encoded recovery key into its BIP-39 mnemonic. * - * @returns A string containing 24-words that serves as the user visible - * recovery key. + * @param recoveryKeyB64 The base64 encoded recovery key to mnemonize. + * + * @returns A 24-word mnemonic that serves as the user visible recovery key. */ -export const convertRecoveryKeyToMnemonic = (recoveryKeyHex: string) => - bip39.entropyToMnemonic(recoveryKeyHex); +export const recoveryKeyB64ToMnemonic = async (recoveryKeyB64: string) => { + const cryptoWorker = await sharedCryptoWorker(); + const recoveryKeyHex = await cryptoWorker.toHex(recoveryKeyB64); + return bip39.entropyToMnemonic(recoveryKeyHex); +}; diff --git a/web/packages/shared/crypto/helpers.ts b/web/packages/shared/crypto/helpers.ts index af5fd71d29..7d9cdd66b3 100644 --- a/web/packages/shared/crypto/helpers.ts +++ b/web/packages/shared/crypto/helpers.ts @@ -110,9 +110,8 @@ 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); + const recoveryKeyB64 = await getRecoveryKey(); + return cryptoWorker.encryptBoxB64(data, recoveryKeyB64); }; export const getRecoveryKey = async () => { @@ -135,7 +134,6 @@ export const getRecoveryKey = async () => { } else { recoveryKey = await createNewRecoveryKey(); } - recoveryKey = await cryptoWorker.toHex(recoveryKey); return recoveryKey; } catch (e) { log.error("getRecoveryKey failed", e); @@ -145,6 +143,7 @@ export const getRecoveryKey = async () => { // Used only for legacy users for whom we did not generate recovery keys during // sign up +// @returns a new base64 encoded recovery key. async function createNewRecoveryKey() { const masterKey = await getActualKey(); const existingAttributes = getData("keyAttributes"); From 25cadce651779af44ee2324bb8aaca7f3510feea Mon Sep 17 00:00:00 2001 From: Manav Rathi Date: Tue, 3 Jun 2025 13:56:05 +0530 Subject: [PATCH 07/19] Vars --- web/packages/accounts/utils/recovery-key.ts | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/web/packages/accounts/utils/recovery-key.ts b/web/packages/accounts/utils/recovery-key.ts index 36eab04786..9ce52da233 100644 --- a/web/packages/accounts/utils/recovery-key.ts +++ b/web/packages/accounts/utils/recovery-key.ts @@ -22,28 +22,31 @@ bip39.setDefaultWordlist("english"); export const decryptUsingRecoveryKeyMnemonic = async ( encryptedData: any, decryptionNonce: any, - recoveryKey: string, + recoveryKeyMnemonicOrHex: string, ) => { - recoveryKey = recoveryKey + 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 (recoveryKey.indexOf(" ") > 0) { - if (recoveryKey.split(" ").length != 24) { + if (trimmedInput.indexOf(" ") > 0) { + if (trimmedInput.split(" ").length != 24) { throw new Error("recovery code should have 24 words"); } - recoveryKey = bip39.mnemonicToEntropy(recoveryKey); + recoveryKeyHex = bip39.mnemonicToEntropy(trimmedInput); + } else { + recoveryKeyHex = trimmedInput; } const cryptoWorker = await sharedCryptoWorker(); return cryptoWorker.decryptB64( encryptedData, decryptionNonce, - await cryptoWorker.fromHex(recoveryKey), + await cryptoWorker.fromHex(recoveryKeyHex), ); }; From 5df1b12ef5819568e088a9389c1983a37c640a3c Mon Sep 17 00:00:00 2001 From: Manav Rathi Date: Tue, 3 Jun 2025 14:02:14 +0530 Subject: [PATCH 08/19] use new api --- web/packages/accounts/utils/recovery-key.ts | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) diff --git a/web/packages/accounts/utils/recovery-key.ts b/web/packages/accounts/utils/recovery-key.ts index 9ce52da233..5d008ee0a3 100644 --- a/web/packages/accounts/utils/recovery-key.ts +++ b/web/packages/accounts/utils/recovery-key.ts @@ -1,5 +1,5 @@ import * as bip39 from "bip39"; -import { sharedCryptoWorker } from "ente-base/crypto"; +import { fromHex, sharedCryptoWorker, toHex } from "ente-base/crypto"; // Mobile client library only supports English. bip39.setDefaultWordlist("english"); @@ -46,7 +46,7 @@ export const decryptUsingRecoveryKeyMnemonic = async ( return cryptoWorker.decryptB64( encryptedData, decryptionNonce, - await cryptoWorker.fromHex(recoveryKeyHex), + await fromHex(recoveryKeyHex), ); }; @@ -57,8 +57,5 @@ export const decryptUsingRecoveryKeyMnemonic = async ( * * @returns A 24-word mnemonic that serves as the user visible recovery key. */ -export const recoveryKeyB64ToMnemonic = async (recoveryKeyB64: string) => { - const cryptoWorker = await sharedCryptoWorker(); - const recoveryKeyHex = await cryptoWorker.toHex(recoveryKeyB64); - return bip39.entropyToMnemonic(recoveryKeyHex); -}; +export const recoveryKeyB64ToMnemonic = async (recoveryKeyB64: string) => + bip39.entropyToMnemonic(await toHex(recoveryKeyB64)); From 9d9ed0f01f78b8334f4fb076a35fd2eb4f877f9e Mon Sep 17 00:00:00 2001 From: Manav Rathi Date: Tue, 3 Jun 2025 14:09:08 +0530 Subject: [PATCH 09/19] Refactor API --- web/packages/accounts/pages/recover.tsx | 8 +++-- .../accounts/pages/two-factor/recover.tsx | 9 +++--- web/packages/accounts/utils/recovery-key.ts | 29 +++++-------------- 3 files changed, 18 insertions(+), 28 deletions(-) diff --git a/web/packages/accounts/pages/recover.tsx b/web/packages/accounts/pages/recover.tsx index 24ac4c328a..356cf8deb3 100644 --- a/web/packages/accounts/pages/recover.tsx +++ b/web/packages/accounts/pages/recover.tsx @@ -5,9 +5,10 @@ import { } from "ente-accounts/components/layouts/centered-paper"; import { appHomeRoute, stashRedirect } from "ente-accounts/services/redirect"; import { sendOTT } from "ente-accounts/services/user"; -import { decryptUsingRecoveryKeyMnemonic } from "ente-accounts/utils/recovery-key"; +import { recoveryKeyB64FromMnemonic } from "ente-accounts/utils/recovery-key"; import { LinkButton } from "ente-base/components/LinkButton"; import { useBaseContext } from "ente-base/context"; +import { sharedCryptoWorker } from "ente-base/crypto"; import log from "ente-base/log"; import SingleInputForm, { type SingleInputFormProps, @@ -61,10 +62,11 @@ const Page: React.FC = () => { ) => { try { const keyAttr = keyAttributes!; - const masterKey = await decryptUsingRecoveryKeyMnemonic( + const cryptoWorker = await sharedCryptoWorker(); + const masterKey = await cryptoWorker.decryptB64( keyAttr.masterKeyEncryptedWithRecoveryKey!, keyAttr.masterKeyDecryptionNonce!, - recoveryKey, + 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 f24f2f64b2..1d207b2990 100644 --- a/web/packages/accounts/pages/two-factor/recover.tsx +++ b/web/packages/accounts/pages/two-factor/recover.tsx @@ -10,10 +10,11 @@ import { removeTwoFactor, type TwoFactorType, } from "ente-accounts/services/user"; -import { decryptUsingRecoveryKeyMnemonic } from "ente-accounts/utils/recovery-key"; +import { recoveryKeyB64FromMnemonic } from "ente-accounts/utils/recovery-key"; 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 type { B64EncryptionResult } from "ente-base/crypto/libsodium"; import log from "ente-base/log"; import SingleInputForm, { @@ -91,12 +92,12 @@ const Page: React.FC = ({ twoFactorType }) => { ) => { try { const { encryptedData, nonce } = encryptedTwoFactorSecret!; - const twoFactorSecret = await decryptUsingRecoveryKeyMnemonic( + const cryptoWorker = await sharedCryptoWorker(); + const twoFactorSecret = await cryptoWorker.decryptB64( encryptedData, nonce, - recoveryKey, + await recoveryKeyB64FromMnemonic(recoveryKey), ); - const resp = await removeTwoFactor( sessionID!, twoFactorSecret, diff --git a/web/packages/accounts/utils/recovery-key.ts b/web/packages/accounts/utils/recovery-key.ts index 5d008ee0a3..59738da782 100644 --- a/web/packages/accounts/utils/recovery-key.ts +++ b/web/packages/accounts/utils/recovery-key.ts @@ -1,27 +1,19 @@ import * as bip39 from "bip39"; -import { fromHex, sharedCryptoWorker, toHex } from "ente-base/crypto"; +import { fromHex, toHex } from "ente-base/crypto"; // Mobile client library only supports English. bip39.setDefaultWordlist("english"); /** - * Decrypt the provided data that was encrypted with the user's recovery key - * using the recovery key derived from the provided mnemonic string. + * Convert the provided BIP-39 mnemonic string into its base64 representation. * - * @param encryptedData The data to decrypt. The data should've been encrypted - * using the same recovery key otherwise the decryption would fail. + * @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. * - * @param decryptionNonce The nonce that was using encryption. - * - * @param recoveryKey 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 decrypted data. + * @returns A base64 string representing the underlying bytes of the recovery key. */ -export const decryptUsingRecoveryKeyMnemonic = async ( - encryptedData: any, - decryptionNonce: any, +export const recoveryKeyB64FromMnemonic = ( recoveryKeyMnemonicOrHex: string, ) => { const trimmedInput = recoveryKeyMnemonicOrHex @@ -42,12 +34,7 @@ export const decryptUsingRecoveryKeyMnemonic = async ( recoveryKeyHex = trimmedInput; } - const cryptoWorker = await sharedCryptoWorker(); - return cryptoWorker.decryptB64( - encryptedData, - decryptionNonce, - await fromHex(recoveryKeyHex), - ); + return fromHex(recoveryKeyHex); }; /** From 97314b7dc1b644d87ae1b3a2efca2fbe39e3ce1f Mon Sep 17 00:00:00 2001 From: Manav Rathi Date: Tue, 3 Jun 2025 14:13:48 +0530 Subject: [PATCH 10/19] conv --- web/packages/accounts/pages/recover.tsx | 11 ++++++----- web/packages/accounts/pages/two-factor/recover.tsx | 8 +++----- 2 files changed, 9 insertions(+), 10 deletions(-) diff --git a/web/packages/accounts/pages/recover.tsx b/web/packages/accounts/pages/recover.tsx index 356cf8deb3..e0ed5eced5 100644 --- a/web/packages/accounts/pages/recover.tsx +++ b/web/packages/accounts/pages/recover.tsx @@ -8,7 +8,7 @@ import { sendOTT } from "ente-accounts/services/user"; import { recoveryKeyB64FromMnemonic } from "ente-accounts/utils/recovery-key"; 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, @@ -62,10 +62,11 @@ const Page: React.FC = () => { ) => { try { const keyAttr = keyAttributes!; - const cryptoWorker = await sharedCryptoWorker(); - const masterKey = await cryptoWorker.decryptB64( - keyAttr.masterKeyEncryptedWithRecoveryKey!, - keyAttr.masterKeyDecryptionNonce!, + const masterKey = await decryptBoxB64( + { + encryptedData: keyAttr.masterKeyEncryptedWithRecoveryKey!, + nonce: keyAttr.masterKeyDecryptionNonce!, + }, await recoveryKeyB64FromMnemonic(recoveryKey), ); await saveKeyInSessionStore("encryptionKey", masterKey); diff --git a/web/packages/accounts/pages/two-factor/recover.tsx b/web/packages/accounts/pages/two-factor/recover.tsx index 1d207b2990..90d93ab23c 100644 --- a/web/packages/accounts/pages/two-factor/recover.tsx +++ b/web/packages/accounts/pages/two-factor/recover.tsx @@ -14,7 +14,7 @@ import { recoveryKeyB64FromMnemonic } from "ente-accounts/utils/recovery-key"; 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, { @@ -92,10 +92,8 @@ const Page: React.FC = ({ twoFactorType }) => { ) => { try { const { encryptedData, nonce } = encryptedTwoFactorSecret!; - const cryptoWorker = await sharedCryptoWorker(); - const twoFactorSecret = await cryptoWorker.decryptB64( - encryptedData, - nonce, + const twoFactorSecret = await decryptBoxB64( + { encryptedData, nonce }, await recoveryKeyB64FromMnemonic(recoveryKey), ); const resp = await removeTwoFactor( From 7cdef46385a017fc3c948b72f07dc9eaceff4a8f Mon Sep 17 00:00:00 2001 From: Manav Rathi Date: Tue, 3 Jun 2025 14:42:33 +0530 Subject: [PATCH 11/19] Update --- web/packages/accounts/services/user.ts | 37 +++++++++++++++++--------- web/packages/shared/crypto/helpers.ts | 32 +++++++++++++--------- 2 files changed, 43 insertions(+), 26 deletions(-) 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/shared/crypto/helpers.ts b/web/packages/shared/crypto/helpers.ts index 7d9cdd66b3..e57ae944d7 100644 --- a/web/packages/shared/crypto/helpers.ts +++ b/web/packages/shared/crypto/helpers.ts @@ -1,9 +1,8 @@ -import { setRecoveryKey } from "ente-accounts/services/user"; +import { putUserRecoveryKeyAttributes } 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"; @@ -141,10 +140,16 @@ export const getRecoveryKey = async () => { } }; -// Used only for legacy users for whom we did not generate recovery keys during -// sign up -// @returns a new base64 encoded recovery key. -async function createNewRecoveryKey() { +/** + * 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 () => { const masterKey = await getActualKey(); const existingAttributes = getData("keyAttributes"); @@ -159,22 +164,23 @@ async function createNewRecoveryKey() { 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); + await putUserRecoveryKeyAttributes(recoveryKeyAttributes); + + setData("keyAttributes", { + ...existingAttributes, + ...recoveryKeyAttributes, + }); return recoveryKey; -} +}; /** * Decrypt the {@link encryptedChallenge} sent by remote during the delete From 78669a855039297dde1218fd174b0b020cffc982 Mon Sep 17 00:00:00 2001 From: Manav Rathi Date: Tue, 3 Jun 2025 15:18:15 +0530 Subject: [PATCH 12/19] Match mobile and architecture docs From libsodium source crypto_secretbox_keygen(unsigned char k[crypto_secretbox_KEYBYTES]) { randombytes_buf(k, crypto_secretbox_KEYBYTES); } crypto_kdf_keygen(unsigned char k[crypto_kdf_KEYBYTES]) { randombytes_buf(k, crypto_kdf_KEYBYTES); } --- web/packages/base/crypto/libsodium.ts | 34 ++++++++++++++++++++++----- 1 file changed, 28 insertions(+), 6 deletions(-) diff --git a/web/packages/base/crypto/libsodium.ts b/web/packages/base/crypto/libsodium.ts index 62d8fe1860..89801a6f90 100644 --- a/web/packages/base/crypto/libsodium.ts +++ b/web/packages/base/crypto/libsodium.ts @@ -130,12 +130,34 @@ 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 generateKey = async () => { + await sodium.ready; + return toB64(sodium.crypto_secretbox_keygen()); +}; + +/** Deprecated, use generateKey */ export const generateBoxKey = async () => { await sodium.ready; return toB64(sodium.crypto_secretbox_keygen()); @@ -863,9 +885,9 @@ export const deriveInteractiveKey = async ( return { key, opsLimit, memLimit }; }; +/** Deprecated, use generateKey */ export async function generateEncryptionKey() { - await sodium.ready; - return await toB64(sodium.crypto_kdf_keygen()); + return generateKey(); } export async function generateSaltToDeriveKey() { From 48dc3a6b6914ff8737ebca439bcfc3f04353e474 Mon Sep 17 00:00:00 2001 From: Manav Rathi Date: Tue, 3 Jun 2025 15:38:23 +0530 Subject: [PATCH 13/19] Redirect --- web/packages/base/crypto/ente-impl.ts | 2 +- web/packages/base/crypto/index.ts | 12 +++++++----- web/packages/base/crypto/libsodium.ts | 6 ------ web/packages/base/crypto/worker.ts | 2 +- 4 files changed, 9 insertions(+), 13 deletions(-) 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 89801a6f90..0c3d3b9779 100644 --- a/web/packages/base/crypto/libsodium.ts +++ b/web/packages/base/crypto/libsodium.ts @@ -157,12 +157,6 @@ export const generateKey = async () => { return toB64(sodium.crypto_secretbox_keygen()); }; -/** Deprecated, use generateKey */ -export const generateBoxKey = async () => { - await sodium.ready; - return toB64(sodium.crypto_secretbox_keygen()); -}; - /** * Generate a new key for use with the *Blob or *Stream encryption functions, * and return its base64 string representation. diff --git a/web/packages/base/crypto/worker.ts b/web/packages/base/crypto/worker.ts index 8e6db10341..95924655c2 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; From f4ff63ec0a31095d18fee1e2bcf59b3af87d5cae Mon Sep 17 00:00:00 2001 From: Manav Rathi Date: Tue, 3 Jun 2025 15:41:47 +0530 Subject: [PATCH 14/19] Swap --- web/apps/photos/src/services/collectionService.ts | 2 +- web/packages/accounts/services/passkey.ts | 8 +++----- web/packages/accounts/services/srp.ts | 4 ++-- web/packages/base/crypto/libsodium.ts | 5 ----- web/packages/base/crypto/worker.ts | 4 ---- web/packages/shared/crypto/helpers.ts | 2 +- 6 files changed, 7 insertions(+), 18 deletions(-) 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/packages/accounts/services/passkey.ts b/web/packages/accounts/services/passkey.ts index e7175c316a..6223ea7497 100644 --- a/web/packages/accounts/services/passkey.ts +++ b/web/packages/accounts/services/passkey.ts @@ -1,9 +1,7 @@ import { TwoFactorAuthorizationResponse } from "ente-accounts/services/user"; import { clientPackageName, isDesktop } from "ente-base/app"; -import { - encryptToB64, - generateEncryptionKey, -} from "ente-base/crypto/libsodium"; +import { generateKey } from "ente-base/crypto"; +import { encryptToB64 } from "ente-base/crypto/libsodium"; import { authenticatedRequestHeaders, ensureOk, @@ -111,7 +109,7 @@ export const openAccountsManagePasskeysPage = async () => { // If not, enable it for them by creating the necessary recovery // information to prevent them from getting locked out. const recoveryKeyB64 = await getRecoveryKey(); - const resetSecret = await generateEncryptionKey(); + const resetSecret = await generateKey(); const encryptionResult = await encryptToB64( resetSecret, recoveryKeyB64, 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/base/crypto/libsodium.ts b/web/packages/base/crypto/libsodium.ts index 0c3d3b9779..1f58fccbab 100644 --- a/web/packages/base/crypto/libsodium.ts +++ b/web/packages/base/crypto/libsodium.ts @@ -879,11 +879,6 @@ export const deriveInteractiveKey = async ( return { key, opsLimit, memLimit }; }; -/** Deprecated, use generateKey */ -export async function generateEncryptionKey() { - return generateKey(); -} - 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 95924655c2..720b993d1d 100644 --- a/web/packages/base/crypto/worker.ts +++ b/web/packages/base/crypto/worker.ts @@ -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/shared/crypto/helpers.ts b/web/packages/shared/crypto/helpers.ts index e57ae944d7..21ab4357d2 100644 --- a/web/packages/shared/crypto/helpers.ts +++ b/web/packages/shared/crypto/helpers.ts @@ -155,7 +155,7 @@ const createNewRecoveryKey = async () => { const cryptoWorker = await sharedCryptoWorker(); - const recoveryKey = await cryptoWorker.generateEncryptionKey(); + const recoveryKey = await cryptoWorker.generateKey(); const encryptedMasterKey = await cryptoWorker.encryptToB64( masterKey, recoveryKey, From 7112e96c75b9816cf7b3796db59381ba1b529bfe Mon Sep 17 00:00:00 2001 From: Manav Rathi Date: Tue, 3 Jun 2025 15:46:15 +0530 Subject: [PATCH 15/19] Swap --- web/packages/shared/crypto/helpers.ts | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/web/packages/shared/crypto/helpers.ts b/web/packages/shared/crypto/helpers.ts index 21ab4357d2..db434c0920 100644 --- a/web/packages/shared/crypto/helpers.ts +++ b/web/packages/shared/crypto/helpers.ts @@ -150,17 +150,16 @@ export const getRecoveryKey = async () => { * @returns a new base64 encoded recovery key. */ const createNewRecoveryKey = async () => { - const masterKey = await getActualKey(); + const masterKey = await masterKeyFromSession(); const existingAttributes = getData("keyAttributes"); const cryptoWorker = await sharedCryptoWorker(); - const recoveryKey = await cryptoWorker.generateKey(); - const encryptedMasterKey = await cryptoWorker.encryptToB64( + const encryptedMasterKey = await cryptoWorker.encryptBoxB64( masterKey, recoveryKey, ); - const encryptedRecoveryKey = await cryptoWorker.encryptToB64( + const encryptedRecoveryKey = await cryptoWorker.encryptBoxB64( recoveryKey, masterKey, ); From f0aac696caa8849a120ce7ebfff3a97425ecb41a Mon Sep 17 00:00:00 2001 From: Manav Rathi Date: Tue, 3 Jun 2025 15:50:43 +0530 Subject: [PATCH 16/19] Conv --- web/packages/shared/crypto/helpers.ts | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/web/packages/shared/crypto/helpers.ts b/web/packages/shared/crypto/helpers.ts index db434c0920..646f9dff6b 100644 --- a/web/packages/shared/crypto/helpers.ts +++ b/web/packages/shared/crypto/helpers.ts @@ -4,7 +4,6 @@ import log from "ente-base/log"; import { masterKeyFromSession } from "ente-base/session"; import { getData, setData, setLSUser } from "ente-shared/storage/localStorage"; 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; @@ -122,16 +121,19 @@ export const getRecoveryKey = async () => { recoveryKeyEncryptedWithMasterKey, recoveryKeyDecryptionNonce, } = keyAttributes; - const masterKey = await getActualKey(); + const masterKey = await masterKeyFromSession(); + let recoveryKey: string; if (recoveryKeyEncryptedWithMasterKey) { - recoveryKey = await cryptoWorker.decryptB64( - recoveryKeyEncryptedWithMasterKey!, - recoveryKeyDecryptionNonce!, + recoveryKey = await cryptoWorker.decryptBoxB64( + { + encryptedData: recoveryKeyEncryptedWithMasterKey!, + nonce: recoveryKeyDecryptionNonce!, + }, masterKey, ); } else { - recoveryKey = await createNewRecoveryKey(); + recoveryKey = await createNewRecoveryKey(masterKey); } return recoveryKey; } catch (e) { @@ -149,8 +151,7 @@ export const getRecoveryKey = async () => { * * @returns a new base64 encoded recovery key. */ -const createNewRecoveryKey = async () => { - const masterKey = await masterKeyFromSession(); +const createNewRecoveryKey = async (masterKey: Uint8Array) => { const existingAttributes = getData("keyAttributes"); const cryptoWorker = await sharedCryptoWorker(); From 2d7689a6dabad5e02624955631ce697b39b720d6 Mon Sep 17 00:00:00 2001 From: Manav Rathi Date: Tue, 3 Jun 2025 15:56:27 +0530 Subject: [PATCH 17/19] Consolidate and move --- .../accounts/components/RecoveryKey.tsx | 8 +- web/packages/accounts/services/passkey.ts | 10 +- .../accounts/services/recovery-key.ts | 122 ++++++++++++++++++ web/packages/accounts/utils/recovery-key.ts | 48 ------- .../new/photos/components/gallery/helpers.ts | 4 +- web/packages/shared/crypto/helpers.ts | 75 +---------- 6 files changed, 135 insertions(+), 132 deletions(-) create mode 100644 web/packages/accounts/services/recovery-key.ts delete mode 100644 web/packages/accounts/utils/recovery-key.ts diff --git a/web/packages/accounts/components/RecoveryKey.tsx b/web/packages/accounts/components/RecoveryKey.tsx index 5d9f25c7b6..9e1b5da3a0 100644 --- a/web/packages/accounts/components/RecoveryKey.tsx +++ b/web/packages/accounts/components/RecoveryKey.tsx @@ -15,10 +15,12 @@ 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 { recoveryKeyB64ToMnemonic } from "../utils/recovery-key"; +import { + getUserRecoveryKeyB64, + recoveryKeyB64ToMnemonic, +} from "../services/recovery-key"; import { CodeBlock } from "./CodeBlock"; type RecoveryKeyProps = ModalVisibilityProps & { @@ -113,7 +115,7 @@ export const RecoveryKey: React.FC = ({ }; const getRecoveryKeyMnemonic = async () => - recoveryKeyB64ToMnemonic(await getRecoveryKey()); + recoveryKeyB64ToMnemonic(await getUserRecoveryKeyB64()); const downloadRecoveryKeyMnemonic = (key: string) => downloadString(key, "ente-recovery-key.txt"); diff --git a/web/packages/accounts/services/passkey.ts b/web/packages/accounts/services/passkey.ts index 6223ea7497..34f3ef769c 100644 --- a/web/packages/accounts/services/passkey.ts +++ b/web/packages/accounts/services/passkey.ts @@ -1,7 +1,6 @@ import { TwoFactorAuthorizationResponse } from "ente-accounts/services/user"; import { clientPackageName, isDesktop } from "ente-base/app"; -import { generateKey } from "ente-base/crypto"; -import { encryptToB64 } from "ente-base/crypto/libsodium"; +import { encryptBoxB64, generateKey } from "ente-base/crypto"; import { authenticatedRequestHeaders, ensureOk, @@ -10,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"; /** @@ -108,11 +107,10 @@ 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 recoveryKeyB64 = await getRecoveryKey(); const resetSecret = await generateKey(); - const encryptionResult = await encryptToB64( + const encryptionResult = await encryptBoxB64( resetSecret, - recoveryKeyB64, + await getUserRecoveryKeyB64(), ); await configurePasskeyRecovery( resetSecret, 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/utils/recovery-key.ts b/web/packages/accounts/utils/recovery-key.ts deleted file mode 100644 index 59738da782..0000000000 --- a/web/packages/accounts/utils/recovery-key.ts +++ /dev/null @@ -1,48 +0,0 @@ -import * as bip39 from "bip39"; -import { fromHex, toHex } from "ente-base/crypto"; - -// 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)); 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 646f9dff6b..0235f739e0 100644 --- a/web/packages/shared/crypto/helpers.ts +++ b/web/packages/shared/crypto/helpers.ts @@ -1,6 +1,5 @@ -import { putUserRecoveryKeyAttributes } from "ente-accounts/services/user"; +import { getUserRecoveryKeyB64 } from "ente-accounts/services/recovery-key"; 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 { type SessionKey, setKey } from "ente-shared/storage/sessionStorage"; @@ -108,80 +107,10 @@ export const saveKeyInSessionStore = async ( export const encryptWithRecoveryKey = async (data: string) => { const cryptoWorker = await sharedCryptoWorker(); - const recoveryKeyB64 = await getRecoveryKey(); + const recoveryKeyB64 = await getUserRecoveryKeyB64(); return cryptoWorker.encryptBoxB64(data, recoveryKeyB64); }; -export const getRecoveryKey = async () => { - try { - const cryptoWorker = await sharedCryptoWorker(); - - const keyAttributes: KeyAttributes = getData("keyAttributes"); - const { - recoveryKeyEncryptedWithMasterKey, - recoveryKeyDecryptionNonce, - } = keyAttributes; - const masterKey = await masterKeyFromSession(); - - let recoveryKey: string; - if (recoveryKeyEncryptedWithMasterKey) { - recoveryKey = await cryptoWorker.decryptBoxB64( - { - encryptedData: recoveryKeyEncryptedWithMasterKey!, - nonce: recoveryKeyDecryptionNonce!, - }, - masterKey, - ); - } else { - recoveryKey = await createNewRecoveryKey(masterKey); - } - return recoveryKey; - } catch (e) { - log.error("getRecoveryKey failed", e); - throw e; - } -}; - -/** - * 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; -}; - /** * Decrypt the {@link encryptedChallenge} sent by remote during the delete * account flow ({@link getAccountDeleteChallenge}), returning a value that can From 6ce7921a16a3f641cbc53329172ff75a648a768d Mon Sep 17 00:00:00 2001 From: Manav Rathi Date: Tue, 3 Jun 2025 16:08:42 +0530 Subject: [PATCH 18/19] Inline --- web/packages/accounts/pages/recover.tsx | 2 +- .../accounts/pages/two-factor/recover.tsx | 2 +- web/packages/accounts/pages/two-factor/setup.tsx | 15 ++++++++------- web/packages/shared/crypto/helpers.ts | 7 ------- 4 files changed, 10 insertions(+), 16 deletions(-) diff --git a/web/packages/accounts/pages/recover.tsx b/web/packages/accounts/pages/recover.tsx index e0ed5eced5..7d4569795d 100644 --- a/web/packages/accounts/pages/recover.tsx +++ b/web/packages/accounts/pages/recover.tsx @@ -3,9 +3,9 @@ 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 { recoveryKeyB64FromMnemonic } from "ente-accounts/utils/recovery-key"; import { LinkButton } from "ente-base/components/LinkButton"; import { useBaseContext } from "ente-base/context"; import { decryptBoxB64 } from "ente-base/crypto"; diff --git a/web/packages/accounts/pages/two-factor/recover.tsx b/web/packages/accounts/pages/two-factor/recover.tsx index 90d93ab23c..df55ea2682 100644 --- a/web/packages/accounts/pages/two-factor/recover.tsx +++ b/web/packages/accounts/pages/two-factor/recover.tsx @@ -5,12 +5,12 @@ import { AccountsPageFooter, AccountsPageTitle, } from "ente-accounts/components/layouts/centered-paper"; +import { recoveryKeyB64FromMnemonic } from "ente-accounts/services/recovery-key"; import { recoverTwoFactor, removeTwoFactor, type TwoFactorType, } from "ente-accounts/services/user"; -import { recoveryKeyB64FromMnemonic } from "ente-accounts/utils/recovery-key"; import { LinkButton } from "ente-base/components/LinkButton"; import type { MiniDialogAttributes } from "ente-base/components/MiniDialog"; import { useBaseContext } from "ente-base/context"; diff --git a/web/packages/accounts/pages/two-factor/setup.tsx b/web/packages/accounts/pages/two-factor/setup.tsx index ec61ccacfe..d97f48bf31 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 encryptedBox = await encryptBoxB64( + twoFactorSecret!.secretCode, + await getUserRecoveryKeyB64(), + ); await enableTwoFactor({ code: otp, - encryptedTwoFactorSecret, - twoFactorSecretDecryptionNonce, + encryptedTwoFactorSecret: encryptedBox.encryptedData, + twoFactorSecretDecryptionNonce: encryptedBox.nonce, }); await setLSUser({ ...getData("user"), isTwoFactorEnabled: true }); await router.push(appHomeRoute); diff --git a/web/packages/shared/crypto/helpers.ts b/web/packages/shared/crypto/helpers.ts index 0235f739e0..7cb9a5a1c8 100644 --- a/web/packages/shared/crypto/helpers.ts +++ b/web/packages/shared/crypto/helpers.ts @@ -1,4 +1,3 @@ -import { getUserRecoveryKeyB64 } from "ente-accounts/services/recovery-key"; import { sharedCryptoWorker } from "ente-base/crypto"; import { masterKeyFromSession } from "ente-base/session"; import { getData, setData, setLSUser } from "ente-shared/storage/localStorage"; @@ -105,12 +104,6 @@ export const saveKeyInSessionStore = async ( } }; -export const encryptWithRecoveryKey = async (data: string) => { - const cryptoWorker = await sharedCryptoWorker(); - const recoveryKeyB64 = await getUserRecoveryKeyB64(); - return cryptoWorker.encryptBoxB64(data, recoveryKeyB64); -}; - /** * Decrypt the {@link encryptedChallenge} sent by remote during the delete * account flow ({@link getAccountDeleteChallenge}), returning a value that can From 113b82045186e7ca3d063b8786f7e1211b766517 Mon Sep 17 00:00:00 2001 From: Manav Rathi Date: Tue, 3 Jun 2025 16:54:17 +0530 Subject: [PATCH 19/19] Same name --- web/packages/accounts/pages/two-factor/setup.tsx | 6 +++--- web/packages/accounts/services/passkey.ts | 6 +++--- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/web/packages/accounts/pages/two-factor/setup.tsx b/web/packages/accounts/pages/two-factor/setup.tsx index d97f48bf31..2f128d35da 100644 --- a/web/packages/accounts/pages/two-factor/setup.tsx +++ b/web/packages/accounts/pages/two-factor/setup.tsx @@ -27,14 +27,14 @@ const Page: React.FC = () => { }, []); const handleSubmit = async (otp: string) => { - const encryptedBox = await encryptBoxB64( + const box = await encryptBoxB64( twoFactorSecret!.secretCode, await getUserRecoveryKeyB64(), ); await enableTwoFactor({ code: otp, - encryptedTwoFactorSecret: encryptedBox.encryptedData, - twoFactorSecretDecryptionNonce: encryptedBox.nonce, + 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 34f3ef769c..e2a749a93e 100644 --- a/web/packages/accounts/services/passkey.ts +++ b/web/packages/accounts/services/passkey.ts @@ -108,14 +108,14 @@ export const openAccountsManagePasskeysPage = async () => { // If not, enable it for them by creating the necessary recovery // information to prevent them from getting locked out. const resetSecret = await generateKey(); - const encryptionResult = await encryptBoxB64( + const box = await encryptBoxB64( resetSecret, await getUserRecoveryKeyB64(), ); await configurePasskeyRecovery( resetSecret, - encryptionResult.encryptedData, - encryptionResult.nonce, + box.encryptedData, + box.nonce, ); }