From 37c0fa1cd6ac42dc7fe11da5f8164e6f5282c031 Mon Sep 17 00:00:00 2001 From: Manav Rathi Date: Fri, 6 Jun 2025 15:37:33 +0530 Subject: [PATCH] Scaffold more infra --- web/packages/accounts/utils/helpers.ts | 13 +++-- web/packages/base/session.ts | 67 ++++++++++++++++++++++++++ 2 files changed, 76 insertions(+), 4 deletions(-) diff --git a/web/packages/accounts/utils/helpers.ts b/web/packages/accounts/utils/helpers.ts index b2b9ae45af..3220cb0698 100644 --- a/web/packages/accounts/utils/helpers.ts +++ b/web/packages/accounts/utils/helpers.ts @@ -39,10 +39,15 @@ export async function decryptAndStoreToken( } } -// We encrypt the masterKey, with an intermediate key derived from the -// passphrase (with Interactive mem and ops limits) to avoid saving it to local -// storage in plain text. This means that on the web user will always have to -// enter their passphrase to access their masterKey. +/** + * Encrypt the user's masterKey with an intermediate kek (key encryption key) + * derived from the passphrase (with interactive mem and ops limits) to avoid + * saving it to local storage in plain text. + * + * This means that on the web user will always have to enter their passphrase to + * access their masterKey when repopening the app in a new tab (on desktop we + * can use OS storage, see [Note: Safe storage and interactive KEK attributes]). + */ export async function generateAndSaveIntermediateKeyAttributes( passphrase: string, existingKeyAttributes: KeyAttributes, diff --git a/web/packages/base/session.ts b/web/packages/base/session.ts index 256781de46..13539e49fd 100644 --- a/web/packages/base/session.ts +++ b/web/packages/base/session.ts @@ -1,5 +1,9 @@ +import { getToken } from "ente-shared/storage/localStorage/helpers"; import { z } from "zod/v4"; import { decryptBox, decryptBoxBytes, encryptBox, generateKey } from "./crypto"; +import { isDevBuild } from "./env"; +import log from "./log"; +import { getAuthToken } from "./token"; /** * Remove all data stored in session storage (data tied to the browser tab). @@ -100,6 +104,69 @@ const saveKeyInSessionStore = async (keyName: string, keyData: string) => { ); }; +/** + * If we're running in the context of the desktop app, then read the master key + * from the OS safe storage and put it into the session storage (if it is not + * already present there). + * + * [Note: Safe storage and interactive KEK attributes] + * + * In the electron app we have the option of using the OS's safe storage (if + * available) to store the master key so that the user does not have to reenter + * their password each time they open the app. + * + * Such an ability is not present on browsers currently, so we need to ask the + * user for their password to derive the KEK for decrypting their master key + * each time they open the app in a new time (See: [Note: Key encryption key]). + * + * However, the default KEK parameters are not suitable for such frequent + * interactive usage. So for the user's convenience, we also derive an new (so + * called "intermediate") KEK using parameters suitable for interactive usage. + * This KEK is not saved to remote, it is only maintained locally. + * + * In either case, eventually we want the encrypted key to be available in the + * session for decrypting the user's files etc. In the web case, the page where + * the user reenters their password will put it there, while on desktop + * (assuming the key has already been saved to the OS safe storage), this + * {@link updateSessionFromElectronSafeStorageIfNeeded} function will do it. + */ +export const updateSessionFromElectronSafeStorageIfNeeded = async () => { + const electron = globalThis.electron; + if (!electron) return; + + if (haveCredentialsInSession()) return; + + let masterKey: string | undefined; + try { + masterKey = await electron.masterKeyB64(); + } catch (e) { + log.error("Failed to read master key from safe storage", e); + } + if (masterKey) { + // Do not use `saveMasterKeyInSessionStore`, that will (unnecessarily) + // overwrite the OS safe storage again. + await saveKeyInSessionStore("encryptionKey", masterKey); + } +}; + +/** + * Return true if we both have the user's master key in session storage, and + * their auth token in KV DB. + */ +export const haveAuthenticatedSession = async () => { + if (!(await masterKeyFromSession())) return false; + const lsToken = getToken(); + const kvToken = await getAuthToken(); + // TODO: To avoid changing old behaviour, this currently relies on the token + // from local storage. Both should be the same though, so it throws an error + // on dev build (tag: Migration). + if (isDevBuild) { + if (lsToken != kvToken) + throw new Error("Local storage and indexed DB mismatch"); + } + return !!lsToken; +}; + /** * Return the decrypted user's key encryption key ("kek") from session storage * if present, otherwise return `undefined`.