From 462adf74292096e2c507b4f6de0d6f49ec312ca4 Mon Sep 17 00:00:00 2001 From: Manav Rathi Date: Fri, 16 Aug 2024 20:25:37 +0530 Subject: [PATCH 01/36] Nomenclature --- .../new/photos/services/user-entity.ts | 55 ++++++++++--------- 1 file changed, 28 insertions(+), 27 deletions(-) diff --git a/web/packages/new/photos/services/user-entity.ts b/web/packages/new/photos/services/user-entity.ts index 67f92c77a8..6ffe72a81b 100644 --- a/web/packages/new/photos/services/user-entity.ts +++ b/web/packages/new/photos/services/user-entity.ts @@ -52,32 +52,33 @@ export type EntityType = const defaultDiffLimit = 500; /** - * A generic user entity. + * An entry in the user entity diff. * - * This is an intermediate step, usually what we really want is a version - * of this with the {@link data} parsed to the specific type of JSON object - * expected to be associated with this entity type. + * Each change either contains the latest data associated with a particular user + * entity that has been created or updated, or indicates that the corresponding + * entity has been deleted. */ -interface UserEntity { +interface UserEntityChange { /** - * A UUID or nanoid for the entity. + * A UUID or nanoid of the entity. */ id: string; /** - * Arbitrary data associated with the entity. The format of this data is - * specific to each entity type. + * Arbitrary (decrypted) data associated with the entity. The format of this + * data is specific to each entity type. * * This will not be present for entities that have been deleted on remote. */ data: Uint8Array | undefined; /** - * Epoch microseconds denoting when this entity was created or last updated. + * Epoch microseconds denoting when this entity was last changed (created or + * updated or deleted). */ updatedAt: number; } -/** Zod schema for {@link RemoteUserEntity} */ -const RemoteUserEntity = z.object({ +/** Zod schema for {@link RemoteUserEntityChange} */ +const RemoteUserEntityChange = z.object({ id: z.string(), /** * Base64 string containing the encrypted contents of the entity. @@ -96,11 +97,11 @@ const RemoteUserEntity = z.object({ }); /** An item in the user entity diff response we get from remote. */ -type RemoteUserEntity = z.infer; +type RemoteUserEntity = z.infer; /** - * Fetch the next batch of user entities of the given type that have been - * created or updated since the given time. + * Fetch the next set of changes (upsert or deletion) to user entities of the + * given type since the given time. * * @param type The type of the entities to fetch. * @@ -114,26 +115,26 @@ type RemoteUserEntity = z.infer; * [Note: Diff response will have at most one entry for an id] * * Unlike git diffs which track all changes, the diffs we get from remote are - * guaranteed to contain only one entry (upsert or delete) for particular Ente + * guaranteed to contain only one entry (upsert or delete) for a particular Ente * object. This holds true irrespective of the diff limit. * - * For example, in the user entity diff response, it is guaranteed that there - * will only be at max one entry for a particular entity id. The entry will have - * no data to indicate that the corresponding entity was deleted. Otherwise, - * when the data is present, it is taken as the creation of a new entity or the - * updation of an existing one. + * For example, in a user entity diff, it is guaranteed that there will only be + * at max one entry for a particular entity id. The entry will have no data to + * indicate that the corresponding entity was deleted. Otherwise, when the data + * is present, it is taken as the creation of a new entity or the updation of an + * existing one. * - * This behaviour comes from how remote stores the underlying, e.g., entities. A + * This behaviour comes from how remote stores the underlying, say, entities. A * diff returns just entities whose updation times greater than the provided * since time (limited to the given diff limit). So there will be at most one * row for a particular entity id. And if that entity has been deleted, then the - * row will be a tombstone so data will be not be present. + * row will be a tombstone, so data be absent. */ -export const userEntityDiff = async ( +const userEntityDiff = async ( type: EntityType, sinceTime: number, entityKeyB64: string, -): Promise => { +): Promise => { const parse = async ({ id, encryptedData, @@ -166,10 +167,10 @@ export const userEntityDiff = async ( headers: await authenticatedRequestHeaders(), }); ensureOk(res); - const entities = z - .object({ diff: z.array(RemoteUserEntity) }) + const diff = z + .object({ diff: z.array(RemoteUserEntityChange) }) .parse(await res.json()).diff; - return Promise.all(entities.map(parse)); + return Promise.all(diff.map(parse)); }; /** From bc192e5b006acd1ee8099fd6032f14a2c8c73f77 Mon Sep 17 00:00:00 2001 From: Manav Rathi Date: Fri, 16 Aug 2024 20:28:51 +0530 Subject: [PATCH 02/36] Hold remote to the delete invariant --- .../new/photos/services/user-entity.ts | 35 ++++++++----------- 1 file changed, 15 insertions(+), 20 deletions(-) diff --git a/web/packages/new/photos/services/user-entity.ts b/web/packages/new/photos/services/user-entity.ts index 6ffe72a81b..312bd22f55 100644 --- a/web/packages/new/photos/services/user-entity.ts +++ b/web/packages/new/photos/services/user-entity.ts @@ -4,6 +4,7 @@ import { authenticatedRequestHeaders, ensureOk, HTTPError } from "@/base/http"; import { getKV, getKVN, setKV } from "@/base/kv"; import { apiURL } from "@/base/origins"; import { usersEncryptionKeyB64 } from "@/base/session-store"; +import { ensure } from "@/utils/ensure"; import { nullToUndefined } from "@/utils/transform"; import { z } from "zod"; import { gunzip } from "./gzip"; @@ -77,7 +78,9 @@ interface UserEntityChange { updatedAt: number; } -/** Zod schema for {@link RemoteUserEntityChange} */ +/** + * Zod schema for a item in the user entity diff. + */ const RemoteUserEntityChange = z.object({ id: z.string(), /** @@ -96,9 +99,6 @@ const RemoteUserEntityChange = z.object({ updatedAt: z.number(), }); -/** An item in the user entity diff response we get from remote. */ -type RemoteUserEntity = z.infer; - /** * Fetch the next set of changes (upsert or deletion) to user entities of the * given type since the given time. @@ -135,21 +135,6 @@ const userEntityDiff = async ( sinceTime: number, entityKeyB64: string, ): Promise => { - const parse = async ({ - id, - encryptedData, - header, - isDeleted, - updatedAt, - }: RemoteUserEntity) => ({ - id, - data: - encryptedData && header && !isDeleted - ? await decrypt(encryptedData, header) - : undefined, - updatedAt, - }); - const decrypt = (encryptedDataB64: string, decryptionHeaderB64: string) => decryptAssociatedDataB64({ encryptedDataB64, @@ -170,7 +155,17 @@ const userEntityDiff = async ( const diff = z .object({ diff: z.array(RemoteUserEntityChange) }) .parse(await res.json()).diff; - return Promise.all(diff.map(parse)); + return Promise.all( + diff.map( + async ({ id, encryptedData, header, isDeleted, updatedAt }) => ({ + id, + data: !isDeleted + ? await decrypt(ensure(encryptedData), ensure(header)) + : undefined, + updatedAt, + }), + ), + ); }; /** From 3962f3a1332270d86e07976cc8c272691e65e27c Mon Sep 17 00:00:00 2001 From: Manav Rathi Date: Fri, 16 Aug 2024 21:28:14 +0530 Subject: [PATCH 03/36] Use same nomenclature as the architecture document --- web/packages/base/session-store.ts | 6 ++--- .../new/photos/services/user-entity.ts | 26 ++++++++++--------- 2 files changed, 17 insertions(+), 15 deletions(-) diff --git a/web/packages/base/session-store.ts b/web/packages/base/session-store.ts index 6e37bac534..f2a4ebd86c 100644 --- a/web/packages/base/session-store.ts +++ b/web/packages/base/session-store.ts @@ -2,16 +2,16 @@ import { sharedCryptoWorker } from "@/base/crypto"; import { z } from "zod"; /** - * Return the base64 encoded user's encryption key from session storage. + * Return the user's master key (as a base64 string) from session storage. * * Precondition: The user should be logged in. */ -export const usersEncryptionKeyB64 = async () => { +export const masterKeyB64FromSession = async () => { // TODO: Same value as the deprecated SESSION_KEYS.ENCRYPTION_KEY. const value = sessionStorage.getItem("encryptionKey"); if (!value) { throw new Error( - "The user's encryption key was not found in session storage. Likely they are not logged in.", + "The user's master key was not found in session storage. Likely they are not logged in.", ); } diff --git a/web/packages/new/photos/services/user-entity.ts b/web/packages/new/photos/services/user-entity.ts index 312bd22f55..4c92364e72 100644 --- a/web/packages/new/photos/services/user-entity.ts +++ b/web/packages/new/photos/services/user-entity.ts @@ -3,7 +3,7 @@ import { decryptAssociatedDataB64 } from "@/base/crypto/ente"; import { authenticatedRequestHeaders, ensureOk, HTTPError } from "@/base/http"; import { getKV, getKVN, setKV } from "@/base/kv"; import { apiURL } from "@/base/origins"; -import { usersEncryptionKeyB64 } from "@/base/session-store"; +import { masterKeyB64FromSession } from "@/base/session-store"; import { ensure } from "@/utils/ensure"; import { nullToUndefined } from "@/utils/transform"; import { z } from "zod"; @@ -172,19 +172,21 @@ const userEntityDiff = async ( * Return the entity key that can be used to decrypt the encrypted contents of * user entities of the given {@link type}. * - * 1. We'll see if we have the (encrypted) entity key present locally. If so, - * we'll decrypt it using the user's master key and return it. + * 1. See if we have the encrypted entity key present locally. If so, return + * the entity key by decrypting it using with the user's master key. * - * 2. Otherwise we'll fetch the entity key for that type from remote. If found, - * we'll decrypte it using the user's master key and return it, also saving - * it locally for future use. + * 2. Otherwise fetch the encrypted entity key for that type from remote. If we + * get one, obtain the entity key by decrypt the encrypted one using the + * user's master key, save it locally for future use, and return it. * - * 3. Otherwise we'll create a new one, save it locally and put it to remote. + * 3. Otherwise generate a new entity key, encrypt it using the user's master + * key, putting the encrypted one to remote and also saving it locally, and + * return it. * * See also, [Note: User entity keys]. */ const getOrCreateEntityKeyB64 = async (type: EntityType) => { - const encryptionKeyB64 = await usersEncryptionKeyB64(); + const encryptionKeyB64 = await masterKeyB64FromSession(); const worker = await sharedCryptoWorker(); const decrypt = async ({ encryptedKey, header }: RemoteUserEntityKey) => { @@ -244,10 +246,10 @@ const saveRemoteUserEntityKey = ( * * [Note: User entity keys] * - * There is one encryption key (itself encrypted with the user's encryption key) - * for each user entity type. If the key doesn't exist on remote, then the - * client is expected to create one on the user's behalf. Remote will disallow - * attempts to multiple keys for the same user entity type. + * There is one encryption key (itself encrypted with the user's master key) for + * each user entity type. If the key doesn't exist on remote, then the client is + * expected to create one on the user's behalf. Remote will disallow attempts to + * multiple keys for the same user entity type. */ const getUserEntityKey = async ( type: EntityType, From d91462773ad00f8627c9191b08ef8dcecc9323eb Mon Sep 17 00:00:00 2001 From: Manav Rathi Date: Fri, 16 Aug 2024 21:34:25 +0530 Subject: [PATCH 04/36] Use same nomenclature as the architecture document --- desktop/src/main/ipc.ts | 10 +++++----- desktop/src/main/services/store.ts | 6 +++--- desktop/src/preload.ts | 10 +++++----- web/apps/photos/src/pages/index.tsx | 4 ++-- web/packages/accounts/pages/credentials.tsx | 7 +++++-- web/packages/base/types/ipc.ts | 16 ++++++++++------ web/packages/shared/crypto/helpers.ts | 2 +- 7 files changed, 31 insertions(+), 24 deletions(-) diff --git a/desktop/src/main/ipc.ts b/desktop/src/main/ipc.ts index 6c4020d6ee..cca4f0a0ff 100644 --- a/desktop/src/main/ipc.ts +++ b/desktop/src/main/ipc.ts @@ -45,9 +45,9 @@ import { convertToJPEG, generateImageThumbnail } from "./services/image"; import { logout } from "./services/logout"; import { createMLWorker } from "./services/ml"; import { - encryptionKey, lastShownChangelogVersion, - saveEncryptionKey, + masterKeyB64, + saveMasterKeyB64, setLastShownChangelogVersion, } from "./services/store"; import { @@ -103,10 +103,10 @@ export const attachIPCHandlers = () => { ipcMain.handle("selectDirectory", () => selectDirectory()); - ipcMain.handle("encryptionKey", () => encryptionKey()); + ipcMain.handle("masterKeyB64", () => masterKeyB64()); - ipcMain.handle("saveEncryptionKey", (_, encryptionKey: string) => - saveEncryptionKey(encryptionKey), + ipcMain.handle("saveMasterKeyB64", (_, masterKeyB64: string) => + saveMasterKeyB64(masterKeyB64), ); ipcMain.handle("lastShownChangelogVersion", () => diff --git a/desktop/src/main/services/store.ts b/desktop/src/main/services/store.ts index 4663c2525f..8ebac1a276 100644 --- a/desktop/src/main/services/store.ts +++ b/desktop/src/main/services/store.ts @@ -24,13 +24,13 @@ export const clearStores = () => { * On macOS, `safeStorage` stores our data under a Keychain entry named * " Safe Storage". In our case, "ente Safe Storage". */ -export const saveEncryptionKey = (encryptionKey: string) => { - const encryptedKey = safeStorage.encryptString(encryptionKey); +export const saveMasterKeyB64 = (masterKeyB64: string) => { + const encryptedKey = safeStorage.encryptString(masterKeyB64); const b64EncryptedKey = Buffer.from(encryptedKey).toString("base64"); safeStorageStore.set("encryptionKey", b64EncryptedKey); }; -export const encryptionKey = (): string | undefined => { +export const masterKeyB64 = (): string | undefined => { const b64EncryptedKey = safeStorageStore.get("encryptionKey"); if (!b64EncryptedKey) return undefined; const keyBuffer = Buffer.from(b64EncryptedKey, "base64"); diff --git a/desktop/src/preload.ts b/desktop/src/preload.ts index 8472e91ff0..0af7682eae 100644 --- a/desktop/src/preload.ts +++ b/desktop/src/preload.ts @@ -103,10 +103,10 @@ const logout = () => { return ipcRenderer.invoke("logout"); }; -const encryptionKey = () => ipcRenderer.invoke("encryptionKey"); +const masterKeyB64 = () => ipcRenderer.invoke("masterKeyB64"); -const saveEncryptionKey = (encryptionKey: string) => - ipcRenderer.invoke("saveEncryptionKey", encryptionKey); +const saveMasterKeyB64 = (masterKeyB64: string) => + ipcRenderer.invoke("saveMasterKeyB64", masterKeyB64); const lastShownChangelogVersion = () => ipcRenderer.invoke("lastShownChangelogVersion"); @@ -342,8 +342,8 @@ contextBridge.exposeInMainWorld("electron", { openLogDirectory, selectDirectory, logout, - encryptionKey, - saveEncryptionKey, + masterKeyB64, + saveMasterKeyB64, lastShownChangelogVersion, setLastShownChangelogVersion, onMainWindowFocus, diff --git a/web/apps/photos/src/pages/index.tsx b/web/apps/photos/src/pages/index.tsx index 1708b1c137..d2a3827efd 100644 --- a/web/apps/photos/src/pages/index.tsx +++ b/web/apps/photos/src/pages/index.tsx @@ -73,9 +73,9 @@ export default function LandingPage() { const electron = globalThis.electron; if (!key && electron) { try { - key = await electron.encryptionKey(); + key = await electron.masterKeyB64(); } catch (e) { - log.error("Failed to get encryption key from electron", e); + log.error("Failed to read master key from secure storage", e); } if (key) { await saveKeyInSessionStore( diff --git a/web/packages/accounts/pages/credentials.tsx b/web/packages/accounts/pages/credentials.tsx index 53ebb07590..769914b74e 100644 --- a/web/packages/accounts/pages/credentials.tsx +++ b/web/packages/accounts/pages/credentials.tsx @@ -125,9 +125,12 @@ const Page: React.FC = ({ appContext }) => { const electron = globalThis.electron; if (!key && electron) { try { - key = await electron.encryptionKey(); + key = await electron.masterKeyB64(); } catch (e) { - log.error("Failed to get encryption key from electron", e); + log.error( + "Failed to read master key from secure storage", + e, + ); } if (key) { await saveKeyInSessionStore( diff --git a/web/packages/base/types/ipc.ts b/web/packages/base/types/ipc.ts index c0644760c0..dd670e21ba 100644 --- a/web/packages/base/types/ipc.ts +++ b/web/packages/base/types/ipc.ts @@ -69,18 +69,22 @@ export interface Electron { logout: () => Promise; /** - * Return the previously saved encryption key from persistent safe storage. + * Return the previously saved user's master key from the persistent safe + * storage accessible to the desktop app. * - * If no such key is found, return `undefined`. + * The key is returned as a base64 encoded string. * - * See also: {@link saveEncryptionKey}. + * If the key is not found, return `undefined`. + * + * See also: {@link saveMasterKeyB64}. */ - encryptionKey: () => Promise; + masterKeyB64: () => Promise; /** - * Save the given {@link encryptionKey} into persistent safe storage. + * Save the given {@link masterKeyB64} (encoded as a base64 string) to the + * persistent safe storage accessible to the desktop app. */ - saveEncryptionKey: (encryptionKey: string) => Promise; + saveMasterKeyB64: (masterKeyB64: string) => Promise; /** * Set or clear the callback {@link cb} to invoke whenever the app comes diff --git a/web/packages/shared/crypto/helpers.ts b/web/packages/shared/crypto/helpers.ts index c7165713ff..d042a11a1c 100644 --- a/web/packages/shared/crypto/helpers.ts +++ b/web/packages/shared/crypto/helpers.ts @@ -108,7 +108,7 @@ export const saveKeyInSessionStore = async ( setKey(keyType, sessionKeyAttributes); const electron = globalThis.electron; if (electron && !fromDesktop && keyType === SESSION_KEYS.ENCRYPTION_KEY) { - electron.saveEncryptionKey(key); + electron.saveMasterKeyB64(key); } }; From 93d48f6d6f47f5e139e226506f7c4f8d4f9794b8 Mon Sep 17 00:00:00 2001 From: Manav Rathi Date: Sat, 17 Aug 2024 11:14:02 +0530 Subject: [PATCH 05/36] Fix --- web/apps/photos/src/pages/index.tsx | 2 +- web/packages/accounts/pages/credentials.tsx | 5 +---- web/packages/new/photos/services/user-entity.ts | 6 +++--- 3 files changed, 5 insertions(+), 8 deletions(-) diff --git a/web/apps/photos/src/pages/index.tsx b/web/apps/photos/src/pages/index.tsx index d2a3827efd..635685b7e3 100644 --- a/web/apps/photos/src/pages/index.tsx +++ b/web/apps/photos/src/pages/index.tsx @@ -75,7 +75,7 @@ export default function LandingPage() { try { key = await electron.masterKeyB64(); } catch (e) { - log.error("Failed to read master key from secure storage", e); + log.error("Failed to read master key from safe storage", e); } if (key) { await saveKeyInSessionStore( diff --git a/web/packages/accounts/pages/credentials.tsx b/web/packages/accounts/pages/credentials.tsx index 769914b74e..b29cb952c6 100644 --- a/web/packages/accounts/pages/credentials.tsx +++ b/web/packages/accounts/pages/credentials.tsx @@ -127,10 +127,7 @@ const Page: React.FC = ({ appContext }) => { try { key = await electron.masterKeyB64(); } catch (e) { - log.error( - "Failed to read master key from secure storage", - e, - ); + log.error("Failed to read master key from safe storage", e); } if (key) { await saveKeyInSessionStore( diff --git a/web/packages/new/photos/services/user-entity.ts b/web/packages/new/photos/services/user-entity.ts index 4c92364e72..4a1945f293 100644 --- a/web/packages/new/photos/services/user-entity.ts +++ b/web/packages/new/photos/services/user-entity.ts @@ -186,11 +186,11 @@ const userEntityDiff = async ( * See also, [Note: User entity keys]. */ const getOrCreateEntityKeyB64 = async (type: EntityType) => { - const encryptionKeyB64 = await masterKeyB64FromSession(); + const masterKeyB64 = await masterKeyB64FromSession(); const worker = await sharedCryptoWorker(); const decrypt = async ({ encryptedKey, header }: RemoteUserEntityKey) => { - return worker.decryptB64(encryptedKey, header, encryptionKeyB64); + return worker.decryptB64(encryptedKey, header, masterKeyB64); }; // See if we already have it locally. @@ -202,7 +202,7 @@ const getOrCreateEntityKeyB64 = async (type: EntityType) => { if (existing) { // Only save it if we can decrypt it to avoid corrupting our local state // in unforeseen circumstances. - const result = decrypt(existing); + const result = await decrypt(existing); await saveRemoteUserEntityKey(type, existing); return result; } From dbe98acbd7abcc8c5490a4d3bf6925ebb25e51cf Mon Sep 17 00:00:00 2001 From: Manav Rathi Date: Sat, 17 Aug 2024 16:38:51 +0530 Subject: [PATCH 06/36] Dec --- .../new/photos/services/user-entity.ts | 27 ++++++++++++------- 1 file changed, 17 insertions(+), 10 deletions(-) diff --git a/web/packages/new/photos/services/user-entity.ts b/web/packages/new/photos/services/user-entity.ts index 4a1945f293..07fe126dd2 100644 --- a/web/packages/new/photos/services/user-entity.ts +++ b/web/packages/new/photos/services/user-entity.ts @@ -1,5 +1,5 @@ import { sharedCryptoWorker } from "@/base/crypto"; -import { decryptAssociatedDataB64 } from "@/base/crypto/ente"; +import { decryptAssociatedDataB64, decryptBoxB64 } from "@/base/crypto/ente"; import { authenticatedRequestHeaders, ensureOk, HTTPError } from "@/base/http"; import { getKV, getKVN, setKV } from "@/base/kv"; import { apiURL } from "@/base/origins"; @@ -186,23 +186,16 @@ const userEntityDiff = async ( * See also, [Note: User entity keys]. */ const getOrCreateEntityKeyB64 = async (type: EntityType) => { - const masterKeyB64 = await masterKeyB64FromSession(); - const worker = await sharedCryptoWorker(); - - const decrypt = async ({ encryptedKey, header }: RemoteUserEntityKey) => { - return worker.decryptB64(encryptedKey, header, masterKeyB64); - }; - // See if we already have it locally. const saved = await savedRemoteUserEntityKey(type); - if (saved) return decrypt(saved); + if (saved) return decryptEntityKey(saved); // See if remote already has it. const existing = await getUserEntityKey(type); if (existing) { // Only save it if we can decrypt it to avoid corrupting our local state // in unforeseen circumstances. - const result = await decrypt(existing); + const result = await decryptEntityKey(existing); await saveRemoteUserEntityKey(type, existing); return result; } @@ -241,6 +234,20 @@ const saveRemoteUserEntityKey = ( entityKey: RemoteUserEntityKey, ) => setKV(entityKeyKey(type), JSON.stringify(entityKey)); +/** + * Decrypt an encrypted entity key using the user's master key. + */ +const decryptEntityKey = async ({ + encryptedKey, + header, +}: RemoteUserEntityKey) => + decryptBoxB64({ + encryptedDataB64: encryptedKey, + // Remote calls it the header, but it really is the nonce. + nonceB64: header, + keyB64: await masterKeyB64FromSession(), + }); + /** * Fetch the encryption key for the given user entity {@link type} from remote. * From f0b86323c33e6286b5001cf110a41e28c9c00275 Mon Sep 17 00:00:00 2001 From: Manav Rathi Date: Sat, 17 Aug 2024 17:00:42 +0530 Subject: [PATCH 07/36] Attempt to curb the combinatorial explosion --- web/packages/base/crypto/ente.ts | 2 +- web/packages/base/crypto/libsodium.ts | 25 ++++++++++++++++++++++++ web/packages/base/crypto/types.ts | 28 +++++++++++++++++++++++++++ 3 files changed, 54 insertions(+), 1 deletion(-) diff --git a/web/packages/base/crypto/ente.ts b/web/packages/base/crypto/ente.ts index dce6dc1050..11f1c41878 100644 --- a/web/packages/base/crypto/ente.ts +++ b/web/packages/base/crypto/ente.ts @@ -142,7 +142,7 @@ export const encryptMetadataJSON = async (r: EncryptJSON) => /** * Decrypt arbitrary data, provided as a base64 string, using the given key and - * the provided nonce. + * the provided nonce, and return the base64 * * This is the sibling of {@link encryptBoxB64}. * diff --git a/web/packages/base/crypto/libsodium.ts b/web/packages/base/crypto/libsodium.ts index 5f57c459b8..a21359b181 100644 --- a/web/packages/base/crypto/libsodium.ts +++ b/web/packages/base/crypto/libsodium.ts @@ -12,10 +12,12 @@ import { mergeUint8Arrays } from "@/utils/array"; import { CustomError } from "@ente/shared/error"; import sodium, { type StateAddress } from "libsodium-wrappers"; import type { + BytesOrB64, DecryptBlobBytes, DecryptBoxBytes, EncryptBytes, EncryptedBlobBytes, + EncryptedBox2, EncryptedBoxBytes, } from "./types"; @@ -342,6 +344,29 @@ export const decryptBox = async ({ ); }; +/** + * If the provided {@link bob} ("Bytes or B64 string") is already a + * {@link Uint8Array}, return it unchanged, otherwise convert the base64 string + * into bytes and return those. + */ +const bytes = async (bob: BytesOrB64) => + typeof bob == "string" ? fromB64(bob) : bob; + +/** + * Decrypt the result of {@link encryptBox}. + */ +export const decryptBox2 = async ( + { encryptedData, nonce }: EncryptedBox2, + key: BytesOrB64, +): Promise => { + await sodium.ready; + return sodium.crypto_secretbox_open_easy( + await bytes(encryptedData), + await bytes(nonce), + await bytes(key), + ); +}; + /** * Decrypt the result of {@link encryptBlob}. */ diff --git a/web/packages/base/crypto/types.ts b/web/packages/base/crypto/types.ts index 1166c00bc3..1e6144d059 100644 --- a/web/packages/base/crypto/types.ts +++ b/web/packages/base/crypto/types.ts @@ -149,6 +149,34 @@ export interface DecryptBoxBytes { keyB64: string; } +/** + * Data provided either as bytes ({@link Uint8Array}) or their base64 string representation. + */ +export type BytesOrB64 = Uint8Array | string; + +/** + * A decryption request to decrypt data encrypted using the secretbox APIs. + * + * See: [Note: 3 forms of encryption (Box | Blob | Stream)]. + */ +export interface EncryptedBox2 { + /** + * The data to decrypt. + */ + encryptedData: BytesOrB64; + /** + * The nonce that was used during encryption. + * + * The nonce is required to decrypt the data, but it does not need to be + * kept secret. + */ + nonce: BytesOrB64; + /** + * The encryption key. + */ + key: BytesOrB64; +} + /** * A variant of {@link DecryptBoxBytes} with the encrypted Blob's data as a * base64 encoded string. From f3e947f47e5f219077968f04fe36783d80999b06 Mon Sep 17 00:00:00 2001 From: Manav Rathi Date: Sat, 17 Aug 2024 17:10:06 +0530 Subject: [PATCH 08/36] Match the types --- web/packages/base/crypto/ente-impl.ts | 18 ++++--------- web/packages/base/crypto/ente.ts | 25 +++++++++++-------- web/packages/base/crypto/types.ts | 4 --- web/packages/base/crypto/worker.ts | 1 + .../new/photos/services/user-entity.ts | 15 +++++------ 5 files changed, 29 insertions(+), 34 deletions(-) diff --git a/web/packages/base/crypto/ente-impl.ts b/web/packages/base/crypto/ente-impl.ts index bb7c6d77a2..156ee30094 100644 --- a/web/packages/base/crypto/ente-impl.ts +++ b/web/packages/base/crypto/ente-impl.ts @@ -1,13 +1,13 @@ /** Careful when adding add other imports! */ import * as libsodium from "./libsodium"; import type { + BytesOrB64, DecryptBlobB64, - DecryptBoxB64, - DecryptBoxBytes, EncryptB64, EncryptBytes, EncryptedBlobB64, EncryptedBlobBytes, + EncryptedBox2, EncryptJSON, } from "./types"; @@ -43,18 +43,10 @@ export const _encryptMetadataJSON = ({ jsonValue, keyB64 }: EncryptJSON) => keyB64, }); -const DecryptBoxB64ToBytes = async ({ - encryptedDataB64, - nonceB64, - keyB64, -}: DecryptBoxB64): Promise => ({ - encryptedData: await libsodium.fromB64(encryptedDataB64), - nonceB64, - keyB64, -}); +export const _decryptBox = libsodium.decryptBox2; -export const _decryptBoxB64 = (r: DecryptBoxB64) => - DecryptBoxB64ToBytes(r).then((rb) => libsodium.decryptBox(rb)); +export const _decryptBoxB64 = (b: EncryptedBox2, k: BytesOrB64) => + _decryptBox(b, k).then(libsodium.toB64); export const _decryptAssociatedData = libsodium.decryptBlob; diff --git a/web/packages/base/crypto/ente.ts b/web/packages/base/crypto/ente.ts index 11f1c41878..d2657bb19f 100644 --- a/web/packages/base/crypto/ente.ts +++ b/web/packages/base/crypto/ente.ts @@ -52,11 +52,12 @@ import { assertionFailed } from "../assert"; import { inWorker } from "../env"; import * as ei from "./ente-impl"; import type { + BytesOrB64, DecryptBlobB64, DecryptBlobBytes, - DecryptBoxB64, EncryptB64, EncryptBytes, + EncryptedBox2, EncryptJSON, } from "./types"; @@ -141,17 +142,21 @@ export const encryptMetadataJSON = async (r: EncryptJSON) => : sharedCryptoWorker().then((w) => w.encryptMetadataJSON(r)); /** - * Decrypt arbitrary data, provided as a base64 string, using the given key and - * the provided nonce, and return the base64 - * - * This is the sibling of {@link encryptBoxB64}. - * - * See {@link decryptBox} for the implementation details. + * Decrypt a box encrypted using {@link encryptBox}. */ -export const decryptBoxB64 = (r: DecryptBoxB64) => +export const decryptBox = (b: EncryptedBox2, k: BytesOrB64) => inWorker() - ? ei._decryptBoxB64(r) - : sharedCryptoWorker().then((w) => w.decryptBoxB64(r)); + ? ei._decryptBox(b, k) + : sharedCryptoWorker().then((w) => w.decryptBox(b, k)); + +/** + * Variant of {@link decryptBox} that returns the decrypted data as a base64 + * string. + */ +export const decryptBoxB64 = (b: EncryptedBox2, k: BytesOrB64) => + inWorker() + ? ei._decryptBoxB64(b, k) + : sharedCryptoWorker().then((w) => w.decryptBoxB64(b, k)); /** * Decrypt arbitrary data associated with an Ente object (file, collection or diff --git a/web/packages/base/crypto/types.ts b/web/packages/base/crypto/types.ts index 1e6144d059..e7ed032acb 100644 --- a/web/packages/base/crypto/types.ts +++ b/web/packages/base/crypto/types.ts @@ -171,10 +171,6 @@ export interface EncryptedBox2 { * kept secret. */ nonce: BytesOrB64; - /** - * The encryption key. - */ - key: BytesOrB64; } /** diff --git a/web/packages/base/crypto/worker.ts b/web/packages/base/crypto/worker.ts index 72ae1fb332..13e36c546d 100644 --- a/web/packages/base/crypto/worker.ts +++ b/web/packages/base/crypto/worker.ts @@ -15,6 +15,7 @@ export class CryptoWorker { encryptBoxB64 = ei._encryptBoxB64; encryptThumbnail = ei._encryptThumbnail; encryptMetadataJSON = ei._encryptMetadataJSON; + decryptBox = ei._decryptBox; decryptBoxB64 = ei._decryptBoxB64; decryptThumbnail = ei._decryptThumbnail; decryptAssociatedDataB64 = ei._decryptAssociatedDataB64; diff --git a/web/packages/new/photos/services/user-entity.ts b/web/packages/new/photos/services/user-entity.ts index 07fe126dd2..5902573088 100644 --- a/web/packages/new/photos/services/user-entity.ts +++ b/web/packages/new/photos/services/user-entity.ts @@ -1,4 +1,3 @@ -import { sharedCryptoWorker } from "@/base/crypto"; import { decryptAssociatedDataB64, decryptBoxB64 } from "@/base/crypto/ente"; import { authenticatedRequestHeaders, ensureOk, HTTPError } from "@/base/http"; import { getKV, getKVN, setKV } from "@/base/kv"; @@ -241,12 +240,14 @@ const decryptEntityKey = async ({ encryptedKey, header, }: RemoteUserEntityKey) => - decryptBoxB64({ - encryptedDataB64: encryptedKey, - // Remote calls it the header, but it really is the nonce. - nonceB64: header, - keyB64: await masterKeyB64FromSession(), - }); + decryptBoxB64( + { + encryptedData: encryptedKey, + // Remote calls it the header, but it really is the nonce. + nonce: header, + }, + await masterKeyB64FromSession(), + ); /** * Fetch the encryption key for the given user entity {@link type} from remote. From 153be4990adbb610aa6d41ab3f12a2d05f9ba569 Mon Sep 17 00:00:00 2001 From: Manav Rathi Date: Sat, 17 Aug 2024 17:13:16 +0530 Subject: [PATCH 09/36] Rearrange --- web/packages/base/crypto/types.ts | 52 +++++++++++++++++-------------- 1 file changed, 28 insertions(+), 24 deletions(-) diff --git a/web/packages/base/crypto/types.ts b/web/packages/base/crypto/types.ts index e7ed032acb..9fde2fd388 100644 --- a/web/packages/base/crypto/types.ts +++ b/web/packages/base/crypto/types.ts @@ -1,3 +1,8 @@ +/** + * Data provided either as bytes ({@link Uint8Array}) or their base64 string representation. + */ +export type BytesOrB64 = Uint8Array | string; + /** * An encryption request with the data to encrypt provided as bytes. */ @@ -43,6 +48,29 @@ export interface EncryptJSON { keyB64: string; } +/** + * The result of encryption using the secretbox APIs. + * + * It contains an encrypted data and a randomly generated nonce that was used + * during encryption. Both these values are needed to decrypt the data. The + * nonce does not need to be secret. + * + * See: [Note: 3 forms of encryption (Box | Blob | Stream)]. + */ +export interface EncryptedBox2 { + /** + * The data to decrypt. + */ + encryptedData: BytesOrB64; + /** + * The nonce that was used during encryption. + * + * The nonce is required to decrypt the data, but it does not need to be + * kept secret. + */ + nonce: BytesOrB64; +} + /** * The result of encryption using the secretbox APIs. * @@ -149,30 +177,6 @@ export interface DecryptBoxBytes { keyB64: string; } -/** - * Data provided either as bytes ({@link Uint8Array}) or their base64 string representation. - */ -export type BytesOrB64 = Uint8Array | string; - -/** - * A decryption request to decrypt data encrypted using the secretbox APIs. - * - * See: [Note: 3 forms of encryption (Box | Blob | Stream)]. - */ -export interface EncryptedBox2 { - /** - * The data to decrypt. - */ - encryptedData: BytesOrB64; - /** - * The nonce that was used during encryption. - * - * The nonce is required to decrypt the data, but it does not need to be - * kept secret. - */ - nonce: BytesOrB64; -} - /** * A variant of {@link DecryptBoxBytes} with the encrypted Blob's data as a * base64 encoded string. From cf6508be4ad5445ea5f53651b068367c22257436 Mon Sep 17 00:00:00 2001 From: Manav Rathi Date: Sat, 17 Aug 2024 17:33:22 +0530 Subject: [PATCH 10/36] Enc --- web/packages/base/crypto/ente-impl.ts | 12 +------- web/packages/base/crypto/ente.ts | 16 +++++----- web/packages/base/crypto/libsodium.ts | 43 +++++++++++++++++++++------ web/packages/base/crypto/types.ts | 28 +++++++++++++++++ 4 files changed, 71 insertions(+), 28 deletions(-) diff --git a/web/packages/base/crypto/ente-impl.ts b/web/packages/base/crypto/ente-impl.ts index 156ee30094..d6b395f758 100644 --- a/web/packages/base/crypto/ente-impl.ts +++ b/web/packages/base/crypto/ente-impl.ts @@ -3,7 +3,6 @@ import * as libsodium from "./libsodium"; import type { BytesOrB64, DecryptBlobB64, - EncryptB64, EncryptBytes, EncryptedBlobB64, EncryptedBlobBytes, @@ -11,14 +10,6 @@ import type { EncryptJSON, } from "./types"; -const EncryptB64ToBytes = async ({ - dataB64, - keyB64, -}: EncryptB64): Promise => ({ - data: await libsodium.fromB64(dataB64), - keyB64, -}); - const EncryptedBlobBytesToB64 = async ({ encryptedData, decryptionHeaderB64, @@ -27,8 +18,7 @@ const EncryptedBlobBytesToB64 = async ({ decryptionHeaderB64, }); -export const _encryptBoxB64 = (r: EncryptB64) => - EncryptB64ToBytes(r).then((rb) => libsodium.encryptBox(rb)); +export const _encryptBoxB64 = libsodium.encryptBoxB64; export const _encryptAssociatedData = libsodium.encryptBlob; diff --git a/web/packages/base/crypto/ente.ts b/web/packages/base/crypto/ente.ts index d2657bb19f..db560d7bbd 100644 --- a/web/packages/base/crypto/ente.ts +++ b/web/packages/base/crypto/ente.ts @@ -55,7 +55,6 @@ import type { BytesOrB64, DecryptBlobB64, DecryptBlobBytes, - EncryptB64, EncryptBytes, EncryptedBox2, EncryptJSON, @@ -75,7 +74,8 @@ const assertInWorker = (x: T): T => { }; /** - * Encrypt arbitrary data using the given key and a randomly generated nonce. + * Encrypt the given data, returning a box containing encrypted data and a + * randomly generated nonce. * * Use {@link decryptBoxB64} to decrypt the result. * @@ -86,10 +86,10 @@ const assertInWorker = (x: T): T => { * > * > See: [Note: 3 forms of encryption (Box | Blob | Stream)] */ -export const encryptBoxB64 = (r: EncryptB64) => +export const encryptBoxB64 = (data: BytesOrB64, key: BytesOrB64) => inWorker() - ? ei._encryptBoxB64(r) - : sharedCryptoWorker().then((w) => w.encryptBoxB64(r)); + ? ei._encryptBoxB64(data, key) + : sharedCryptoWorker().then((w) => w.encryptBoxB64(data, key)); /** * Encrypt arbitrary data associated with an Ente object (file, collection, @@ -153,10 +153,10 @@ export const decryptBox = (b: EncryptedBox2, k: BytesOrB64) => * Variant of {@link decryptBox} that returns the decrypted data as a base64 * string. */ -export const decryptBoxB64 = (b: EncryptedBox2, k: BytesOrB64) => +export const decryptBoxB64 = (box: EncryptedBox2, key: BytesOrB64) => inWorker() - ? ei._decryptBoxB64(b, k) - : sharedCryptoWorker().then((w) => w.decryptBoxB64(b, k)); + ? ei._decryptBoxB64(box, key) + : sharedCryptoWorker().then((w) => w.decryptBoxB64(box, key)); /** * Decrypt arbitrary data associated with an Ente object (file, collection or diff --git a/web/packages/base/crypto/libsodium.ts b/web/packages/base/crypto/libsodium.ts index a21359b181..5b7d6aa25e 100644 --- a/web/packages/base/crypto/libsodium.ts +++ b/web/packages/base/crypto/libsodium.ts @@ -18,6 +18,7 @@ import type { EncryptBytes, EncryptedBlobBytes, EncryptedBox2, + EncryptedBoxB64, EncryptedBoxBytes, } from "./types"; @@ -122,7 +123,22 @@ export async function fromHex(input: string) { } /** - * Encrypt the given data using the provided base64 encoded key. + * If the provided {@link bob} ("Bytes or B64 string") is already a + * {@link Uint8Array}, return it unchanged, otherwise convert the base64 string + * into bytes and return those. + */ +const bytes = async (bob: BytesOrB64) => + typeof bob == "string" ? fromB64(bob) : bob; + +/** + * Encrypt the given data using libsodium's secretbox APIs, using a randomly + * generated nonce. + * + * Use {@link decryptBox} to decrypt the result. + * + * @param data The data to encrypt. + * + * @param key The key to use for encryption. * * [Note: 3 forms of encryption (Box | Blob | Stream)] * @@ -207,6 +223,23 @@ export async function fromHex(input: string) { * * 3. Box returns a "nonce", while Blob returns a "header". */ +export const encryptBoxB64 = async ( + data: BytesOrB64, + key: BytesOrB64, +): Promise => { + await sodium.ready; + const nonce = sodium.randombytes_buf(sodium.crypto_secretbox_NONCEBYTES); + const encryptedData = sodium.crypto_secretbox_easy( + data, + nonce, + await bytes(key), + ); + return { + encryptedData: await toB64(encryptedData), + nonce: await toB64(nonce), + }; +}; + export const encryptBox = async ({ data, keyB64, @@ -344,14 +377,6 @@ export const decryptBox = async ({ ); }; -/** - * If the provided {@link bob} ("Bytes or B64 string") is already a - * {@link Uint8Array}, return it unchanged, otherwise convert the base64 string - * into bytes and return those. - */ -const bytes = async (bob: BytesOrB64) => - typeof bob == "string" ? fromB64(bob) : bob; - /** * Decrypt the result of {@link encryptBox}. */ diff --git a/web/packages/base/crypto/types.ts b/web/packages/base/crypto/types.ts index 9fde2fd388..0824fa7286 100644 --- a/web/packages/base/crypto/types.ts +++ b/web/packages/base/crypto/types.ts @@ -3,6 +3,20 @@ */ export type BytesOrB64 = Uint8Array | string; +/** + * An encryption request + */ +export interface EncryptBytesOrB64 { + /** + * The data to encrypt. + */ + data: BytesOrB64; + /** + * The key to use for encryption. + */ + key: BytesOrB64; +} + /** * An encryption request with the data to encrypt provided as bytes. */ @@ -71,6 +85,20 @@ export interface EncryptedBox2 { nonce: BytesOrB64; } +export interface EncryptedBoxB64 { + /** + * The encrypted data as a base64 string. + */ + encryptedData: string; + /** + * The nonce that was used during encryption, as a base64 string. + * + * The nonce is required to decrypt the data, but it does not need to be + * kept secret. + */ + nonce: string; +} + /** * The result of encryption using the secretbox APIs. * From 25b9a36554c2a69727715903224a3f2ae83e969e Mon Sep 17 00:00:00 2001 From: Manav Rathi Date: Sat, 17 Aug 2024 18:30:37 +0530 Subject: [PATCH 11/36] Fix crypto_secretbox_easy takes a string, but that's a UTF-8 string, not the base64 one that we're looking for. --- web/packages/base/crypto/libsodium.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/web/packages/base/crypto/libsodium.ts b/web/packages/base/crypto/libsodium.ts index 5b7d6aa25e..f1b537e62e 100644 --- a/web/packages/base/crypto/libsodium.ts +++ b/web/packages/base/crypto/libsodium.ts @@ -230,8 +230,8 @@ export const encryptBoxB64 = async ( await sodium.ready; const nonce = sodium.randombytes_buf(sodium.crypto_secretbox_NONCEBYTES); const encryptedData = sodium.crypto_secretbox_easy( - data, - nonce, + await bytes(data), + await bytes(nonce), await bytes(key), ); return { From 890ed7dd4b9aeb756c8bb723d03785ce7c370866 Mon Sep 17 00:00:00 2001 From: Manav Rathi Date: Sat, 17 Aug 2024 18:43:31 +0530 Subject: [PATCH 12/36] Cleanup --- web/packages/base/crypto/ente-impl.ts | 7 +------ web/packages/base/crypto/ente.ts | 24 ++++++++---------------- web/packages/base/crypto/libsodium.ts | 9 +++++++-- web/packages/base/crypto/types.ts | 5 +++-- web/packages/base/crypto/worker.ts | 1 - 5 files changed, 19 insertions(+), 27 deletions(-) diff --git a/web/packages/base/crypto/ente-impl.ts b/web/packages/base/crypto/ente-impl.ts index d6b395f758..71ecc88b7a 100644 --- a/web/packages/base/crypto/ente-impl.ts +++ b/web/packages/base/crypto/ente-impl.ts @@ -1,12 +1,10 @@ /** Careful when adding add other imports! */ import * as libsodium from "./libsodium"; import type { - BytesOrB64, DecryptBlobB64, EncryptBytes, EncryptedBlobB64, EncryptedBlobBytes, - EncryptedBox2, EncryptJSON, } from "./types"; @@ -33,10 +31,7 @@ export const _encryptMetadataJSON = ({ jsonValue, keyB64 }: EncryptJSON) => keyB64, }); -export const _decryptBox = libsodium.decryptBox2; - -export const _decryptBoxB64 = (b: EncryptedBox2, k: BytesOrB64) => - _decryptBox(b, k).then(libsodium.toB64); +export const _decryptBoxB64 = libsodium.decryptBoxB64; export const _decryptAssociatedData = libsodium.decryptBlob; diff --git a/web/packages/base/crypto/ente.ts b/web/packages/base/crypto/ente.ts index db560d7bbd..afd7ceb606 100644 --- a/web/packages/base/crypto/ente.ts +++ b/web/packages/base/crypto/ente.ts @@ -64,9 +64,9 @@ import type { * Some of these functions have not yet been needed on the main thread, and for * these we don't have a corresponding sharedCryptoWorker method. * - * This assertion will let us know when we need to implement them (this'll + * This assertion will let us know when we need to implement them. This will * gracefully degrade in production: the functionality will work, just that the - * crypto will happen on the main thread itself). + * crypto operations will happen on the main thread itself. */ const assertInWorker = (x: T): T => { if (!inWorker()) assertionFailed("Currently only usable in a web worker"); @@ -74,13 +74,13 @@ const assertInWorker = (x: T): T => { }; /** - * Encrypt the given data, returning a box containing encrypted data and a - * randomly generated nonce. + * Encrypt the given data, returning a box containing the encrypted data and a + * randomly generated nonce that was used during encryption. + * + * Both the encrypted data and the nonce are returned as base64 strings. * * Use {@link decryptBoxB64} to decrypt the result. * - * ee {@link encryptBox} for the implementation details. - * * > The suffix "Box" comes from the fact that it uses the so called secretbox * > APIs provided by libsodium under the hood. * > @@ -142,16 +142,8 @@ export const encryptMetadataJSON = async (r: EncryptJSON) => : sharedCryptoWorker().then((w) => w.encryptMetadataJSON(r)); /** - * Decrypt a box encrypted using {@link encryptBox}. - */ -export const decryptBox = (b: EncryptedBox2, k: BytesOrB64) => - inWorker() - ? ei._decryptBox(b, k) - : sharedCryptoWorker().then((w) => w.decryptBox(b, k)); - -/** - * Variant of {@link decryptBox} that returns the decrypted data as a base64 - * string. + * Decrypt a box encrypted using {@link encryptBoxB64}, and return the result as + * a base64 string. */ export const decryptBoxB64 = (box: EncryptedBox2, key: BytesOrB64) => inWorker() diff --git a/web/packages/base/crypto/libsodium.ts b/web/packages/base/crypto/libsodium.ts index f1b537e62e..2937d1a7ba 100644 --- a/web/packages/base/crypto/libsodium.ts +++ b/web/packages/base/crypto/libsodium.ts @@ -378,9 +378,14 @@ export const decryptBox = async ({ }; /** - * Decrypt the result of {@link encryptBox}. + * Decrypt the result of {@link encryptBoxB64}. */ -export const decryptBox2 = async ( +export const decryptBoxB64 = ( + box: EncryptedBox2, + key: BytesOrB64, +): Promise => _decryptBox(box, key).then(toB64); + +export const _decryptBox = async ( { encryptedData, nonce }: EncryptedBox2, key: BytesOrB64, ): Promise => { diff --git a/web/packages/base/crypto/types.ts b/web/packages/base/crypto/types.ts index 0824fa7286..8795267338 100644 --- a/web/packages/base/crypto/types.ts +++ b/web/packages/base/crypto/types.ts @@ -1,10 +1,11 @@ /** - * Data provided either as bytes ({@link Uint8Array}) or their base64 string representation. + * Data provided either as bytes ({@link Uint8Array}) or their base64 string + * representation. */ export type BytesOrB64 = Uint8Array | string; /** - * An encryption request + * An encryption request. */ export interface EncryptBytesOrB64 { /** diff --git a/web/packages/base/crypto/worker.ts b/web/packages/base/crypto/worker.ts index 13e36c546d..72ae1fb332 100644 --- a/web/packages/base/crypto/worker.ts +++ b/web/packages/base/crypto/worker.ts @@ -15,7 +15,6 @@ export class CryptoWorker { encryptBoxB64 = ei._encryptBoxB64; encryptThumbnail = ei._encryptThumbnail; encryptMetadataJSON = ei._encryptMetadataJSON; - decryptBox = ei._decryptBox; decryptBoxB64 = ei._decryptBoxB64; decryptThumbnail = ei._decryptThumbnail; decryptAssociatedDataB64 = ei._decryptAssociatedDataB64; From d1b7177a53c9cb21a45b2d052d31e92f56c94f1d Mon Sep 17 00:00:00 2001 From: Manav Rathi Date: Sat, 17 Aug 2024 18:49:41 +0530 Subject: [PATCH 13/36] We need both --- web/packages/base/crypto/ente-impl.ts | 2 ++ web/packages/base/crypto/ente.ts | 11 +++++++++-- web/packages/base/crypto/libsodium.ts | 15 +++++++++------ web/packages/base/crypto/worker.ts | 1 + 4 files changed, 21 insertions(+), 8 deletions(-) diff --git a/web/packages/base/crypto/ente-impl.ts b/web/packages/base/crypto/ente-impl.ts index 71ecc88b7a..305cee31e7 100644 --- a/web/packages/base/crypto/ente-impl.ts +++ b/web/packages/base/crypto/ente-impl.ts @@ -31,6 +31,8 @@ export const _encryptMetadataJSON = ({ jsonValue, keyB64 }: EncryptJSON) => keyB64, }); +export const _decryptBox = libsodium.decryptBox2; + export const _decryptBoxB64 = libsodium.decryptBoxB64; export const _decryptAssociatedData = libsodium.decryptBlob; diff --git a/web/packages/base/crypto/ente.ts b/web/packages/base/crypto/ente.ts index afd7ceb606..01682c2ec5 100644 --- a/web/packages/base/crypto/ente.ts +++ b/web/packages/base/crypto/ente.ts @@ -142,8 +142,15 @@ export const encryptMetadataJSON = async (r: EncryptJSON) => : sharedCryptoWorker().then((w) => w.encryptMetadataJSON(r)); /** - * Decrypt a box encrypted using {@link encryptBoxB64}, and return the result as - * a base64 string. + * Decrypt a box encrypted using {@link encryptBoxB64}. + */ +export const decryptBox = (box: EncryptedBox2, key: BytesOrB64) => + inWorker() + ? ei._decryptBox(box, key) + : sharedCryptoWorker().then((w) => w.decryptBox(box, key)); + +/** + * Variant of {@link decryptBoxlink} that returns the result as a base64 string. */ export const decryptBoxB64 = (box: EncryptedBox2, key: BytesOrB64) => inWorker() diff --git a/web/packages/base/crypto/libsodium.ts b/web/packages/base/crypto/libsodium.ts index 2937d1a7ba..bb19f53f6e 100644 --- a/web/packages/base/crypto/libsodium.ts +++ b/web/packages/base/crypto/libsodium.ts @@ -380,12 +380,7 @@ export const decryptBox = async ({ /** * Decrypt the result of {@link encryptBoxB64}. */ -export const decryptBoxB64 = ( - box: EncryptedBox2, - key: BytesOrB64, -): Promise => _decryptBox(box, key).then(toB64); - -export const _decryptBox = async ( +export const decryptBox2 = async ( { encryptedData, nonce }: EncryptedBox2, key: BytesOrB64, ): Promise => { @@ -397,6 +392,14 @@ export const _decryptBox = async ( ); }; +/** + * Variant of {@link decryptBox} that returns the data as a base64 string. + */ +export const decryptBoxB64 = ( + box: EncryptedBox2, + key: BytesOrB64, +): Promise => decryptBox2(box, key).then(toB64); + /** * Decrypt the result of {@link encryptBlob}. */ diff --git a/web/packages/base/crypto/worker.ts b/web/packages/base/crypto/worker.ts index 72ae1fb332..13e36c546d 100644 --- a/web/packages/base/crypto/worker.ts +++ b/web/packages/base/crypto/worker.ts @@ -15,6 +15,7 @@ export class CryptoWorker { encryptBoxB64 = ei._encryptBoxB64; encryptThumbnail = ei._encryptThumbnail; encryptMetadataJSON = ei._encryptMetadataJSON; + decryptBox = ei._decryptBox; decryptBoxB64 = ei._decryptBoxB64; decryptThumbnail = ei._decryptThumbnail; decryptAssociatedDataB64 = ei._decryptAssociatedDataB64; From 90ffb68b514d8495e0d2a3981e9beb33e2c9a9e0 Mon Sep 17 00:00:00 2001 From: Manav Rathi Date: Sat, 17 Aug 2024 18:53:00 +0530 Subject: [PATCH 14/36] key variants too --- web/packages/base/session-store.ts | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/web/packages/base/session-store.ts b/web/packages/base/session-store.ts index f2a4ebd86c..26782ce964 100644 --- a/web/packages/base/session-store.ts +++ b/web/packages/base/session-store.ts @@ -1,12 +1,13 @@ -import { sharedCryptoWorker } from "@/base/crypto"; import { z } from "zod"; +import { decryptBox } from "./crypto/ente"; +import { toB64 } from "./crypto/libsodium"; /** * Return the user's master key (as a base64 string) from session storage. * * Precondition: The user should be logged in. */ -export const masterKeyB64FromSession = async () => { +export const masterKeyFromSession = async () => { // TODO: Same value as the deprecated SESSION_KEYS.ENCRYPTION_KEY. const value = sessionStorage.getItem("encryptionKey"); if (!value) { @@ -19,10 +20,15 @@ export const masterKeyB64FromSession = async () => { JSON.parse(value), ); - const cryptoWorker = await sharedCryptoWorker(); - return cryptoWorker.decryptB64(encryptedData, nonce, key); + return decryptBox({ encryptedData, nonce }, key); }; +/** + * Variant of {@link masterKeyFromSession} that returns the master key as a + * base64 string. + */ +export const masterKeyB64FromSession = () => masterKeyFromSession().then(toB64); + // TODO: Same as B64EncryptionResult. Revisit. const EncryptionKeyAttributes = z.object({ encryptedData: z.string(), From 5c0935973861144aa3a21d3bb7139d2062db16da Mon Sep 17 00:00:00 2001 From: Manav Rathi Date: Sat, 17 Aug 2024 18:54:50 +0530 Subject: [PATCH 15/36] Remove unnecessary conversion --- web/packages/new/photos/services/user-entity.ts | 13 +++++-------- 1 file changed, 5 insertions(+), 8 deletions(-) diff --git a/web/packages/new/photos/services/user-entity.ts b/web/packages/new/photos/services/user-entity.ts index 5902573088..fc9865b6eb 100644 --- a/web/packages/new/photos/services/user-entity.ts +++ b/web/packages/new/photos/services/user-entity.ts @@ -2,7 +2,7 @@ import { decryptAssociatedDataB64, decryptBoxB64 } from "@/base/crypto/ente"; import { authenticatedRequestHeaders, ensureOk, HTTPError } from "@/base/http"; import { getKV, getKVN, setKV } from "@/base/kv"; import { apiURL } from "@/base/origins"; -import { masterKeyB64FromSession } from "@/base/session-store"; +import { masterKeyFromSession } from "@/base/session-store"; import { ensure } from "@/utils/ensure"; import { nullToUndefined } from "@/utils/transform"; import { z } from "zod"; @@ -236,17 +236,14 @@ const saveRemoteUserEntityKey = ( /** * Decrypt an encrypted entity key using the user's master key. */ -const decryptEntityKey = async ({ - encryptedKey, - header, -}: RemoteUserEntityKey) => +const decryptEntityKey = async (remote: RemoteUserEntityKey) => decryptBoxB64( { - encryptedData: encryptedKey, + encryptedData: remote.encryptedKey, // Remote calls it the header, but it really is the nonce. - nonce: header, + nonce: remote.header, }, - await masterKeyB64FromSession(), + await masterKeyFromSession(), ); /** From 360113c3aca2a4f2d0f38acb1d22796077ebbafe Mon Sep 17 00:00:00 2001 From: Manav Rathi Date: Sat, 17 Aug 2024 20:21:36 +0530 Subject: [PATCH 16/36] Use newer pattern in more places --- .../src/services/upload/uploadService.ts | 7 +-- web/packages/base/crypto/ente-impl.ts | 47 +++++++++--------- web/packages/base/crypto/ente.ts | 43 +++++++++------- web/packages/base/crypto/libsodium.ts | 49 ++++++++++++++----- web/packages/base/crypto/types.ts | 42 ++++++++++++++++ web/packages/new/photos/services/file-data.ts | 12 +++-- 6 files changed, 136 insertions(+), 64 deletions(-) diff --git a/web/apps/photos/src/services/upload/uploadService.ts b/web/apps/photos/src/services/upload/uploadService.ts index 42c2706105..fca23c01b0 100644 --- a/web/apps/photos/src/services/upload/uploadService.ts +++ b/web/apps/photos/src/services/upload/uploadService.ts @@ -1119,11 +1119,8 @@ const encryptFile = async ( const { encryptedData: thumbEncryptedData, - decryptionHeaderB64: thumbDecryptionHeader, - } = await worker.encryptThumbnail({ - data: file.thumbnail, - keyB64: fileKey, - }); + decryptionHeader: thumbDecryptionHeader, + } = await worker.encryptThumbnail(file.thumbnail, fileKey); const encryptedThumbnail = { encryptedData: thumbEncryptedData, decryptionHeader: thumbDecryptionHeader, diff --git a/web/packages/base/crypto/ente-impl.ts b/web/packages/base/crypto/ente-impl.ts index 305cee31e7..d928d3a8e1 100644 --- a/web/packages/base/crypto/ente-impl.ts +++ b/web/packages/base/crypto/ente-impl.ts @@ -1,35 +1,36 @@ /** Careful when adding add other imports! */ import * as libsodium from "./libsodium"; -import type { - DecryptBlobB64, - EncryptBytes, - EncryptedBlobB64, - EncryptedBlobBytes, - EncryptJSON, -} from "./types"; - -const EncryptedBlobBytesToB64 = async ({ - encryptedData, - decryptionHeaderB64, -}: EncryptedBlobBytes): Promise => ({ - encryptedDataB64: await libsodium.toB64(encryptedData), - decryptionHeaderB64, -}); +import type { BytesOrB64, DecryptBlobB64, EncryptJSON } from "./types"; export const _encryptBoxB64 = libsodium.encryptBoxB64; -export const _encryptAssociatedData = libsodium.encryptBlob; +export const _encryptBlob = libsodium.encryptBlob; -export const _encryptThumbnail = _encryptAssociatedData; +export const _encryptBlobB64 = libsodium.encryptBlobB64; -export const _encryptAssociatedDataB64 = (r: EncryptBytes) => - _encryptAssociatedData(r).then(EncryptedBlobBytesToB64); +export const _encryptThumbnail = async (data: BytesOrB64, key: BytesOrB64) => { + const { encryptedData, decryptionHeader } = await _encryptBlob(data, key); + return { + encryptedData, + decryptionHeader: await libsodium.toB64(decryptionHeader), + }; +}; -export const _encryptMetadataJSON = ({ jsonValue, keyB64 }: EncryptJSON) => - _encryptAssociatedDataB64({ - data: new TextEncoder().encode(JSON.stringify(jsonValue)), +export const _encryptMetadataJSON_New = ({ jsonValue, keyB64 }: EncryptJSON) => + _encryptBlobB64( + new TextEncoder().encode(JSON.stringify(jsonValue)), keyB64, - }); + ); + +export const _encryptMetadataJSON = async (r: EncryptJSON) => { + // Deprecated. Keep the old API for now. + const { encryptedData, decryptionHeader } = + await _encryptMetadataJSON_New(r); + return { + encryptedDataB64: encryptedData, + decryptionHeaderB64: decryptionHeader, + }; +}; export const _decryptBox = libsodium.decryptBox2; diff --git a/web/packages/base/crypto/ente.ts b/web/packages/base/crypto/ente.ts index 01682c2ec5..9265f615a9 100644 --- a/web/packages/base/crypto/ente.ts +++ b/web/packages/base/crypto/ente.ts @@ -55,7 +55,6 @@ import type { BytesOrB64, DecryptBlobB64, DecryptBlobBytes, - EncryptBytes, EncryptedBox2, EncryptJSON, } from "./types"; @@ -92,34 +91,40 @@ export const encryptBoxB64 = (data: BytesOrB64, key: BytesOrB64) => : sharedCryptoWorker().then((w) => w.encryptBoxB64(data, key)); /** - * Encrypt arbitrary data associated with an Ente object (file, collection, - * entity) using the object's key. + * Encrypt the given data, returning a blob containing the encrypted data and a + * decryption header. * - * Use {@link decryptAssociatedData} to decrypt the result. + * This function is usually used to encrypt data associated with an Ente object + * (file, collection, entity) using the object's key. * - * See {@link encryptBlob} for the implementation details. + * Use {@link decryptBlobB64} to decrypt the result. + * + * > The suffix "Blob" comes from our convention of naming functions that use + * > the secretstream APIs in one-shot mode. + * > + * > See: [Note: 3 forms of encryption (Box | Blob | Stream)] */ -export const encryptAssociatedData = (r: EncryptBytes) => - assertInWorker(ei._encryptAssociatedData(r)); +export const encryptBlob = (data: BytesOrB64, key: BytesOrB64) => + assertInWorker(ei._encryptBlob(data, key)); + +/** + * A variant of {@link encryptBlob} that returns the result components as base64 + * strings. + */ +export const encryptBlobB64 = (data: BytesOrB64, key: BytesOrB64) => + assertInWorker(ei._encryptBlobB64(data, key)); /** * Encrypt the thumbnail for a file. * - * This is just an alias for {@link encryptAssociatedData}. + * This is midway variant of {@link encryptBlob} and {@link encryptBlobB64} that + * returns the decryption header as a base64 string, but leaves the data + * unchanged. * * Use {@link decryptThumbnail} to decrypt the result. */ -export const encryptThumbnail = (r: EncryptBytes) => - assertInWorker(ei._encryptThumbnail(r)); - -/** - * A variant of {@link encryptAssociatedData} that returns the encrypted data as - * a base64 string instead of returning its bytes. - * - * Use {@link decryptAssociatedDataB64} to decrypt the result. - */ -export const encryptAssociatedDataB64 = (r: EncryptBytes) => - assertInWorker(ei._encryptAssociatedDataB64(r)); +export const encryptThumbnail = (data: BytesOrB64, key: BytesOrB64) => + assertInWorker(ei._encryptThumbnail(data, key)); /** * Encrypt the JSON metadata associated with an Ente object (file, collection or diff --git a/web/packages/base/crypto/libsodium.ts b/web/packages/base/crypto/libsodium.ts index bb19f53f6e..73e96a6078 100644 --- a/web/packages/base/crypto/libsodium.ts +++ b/web/packages/base/crypto/libsodium.ts @@ -16,7 +16,8 @@ import type { DecryptBlobBytes, DecryptBoxBytes, EncryptBytes, - EncryptedBlobBytes, + EncryptedBlob_2, + EncryptedBlobB64_2, EncryptedBox2, EncryptedBoxB64, EncryptedBoxBytes, @@ -140,6 +141,8 @@ const bytes = async (bob: BytesOrB64) => * * @param key The key to use for encryption. * + * @returns The encrypted data and the generated nonce, both as base64 strings. + * * [Note: 3 forms of encryption (Box | Blob | Stream)] * * libsodium provides two "high level" encryption patterns: @@ -240,7 +243,8 @@ export const encryptBoxB64 = async ( }; }; -export const encryptBox = async ({ +/** deprecated + needs rename */ +const encryptBox = async ({ data, keyB64, }: EncryptBytes): Promise => { @@ -255,22 +259,27 @@ export const encryptBox = async ({ }; /** - * Encrypt the given data using secretstream APIs in one-shot mode, using the - * given base64 encoded key. + * Encrypt the given data using libsodium's secretstream APIs in one-shot mode. * * Use {@link decryptBlob} to decrypt the result. * - * See: [Note: 3 forms of encryption (Box | Blob | Stream)]. + * @param data The data to encrypt. * - * See: https://doc.libsodium.org/secret-key_cryptography/secretstream + * @param key The key to use for encryption. + * + * @returns The encrypted data and the decryption header as {@link Uint8Array}s. + * + * - See: [Note: 3 forms of encryption (Box | Blob | Stream)]. + * + * - See: https://doc.libsodium.org/secret-key_cryptography/secretstream */ -export const encryptBlob = async ({ - data, - keyB64, -}: EncryptBytes): Promise => { +export const encryptBlob = async ( + data: BytesOrB64, + key: BytesOrB64, +): Promise => { await sodium.ready; - const uintkey: Uint8Array = await fromB64(keyB64); + const uintkey = await bytes(key); const initPushResult = sodium.crypto_secretstream_xchacha20poly1305_init_push(uintkey); const [pushState, header] = [initPushResult.state, initPushResult.header]; @@ -283,10 +292,26 @@ export const encryptBlob = async ({ ); return { encryptedData: pushResult, - decryptionHeaderB64: await toB64(header), + decryptionHeader: header, }; }; +/** + * A variant of {@link encryptBlob} that returns the both the encrypted data and + * decryption header as base64 strings. + */ +export const encryptBlobB64 = async ( + data: BytesOrB64, + key: BytesOrB64, +): Promise => { + const { encryptedData, decryptionHeader} = await encryptBlob(data, key); + return { + encryptedData: await toB64(encryptedData), + decryptionHeader: await toB64(decryptionHeader), + }; +}; + + export const ENCRYPTION_CHUNK_SIZE = 4 * 1024 * 1024; export const encryptChaCha = async (data: Uint8Array) => { diff --git a/web/packages/base/crypto/types.ts b/web/packages/base/crypto/types.ts index 8795267338..19e4e1e7eb 100644 --- a/web/packages/base/crypto/types.ts +++ b/web/packages/base/crypto/types.ts @@ -100,6 +100,48 @@ export interface EncryptedBoxB64 { nonce: string; } +/** + * The result of encryption using the secretstream APIs in one-shot mode. + * + * It contains an encrypted data and a header that should be provided during + * decryption. The header does not need to be secret. + * + * See: [Note: 3 forms of encryption (Box | Blob | Stream)]. + */ +export interface EncryptedBlob_2 { + /** + * The encrypted data. + */ + encryptedData: Uint8Array; + /** + * The decryption header. + * + * While the exact contents of the header are libsodium's internal details, + * it effectively contains a random nonce generated by libsodium. It does + * not need to be secret, but it is required to decrypt the data. + */ + decryptionHeader: Uint8Array; +} + +/** + * A variant of {@link EncryptedBlob_2} that has the encrypted data and header + * as base64 strings. + */ +export interface EncryptedBlobB64_2 { + /** + * The encrypted data as a base64 string. + */ + encryptedData: string; + /** + * A base64 string containing the decryption header. + * + * While the exact contents of the header are libsodium's internal details, + * it effectively contains a random nonce generated by libsodium. It does + * not need to be secret, but it is required to decrypt the data. + */ + decryptionHeader: string; +} + /** * The result of encryption using the secretbox APIs. * diff --git a/web/packages/new/photos/services/file-data.ts b/web/packages/new/photos/services/file-data.ts index 0b3f1ef0b8..df4ddbf9f4 100644 --- a/web/packages/new/photos/services/file-data.ts +++ b/web/packages/new/photos/services/file-data.ts @@ -1,4 +1,4 @@ -import { encryptAssociatedDataB64 } from "@/base/crypto/ente"; +import { encryptBlobB64 } from "@/base/crypto/ente"; import { authenticatedRequestHeaders, ensureOk } from "@/base/http"; import { apiURL } from "@/base/origins"; import type { EnteFile } from "@/new/photos/types/file"; @@ -94,8 +94,10 @@ export const putFileData = async ( type: FileDataType, data: Uint8Array, ) => { - const { encryptedDataB64, decryptionHeaderB64 } = - await encryptAssociatedDataB64({ data: data, keyB64: enteFile.key }); + const { encryptedData, decryptionHeader } = await encryptBlobB64( + data, + enteFile.key, + ); const res = await fetch(await apiURL("/files/data"), { method: "PUT", @@ -103,8 +105,8 @@ export const putFileData = async ( body: JSON.stringify({ fileID: enteFile.id, type, - encryptedData: encryptedDataB64, - decryptionHeader: decryptionHeaderB64, + encryptedData, + decryptionHeader, }), }); ensureOk(res); From 2775917e44536b46fd855935d588b606ce8f4164 Mon Sep 17 00:00:00 2001 From: Manav Rathi Date: Sat, 17 Aug 2024 20:44:15 +0530 Subject: [PATCH 17/36] Parallel hierarchy --- web/packages/base/crypto/ente-impl.ts | 4 ++++ web/packages/base/crypto/ente.ts | 20 ++++++++++++++-- web/packages/base/crypto/libsodium.ts | 34 ++++++++++++++++++++++++--- web/packages/base/crypto/types.ts | 23 ++++++++++++++++++ web/packages/base/crypto/worker.ts | 1 + 5 files changed, 77 insertions(+), 5 deletions(-) diff --git a/web/packages/base/crypto/ente-impl.ts b/web/packages/base/crypto/ente-impl.ts index d928d3a8e1..875510f0bf 100644 --- a/web/packages/base/crypto/ente-impl.ts +++ b/web/packages/base/crypto/ente-impl.ts @@ -36,6 +36,10 @@ export const _decryptBox = libsodium.decryptBox2; export const _decryptBoxB64 = libsodium.decryptBoxB64; +export const _decryptBlob = libsodium.decryptBlob2; + +export const _decryptBlobB64 = libsodium.decryptBlobB64; + export const _decryptAssociatedData = libsodium.decryptBlob; export const _decryptThumbnail = _decryptAssociatedData; diff --git a/web/packages/base/crypto/ente.ts b/web/packages/base/crypto/ente.ts index 9265f615a9..f568e6ed5e 100644 --- a/web/packages/base/crypto/ente.ts +++ b/web/packages/base/crypto/ente.ts @@ -55,6 +55,7 @@ import type { BytesOrB64, DecryptBlobB64, DecryptBlobBytes, + EncryptedBlob_2, EncryptedBox2, EncryptJSON, } from "./types"; @@ -97,7 +98,7 @@ export const encryptBoxB64 = (data: BytesOrB64, key: BytesOrB64) => * This function is usually used to encrypt data associated with an Ente object * (file, collection, entity) using the object's key. * - * Use {@link decryptBlobB64} to decrypt the result. + * Use {@link decryptBlob} to decrypt the result. * * > The suffix "Blob" comes from our convention of naming functions that use * > the secretstream APIs in one-shot mode. @@ -155,13 +156,28 @@ export const decryptBox = (box: EncryptedBox2, key: BytesOrB64) => : sharedCryptoWorker().then((w) => w.decryptBox(box, key)); /** - * Variant of {@link decryptBoxlink} that returns the result as a base64 string. + * Variant of {@link decryptBox} that returns the result as a base64 string. */ export const decryptBoxB64 = (box: EncryptedBox2, key: BytesOrB64) => inWorker() ? ei._decryptBoxB64(box, key) : sharedCryptoWorker().then((w) => w.decryptBoxB64(box, key)); +/** + * Decrypt a blob encrypted using either {@link encryptBlob} or + * {@link encryptBlobB64}. + */ +export const decryptBlob = (blob: EncryptedBlob_2, key: BytesOrB64) => + assertInWorker(ei._decryptBlob(blob, key)); + +/** + * A variant of {@link decryptBlob} that returns the result as a base64 string. + */ +export const decryptBlobB64 = (blob: EncryptedBlob_2, key: BytesOrB64) => + inWorker() + ? ei._decryptBlobB64(blob, key) + : sharedCryptoWorker().then((w) => w.decryptBlobB64(blob, key)); + /** * Decrypt arbitrary data associated with an Ente object (file, collection or * entity) using the object's key. diff --git a/web/packages/base/crypto/libsodium.ts b/web/packages/base/crypto/libsodium.ts index 73e96a6078..f49ee4e2a1 100644 --- a/web/packages/base/crypto/libsodium.ts +++ b/web/packages/base/crypto/libsodium.ts @@ -18,6 +18,7 @@ import type { EncryptBytes, EncryptedBlob_2, EncryptedBlobB64_2, + EncryptedBlobBytes_2, EncryptedBox2, EncryptedBoxB64, EncryptedBoxBytes, @@ -276,7 +277,7 @@ const encryptBox = async ({ export const encryptBlob = async ( data: BytesOrB64, key: BytesOrB64, -): Promise => { +): Promise => { await sodium.ready; const uintkey = await bytes(key); @@ -304,14 +305,13 @@ export const encryptBlobB64 = async ( data: BytesOrB64, key: BytesOrB64, ): Promise => { - const { encryptedData, decryptionHeader} = await encryptBlob(data, key); + const { encryptedData, decryptionHeader } = await encryptBlob(data, key); return { encryptedData: await toB64(encryptedData), decryptionHeader: await toB64(decryptionHeader), }; }; - export const ENCRYPTION_CHUNK_SIZE = 4 * 1024 * 1024; export const encryptChaCha = async (data: Uint8Array) => { @@ -425,6 +425,34 @@ export const decryptBoxB64 = ( key: BytesOrB64, ): Promise => decryptBox2(box, key).then(toB64); +/** + * Decrypt the result of {@link encryptBlob} or {@link encryptBlobB64}. + */ +export const decryptBlob2 = async ( + { encryptedData, decryptionHeader }: EncryptedBlob_2, + key: BytesOrB64, +): Promise => { + await sodium.ready; + const pullState = sodium.crypto_secretstream_xchacha20poly1305_init_pull( + await bytes(decryptionHeader), + await bytes(key), + ); + const pullResult = sodium.crypto_secretstream_xchacha20poly1305_pull( + pullState, + await bytes(encryptedData), + null, + ); + return pullResult.message; +}; + +/** + * A variant of {@link decryptBlob2} that returns the result as a base64 string. + */ +export const decryptBlobB64 = ( + blob: EncryptedBlob_2, + key: BytesOrB64, +): Promise => decryptBlob2(blob, key).then(toB64); + /** * Decrypt the result of {@link encryptBlob}. */ diff --git a/web/packages/base/crypto/types.ts b/web/packages/base/crypto/types.ts index 19e4e1e7eb..9dd2e30e37 100644 --- a/web/packages/base/crypto/types.ts +++ b/web/packages/base/crypto/types.ts @@ -107,8 +107,31 @@ export interface EncryptedBoxB64 { * decryption. The header does not need to be secret. * * See: [Note: 3 forms of encryption (Box | Blob | Stream)]. + * + * This type is a combination of {@link EncryptedBlobBytes_2} and + * {@link EncryptedBlobB64_2} which allows the decryption routines to accept + * either the bytes or the base64 variants produced by the encryption routines. */ export interface EncryptedBlob_2 { + /** + * The encrypted data. + */ + encryptedData: BytesOrB64; + /** + * The decryption header. + * + * While the exact contents of the header are libsodium's internal details, + * it effectively contains a random nonce generated by libsodium. It does + * not need to be secret, but it is required to decrypt the data. + */ + decryptionHeader: BytesOrB64; +} + +/** + * A variant of {@link EncryptedBlob_2} that has the encrypted data and header + * as bytes ({@link Uint8Array}s). + */ +export interface EncryptedBlobBytes_2 { /** * The encrypted data. */ diff --git a/web/packages/base/crypto/worker.ts b/web/packages/base/crypto/worker.ts index 13e36c546d..1e8b930d38 100644 --- a/web/packages/base/crypto/worker.ts +++ b/web/packages/base/crypto/worker.ts @@ -17,6 +17,7 @@ export class CryptoWorker { encryptMetadataJSON = ei._encryptMetadataJSON; decryptBox = ei._decryptBox; decryptBoxB64 = ei._decryptBoxB64; + decryptBlobB64 = ei._decryptBlobB64; decryptThumbnail = ei._decryptThumbnail; decryptAssociatedDataB64 = ei._decryptAssociatedDataB64; decryptMetadataJSON = ei._decryptMetadataJSON; From 4fa3b177c6f486c2208858346c840fb50a943794 Mon Sep 17 00:00:00 2001 From: Manav Rathi Date: Sat, 17 Aug 2024 20:48:41 +0530 Subject: [PATCH 18/36] Use --- web/packages/base/crypto/ente.ts | 11 ----------- web/packages/new/photos/services/ml/ml-data.ts | 8 ++------ 2 files changed, 2 insertions(+), 17 deletions(-) diff --git a/web/packages/base/crypto/ente.ts b/web/packages/base/crypto/ente.ts index f568e6ed5e..b943e886a0 100644 --- a/web/packages/base/crypto/ente.ts +++ b/web/packages/base/crypto/ente.ts @@ -178,17 +178,6 @@ export const decryptBlobB64 = (blob: EncryptedBlob_2, key: BytesOrB64) => ? ei._decryptBlobB64(blob, key) : sharedCryptoWorker().then((w) => w.decryptBlobB64(blob, key)); -/** - * Decrypt arbitrary data associated with an Ente object (file, collection or - * entity) using the object's key. - * - * This is the sibling of {@link encryptAssociatedData}. - * - * See {@link decryptBlob} for the implementation details. - */ -export const decryptAssociatedData = (r: DecryptBlobBytes) => - assertInWorker(ei._decryptAssociatedData(r)); - /** * Decrypt the thumbnail for a file. * diff --git a/web/packages/new/photos/services/ml/ml-data.ts b/web/packages/new/photos/services/ml/ml-data.ts index 4c82ef426f..d9ec5a8425 100644 --- a/web/packages/new/photos/services/ml/ml-data.ts +++ b/web/packages/new/photos/services/ml/ml-data.ts @@ -1,4 +1,4 @@ -import { decryptAssociatedDataB64 } from "@/base/crypto/ente"; +import { decryptBlob } from "@/base/crypto/ente"; import log from "@/base/log"; import type { EnteFile } from "@/new/photos/types/file"; import { nullToUndefined } from "@/utils/transform"; @@ -172,11 +172,7 @@ export const fetchMLData = async ( } try { - const decryptedBytes = await decryptAssociatedDataB64({ - encryptedDataB64: remoteFileData.encryptedData, - decryptionHeaderB64: remoteFileData.decryptionHeader, - keyB64: file.key, - }); + const decryptedBytes = await decryptBlob(remoteFileData, file.key); const jsonString = await gunzip(decryptedBytes); result.set(fileID, remoteMLDataFromJSONString(jsonString)); } catch (e) { From 0fcd21f61d1585c1200fb6af1fdd22c52a5a2181 Mon Sep 17 00:00:00 2001 From: Manav Rathi Date: Sat, 17 Aug 2024 20:52:24 +0530 Subject: [PATCH 19/36] Use --- web/packages/base/crypto/ente.ts | 10 ---------- web/packages/base/crypto/worker.ts | 1 - web/packages/new/photos/services/user-entity.ts | 10 +++------- 3 files changed, 3 insertions(+), 18 deletions(-) diff --git a/web/packages/base/crypto/ente.ts b/web/packages/base/crypto/ente.ts index b943e886a0..2ce57f3395 100644 --- a/web/packages/base/crypto/ente.ts +++ b/web/packages/base/crypto/ente.ts @@ -186,16 +186,6 @@ export const decryptBlobB64 = (blob: EncryptedBlob_2, key: BytesOrB64) => export const decryptThumbnail = (r: DecryptBlobBytes) => assertInWorker(ei._decryptThumbnail(r)); -/** - * A variant of {@link decryptAssociatedData} that expects the encrypted data as - * a base64 encoded string. - * - * This is the sibling of {@link encryptAssociatedDataB64}. - */ -export const decryptAssociatedDataB64 = (r: DecryptBlobB64) => - inWorker() - ? ei._decryptAssociatedDataB64(r) - : sharedCryptoWorker().then((w) => w.decryptAssociatedDataB64(r)); /** * Decrypt the metadata JSON associated with an Ente object. * diff --git a/web/packages/base/crypto/worker.ts b/web/packages/base/crypto/worker.ts index 1e8b930d38..547a458d8a 100644 --- a/web/packages/base/crypto/worker.ts +++ b/web/packages/base/crypto/worker.ts @@ -19,7 +19,6 @@ export class CryptoWorker { decryptBoxB64 = ei._decryptBoxB64; decryptBlobB64 = ei._decryptBlobB64; decryptThumbnail = ei._decryptThumbnail; - decryptAssociatedDataB64 = ei._decryptAssociatedDataB64; decryptMetadataJSON = ei._decryptMetadataJSON; // TODO: -- AUDIT BELOW -- diff --git a/web/packages/new/photos/services/user-entity.ts b/web/packages/new/photos/services/user-entity.ts index fc9865b6eb..7ea981df09 100644 --- a/web/packages/new/photos/services/user-entity.ts +++ b/web/packages/new/photos/services/user-entity.ts @@ -1,4 +1,4 @@ -import { decryptAssociatedDataB64, decryptBoxB64 } from "@/base/crypto/ente"; +import { decryptBlob, decryptBoxB64 } from "@/base/crypto/ente"; import { authenticatedRequestHeaders, ensureOk, HTTPError } from "@/base/http"; import { getKV, getKVN, setKV } from "@/base/kv"; import { apiURL } from "@/base/origins"; @@ -134,12 +134,8 @@ const userEntityDiff = async ( sinceTime: number, entityKeyB64: string, ): Promise => { - const decrypt = (encryptedDataB64: string, decryptionHeaderB64: string) => - decryptAssociatedDataB64({ - encryptedDataB64, - decryptionHeaderB64, - keyB64: entityKeyB64, - }); + const decrypt = (encryptedData: string, decryptionHeader: string) => + decryptBlob({ encryptedData, decryptionHeader }, entityKeyB64); const params = new URLSearchParams({ type, From fdfaadfb1e683c1fd14741ab4784be1583af3877 Mon Sep 17 00:00:00 2001 From: Manav Rathi Date: Sat, 17 Aug 2024 20:56:05 +0530 Subject: [PATCH 20/36] Use --- web/packages/base/crypto/ente-impl.ts | 2 +- web/packages/base/crypto/ente.ts | 9 +++------ web/packages/new/photos/services/download.ts | 12 +++++++----- 3 files changed, 11 insertions(+), 12 deletions(-) diff --git a/web/packages/base/crypto/ente-impl.ts b/web/packages/base/crypto/ente-impl.ts index 875510f0bf..b1be404067 100644 --- a/web/packages/base/crypto/ente-impl.ts +++ b/web/packages/base/crypto/ente-impl.ts @@ -42,7 +42,7 @@ export const _decryptBlobB64 = libsodium.decryptBlobB64; export const _decryptAssociatedData = libsodium.decryptBlob; -export const _decryptThumbnail = _decryptAssociatedData; +export const _decryptThumbnail = _decryptBlob; export const _decryptAssociatedDataB64 = async ({ encryptedDataB64, diff --git a/web/packages/base/crypto/ente.ts b/web/packages/base/crypto/ente.ts index 2ce57f3395..95d0ce5aed 100644 --- a/web/packages/base/crypto/ente.ts +++ b/web/packages/base/crypto/ente.ts @@ -54,7 +54,6 @@ import * as ei from "./ente-impl"; import type { BytesOrB64, DecryptBlobB64, - DecryptBlobBytes, EncryptedBlob_2, EncryptedBox2, EncryptJSON, @@ -179,12 +178,10 @@ export const decryptBlobB64 = (blob: EncryptedBlob_2, key: BytesOrB64) => : sharedCryptoWorker().then((w) => w.decryptBlobB64(blob, key)); /** - * Decrypt the thumbnail for a file. - * - * This is the sibling of {@link encryptThumbnail}. + * Decrypt the thumbnail encrypted using {@link encryptThumbnail}. */ -export const decryptThumbnail = (r: DecryptBlobBytes) => - assertInWorker(ei._decryptThumbnail(r)); +export const decryptThumbnail = (blob: EncryptedBlob_2, key: BytesOrB64) => + assertInWorker(ei._decryptThumbnail(blob, key)); /** * Decrypt the metadata JSON associated with an Ente object. diff --git a/web/packages/new/photos/services/download.ts b/web/packages/new/photos/services/download.ts index a5b06c27cf..47a0dbfa58 100644 --- a/web/packages/new/photos/services/download.ts +++ b/web/packages/new/photos/services/download.ts @@ -124,11 +124,13 @@ class DownloadManagerImpl { const { downloadClient, cryptoWorker } = this.ensureInitialized(); const encrypted = await downloadClient.downloadThumbnail(file); - const decrypted = await cryptoWorker.decryptThumbnail({ - encryptedData: encrypted, - decryptionHeaderB64: file.thumbnail.decryptionHeader, - keyB64: file.key, - }); + const decrypted = await cryptoWorker.decryptThumbnail( + { + encryptedData: encrypted, + decryptionHeader: file.thumbnail.decryptionHeader, + }, + file.key, + ); return decrypted; }; From 68e5d842e5385c4d614e5fb06fccca326a5ebf22 Mon Sep 17 00:00:00 2001 From: Manav Rathi Date: Sat, 17 Aug 2024 20:57:20 +0530 Subject: [PATCH 21/36] Tweak --- web/packages/new/photos/services/download.ts | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) diff --git a/web/packages/new/photos/services/download.ts b/web/packages/new/photos/services/download.ts index 47a0dbfa58..a3f52e73c2 100644 --- a/web/packages/new/photos/services/download.ts +++ b/web/packages/new/photos/services/download.ts @@ -123,15 +123,12 @@ class DownloadManagerImpl { private downloadThumb = async (file: EnteFile) => { const { downloadClient, cryptoWorker } = this.ensureInitialized(); - const encrypted = await downloadClient.downloadThumbnail(file); - const decrypted = await cryptoWorker.decryptThumbnail( - { - encryptedData: encrypted, - decryptionHeader: file.thumbnail.decryptionHeader, - }, + const encryptedData = await downloadClient.downloadThumbnail(file); + const decryptionHeader = file.thumbnail.decryptionHeader; + return cryptoWorker.decryptThumbnail( + { encryptedData, decryptionHeader }, file.key, ); - return decrypted; }; async getThumbnail(file: EnteFile, localOnly = false) { From 757ff5cd9a8e07ed35faa46868231708cfbfbd29 Mon Sep 17 00:00:00 2001 From: Manav Rathi Date: Sat, 17 Aug 2024 21:02:34 +0530 Subject: [PATCH 22/36] New --- web/packages/base/crypto/ente-impl.ts | 15 ++++++++++++++- web/packages/base/crypto/ente.ts | 16 ++++++++++++++++ web/packages/base/crypto/worker.ts | 1 + 3 files changed, 31 insertions(+), 1 deletion(-) diff --git a/web/packages/base/crypto/ente-impl.ts b/web/packages/base/crypto/ente-impl.ts index b1be404067..213df8b13c 100644 --- a/web/packages/base/crypto/ente-impl.ts +++ b/web/packages/base/crypto/ente-impl.ts @@ -1,6 +1,11 @@ /** Careful when adding add other imports! */ import * as libsodium from "./libsodium"; -import type { BytesOrB64, DecryptBlobB64, EncryptJSON } from "./types"; +import type { + BytesOrB64, + DecryptBlobB64, + EncryptedBlob_2, + EncryptJSON, +} from "./types"; export const _encryptBoxB64 = libsodium.encryptBoxB64; @@ -55,6 +60,14 @@ export const _decryptAssociatedDataB64 = async ({ keyB64, }); +export const _decryptMetadataJSON_New = async ( + blob: EncryptedBlob_2, + key: BytesOrB64, +) => + JSON.parse( + new TextDecoder().decode(await _decryptBlob(blob, key)), + ) as unknown; + export const _decryptMetadataJSON = async (r: DecryptBlobB64) => JSON.parse( new TextDecoder().decode(await _decryptAssociatedDataB64(r)), diff --git a/web/packages/base/crypto/ente.ts b/web/packages/base/crypto/ente.ts index 95d0ce5aed..2ef5ef9a57 100644 --- a/web/packages/base/crypto/ente.ts +++ b/web/packages/base/crypto/ente.ts @@ -183,6 +183,22 @@ export const decryptBlobB64 = (blob: EncryptedBlob_2, key: BytesOrB64) => export const decryptThumbnail = (blob: EncryptedBlob_2, key: BytesOrB64) => assertInWorker(ei._decryptThumbnail(blob, key)); +/** + * Decrypt the metadata JSON encrypted using {@link encryptMetadataJSON}. + * + * @returns The decrypted JSON value. Since TypeScript does not have a native + * JSON type, we need to return it as an `unknown`. + */ +export const decryptMetadataJSON_New = ( + blob: EncryptedBlob_2, + key: BytesOrB64, +) => + inWorker() + ? ei._decryptMetadataJSON_New(blob, key) + : sharedCryptoWorker().then((w) => + w.decryptMetadataJSON_New(blob, key), + ); + /** * Decrypt the metadata JSON associated with an Ente object. * diff --git a/web/packages/base/crypto/worker.ts b/web/packages/base/crypto/worker.ts index 547a458d8a..c87ce36b42 100644 --- a/web/packages/base/crypto/worker.ts +++ b/web/packages/base/crypto/worker.ts @@ -19,6 +19,7 @@ export class CryptoWorker { decryptBoxB64 = ei._decryptBoxB64; decryptBlobB64 = ei._decryptBlobB64; decryptThumbnail = ei._decryptThumbnail; + decryptMetadataJSON_New = ei._decryptMetadataJSON_New; decryptMetadataJSON = ei._decryptMetadataJSON; // TODO: -- AUDIT BELOW -- From 788ce533884cecf73f513e9151c4e7a5b1bb223f Mon Sep 17 00:00:00 2001 From: Manav Rathi Date: Sat, 17 Aug 2024 21:04:02 +0530 Subject: [PATCH 23/36] Use --- web/packages/base/crypto/ente-impl.ts | 10 +++++++--- web/packages/base/crypto/ente.ts | 7 +------ 2 files changed, 8 insertions(+), 9 deletions(-) diff --git a/web/packages/base/crypto/ente-impl.ts b/web/packages/base/crypto/ente-impl.ts index 213df8b13c..60bf3a7d07 100644 --- a/web/packages/base/crypto/ente-impl.ts +++ b/web/packages/base/crypto/ente-impl.ts @@ -69,6 +69,10 @@ export const _decryptMetadataJSON_New = async ( ) as unknown; export const _decryptMetadataJSON = async (r: DecryptBlobB64) => - JSON.parse( - new TextDecoder().decode(await _decryptAssociatedDataB64(r)), - ) as unknown; + _decryptMetadataJSON_New( + { + encryptedData: r.encryptedDataB64, + decryptionHeader: r.decryptionHeaderB64, + }, + r.keyB64, + ); diff --git a/web/packages/base/crypto/ente.ts b/web/packages/base/crypto/ente.ts index 2ef5ef9a57..854efcfaae 100644 --- a/web/packages/base/crypto/ente.ts +++ b/web/packages/base/crypto/ente.ts @@ -200,12 +200,7 @@ export const decryptMetadataJSON_New = ( ); /** - * Decrypt the metadata JSON associated with an Ente object. - * - * This is the sibling of {@link encryptMetadataJSON}. - * - * @returns The decrypted JSON value. Since TypeScript does not have a native - * JSON type, we need to return it as an `unknown`. + * Deprecated, retains the old API. */ export const decryptMetadataJSON = (r: DecryptBlobB64) => inWorker() From 25f1087685f815748eb451356d07fcd19dd02838 Mon Sep 17 00:00:00 2001 From: Manav Rathi Date: Sat, 17 Aug 2024 21:05:49 +0530 Subject: [PATCH 24/36] Unused --- web/packages/base/crypto/ente-impl.ts | 13 ------------- web/packages/base/crypto/libsodium.ts | 22 ---------------------- 2 files changed, 35 deletions(-) diff --git a/web/packages/base/crypto/ente-impl.ts b/web/packages/base/crypto/ente-impl.ts index 60bf3a7d07..8a0a01dacb 100644 --- a/web/packages/base/crypto/ente-impl.ts +++ b/web/packages/base/crypto/ente-impl.ts @@ -45,21 +45,8 @@ export const _decryptBlob = libsodium.decryptBlob2; export const _decryptBlobB64 = libsodium.decryptBlobB64; -export const _decryptAssociatedData = libsodium.decryptBlob; - export const _decryptThumbnail = _decryptBlob; -export const _decryptAssociatedDataB64 = async ({ - encryptedDataB64, - decryptionHeaderB64, - keyB64, -}: DecryptBlobB64) => - await _decryptAssociatedData({ - encryptedData: await libsodium.fromB64(encryptedDataB64), - decryptionHeaderB64, - keyB64, - }); - export const _decryptMetadataJSON_New = async ( blob: EncryptedBlob_2, key: BytesOrB64, diff --git a/web/packages/base/crypto/libsodium.ts b/web/packages/base/crypto/libsodium.ts index f49ee4e2a1..a7cce5710a 100644 --- a/web/packages/base/crypto/libsodium.ts +++ b/web/packages/base/crypto/libsodium.ts @@ -13,7 +13,6 @@ import { CustomError } from "@ente/shared/error"; import sodium, { type StateAddress } from "libsodium-wrappers"; import type { BytesOrB64, - DecryptBlobBytes, DecryptBoxBytes, EncryptBytes, EncryptedBlob_2, @@ -453,27 +452,6 @@ export const decryptBlobB64 = ( key: BytesOrB64, ): Promise => decryptBlob2(blob, key).then(toB64); -/** - * Decrypt the result of {@link encryptBlob}. - */ -export const decryptBlob = async ({ - encryptedData, - decryptionHeaderB64, - keyB64, -}: DecryptBlobBytes): Promise => { - await sodium.ready; - const pullState = sodium.crypto_secretstream_xchacha20poly1305_init_pull( - await fromB64(decryptionHeaderB64), - await fromB64(keyB64), - ); - const pullResult = sodium.crypto_secretstream_xchacha20poly1305_pull( - pullState, - encryptedData, - null, - ); - return pullResult.message; -}; - /** Decrypt Stream, but merge the results. */ export const decryptChaCha = async ( data: Uint8Array, From 6488c4fa0e217bfe210fd5d5cad88f462debcb92 Mon Sep 17 00:00:00 2001 From: Manav Rathi Date: Sat, 17 Aug 2024 21:10:11 +0530 Subject: [PATCH 25/36] Sigh --- web/packages/new/photos/services/ml/ml-data.ts | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/web/packages/new/photos/services/ml/ml-data.ts b/web/packages/new/photos/services/ml/ml-data.ts index d9ec5a8425..fb31bc9dcc 100644 --- a/web/packages/new/photos/services/ml/ml-data.ts +++ b/web/packages/new/photos/services/ml/ml-data.ts @@ -172,6 +172,11 @@ export const fetchMLData = async ( } try { + // TODO: This line is included in the photos app which currently + // doesn't have strict mode enabled, and where it causes a spurious + // error, so we unfortunately need to turn off typechecking. + // eslint-disable-next-line @typescript-eslint/ban-ts-comment, @typescript-eslint/prefer-ts-expect-error + // @ts-ignore const decryptedBytes = await decryptBlob(remoteFileData, file.key); const jsonString = await gunzip(decryptedBytes); result.set(fileID, remoteMLDataFromJSONString(jsonString)); From eafa662cc042cb304f61973c451a36350a861928 Mon Sep 17 00:00:00 2001 From: Manav Rathi Date: Sat, 17 Aug 2024 21:13:48 +0530 Subject: [PATCH 26/36] Prune --- web/packages/base/crypto/types.ts | 116 ++---------------------------- 1 file changed, 4 insertions(+), 112 deletions(-) diff --git a/web/packages/base/crypto/types.ts b/web/packages/base/crypto/types.ts index 9dd2e30e37..64d01576f7 100644 --- a/web/packages/base/crypto/types.ts +++ b/web/packages/base/crypto/types.ts @@ -5,21 +5,7 @@ export type BytesOrB64 = Uint8Array | string; /** - * An encryption request. - */ -export interface EncryptBytesOrB64 { - /** - * The data to encrypt. - */ - data: BytesOrB64; - /** - * The key to use for encryption. - */ - key: BytesOrB64; -} - -/** - * An encryption request with the data to encrypt provided as bytes. + * Deprecated. */ export interface EncryptBytes { /** @@ -33,20 +19,8 @@ export interface EncryptBytes { } /** - * A variant of {@link EncryptBytes} with the data as base64 encoded string. - */ -export interface EncryptB64 { - /** - * A base64 string containing the data to encrypt. - */ - dataB64: string; - /** - * A base64 string containing the encryption key. - */ - keyB64: string; -} - -/** + * Deprecated. + * * A variant of {@link EncryptBytes} with the data as a JSON value. */ export interface EncryptJSON { @@ -188,65 +162,6 @@ export interface EncryptedBoxBytes { nonceB64: string; } -/** - * A variant of {@link EncryptedBoxBytes} with the encrypted data encoded as a - * base64 string. - */ -export interface EncryptedBox64 { - /** - * A base64 string containing the encrypted data. - */ - encryptedDataB64: string; - /** - * A base64 string containing the nonce used during encryption. - * - * A randomly generated nonce for this encryption. It does not need to be - * confidential, but it will be required to decrypt the data. - */ - nonceB64: string; -} - -/** - * The result of encryption using the secretstream APIs used in one-shot mode. - * - * It contains the encrypted data (bytes) and decryption header (base64 encoded - * string) pair. Both these values are needed to decrypt the data. The header - * does not need to be secret. - * - * See: [Note: 3 forms of encryption (Box | Blob | Stream)]. - */ -export interface EncryptedBlobBytes { - /** - * A {@link Uint8Array} containing the encrypted data. - */ - encryptedData: Uint8Array; - /** - * A base64 string containing the decryption header. - * - * The header contains a random nonce and other libsodium specific metadata. - * It does not need to be secret, but it is required to decrypt the data. - */ - decryptionHeaderB64: string; -} - -/** - * A variant of {@link EncryptedBlobBytes} with the encrypted data encoded as a - * base64 string. - */ -export interface EncryptedBlobB64 { - /** - * A base64 string containing the encrypted data. - */ - encryptedDataB64: string; - /** - * A base64 string containing the decryption header. - * - * The header contains a random nonce and other libsodium specific metadata. - * It does not need to be secret, but it is required to decrypt the data. - */ - decryptionHeaderB64: string; -} - /** * A decryption request to decrypt data encrypted using the secretbox APIs. The * encrypted Box's data is provided as bytes. @@ -294,31 +209,8 @@ export interface DecryptBoxB64 { } /** - * A decryption request to decrypt data encrypted using the secretstream APIs in - * one-shot mode. The encrypted Blob's data is provided as bytes. + * Deprecated. * - * See: [Note: 3 forms of encryption (Box | Blob | Stream)]. - */ -export interface DecryptBlobBytes { - /** - * A {@link Uint8Array} containing the bytes to decrypt. - */ - encryptedData: Uint8Array; - /** - * A base64 string containing the decryption header that was produced during - * encryption. - * - * The header contains a random nonce and other libsodium metadata. It does - * not need to be kept secret. - */ - decryptionHeaderB64: string; - /** - * A base64 string containing the encryption key. - */ - keyB64: string; -} - -/** * A variant of {@link DecryptBlobBytes} with the encrypted Blob's data as a * base64 encoded string. */ From 069cf82bbb143492fe5fb769247966b264f13876 Mon Sep 17 00:00:00 2001 From: Manav Rathi Date: Sat, 17 Aug 2024 21:21:09 +0530 Subject: [PATCH 27/36] Mig --- web/packages/base/crypto/libsodium.ts | 17 ++++++---- web/packages/base/crypto/types.ts | 46 --------------------------- 2 files changed, 10 insertions(+), 53 deletions(-) diff --git a/web/packages/base/crypto/libsodium.ts b/web/packages/base/crypto/libsodium.ts index a7cce5710a..f1972de6be 100644 --- a/web/packages/base/crypto/libsodium.ts +++ b/web/packages/base/crypto/libsodium.ts @@ -13,7 +13,6 @@ import { CustomError } from "@ente/shared/error"; import sodium, { type StateAddress } from "libsodium-wrappers"; import type { BytesOrB64, - DecryptBoxBytes, EncryptBytes, EncryptedBlob_2, EncryptedBlobB64_2, @@ -135,7 +134,7 @@ const bytes = async (bob: BytesOrB64) => * Encrypt the given data using libsodium's secretbox APIs, using a randomly * generated nonce. * - * Use {@link decryptBox} to decrypt the result. + * Use {@link decryptBox_Deprecated} to decrypt the result. * * @param data The data to encrypt. * @@ -388,11 +387,15 @@ export async function encryptFileChunk( /** * Decrypt the result of {@link encryptBox}. */ -export const decryptBox = async ({ +const decryptBox_Deprecated = async ({ encryptedData, nonceB64, keyB64, -}: DecryptBoxBytes): Promise => { +}: { + encryptedData: Uint8Array; + nonceB64: string; + keyB64: string; +}): Promise => { await sodium.ready; return sodium.crypto_secretbox_open_easy( encryptedData, @@ -417,7 +420,7 @@ export const decryptBox2 = async ( }; /** - * Variant of {@link decryptBox} that returns the data as a base64 string. + * Variant of {@link decryptBox_Deprecated} that returns the data as a base64 string. */ export const decryptBoxB64 = ( box: EncryptedBox2, @@ -557,7 +560,7 @@ export async function decryptB64( keyB64: string, ) { await sodium.ready; - const decrypted = await decryptBox({ + const decrypted = await decryptBox_Deprecated({ encryptedData: await fromB64(data), nonceB64, keyB64, @@ -573,7 +576,7 @@ export async function decryptToUTF8( keyB64: string, ) { await sodium.ready; - const decrypted = await decryptBox({ + const decrypted = await decryptBox_Deprecated({ encryptedData: await fromB64(data), nonceB64, keyB64, diff --git a/web/packages/base/crypto/types.ts b/web/packages/base/crypto/types.ts index 64d01576f7..ea35e23f49 100644 --- a/web/packages/base/crypto/types.ts +++ b/web/packages/base/crypto/types.ts @@ -162,52 +162,6 @@ export interface EncryptedBoxBytes { nonceB64: string; } -/** - * A decryption request to decrypt data encrypted using the secretbox APIs. The - * encrypted Box's data is provided as bytes. - * - * See: [Note: 3 forms of encryption (Box | Blob | Stream)]. - */ -export interface DecryptBoxBytes { - /** - * A {@link Uint8Array} containing the bytes to decrypt. - */ - encryptedData: Uint8Array; - /** - * A base64 string containing the nonce that was used during encryption. - * - * The nonce is required to decrypt the data, but it does not need to be - * kept secret. - */ - nonceB64: string; - /** - * A base64 string containing the encryption key. - */ - keyB64: string; -} - -/** - * A variant of {@link DecryptBoxBytes} with the encrypted Blob's data as a - * base64 encoded string. - */ -export interface DecryptBoxB64 { - /** - * A base64 string containing the data to decrypt. - */ - encryptedDataB64: string; - /** - * A base64 string containing the nonce that was used during encryption. - * - * The nonce is required to decrypt the data, but it does not need to be - * kept secret. - */ - nonceB64: string; - /** - * A base64 string containing the encryption key. - */ - keyB64: string; -} - /** * Deprecated. * From de3943e69cbbe94b1b14d49b0f6e8a18b3aee2f9 Mon Sep 17 00:00:00 2001 From: Manav Rathi Date: Sat, 17 Aug 2024 21:22:54 +0530 Subject: [PATCH 28/36] Prune --- web/packages/base/crypto/libsodium.ts | 19 +++++++++----- web/packages/base/crypto/types.ts | 36 --------------------------- 2 files changed, 13 insertions(+), 42 deletions(-) diff --git a/web/packages/base/crypto/libsodium.ts b/web/packages/base/crypto/libsodium.ts index f1972de6be..6bcd0f2e87 100644 --- a/web/packages/base/crypto/libsodium.ts +++ b/web/packages/base/crypto/libsodium.ts @@ -13,13 +13,11 @@ import { CustomError } from "@ente/shared/error"; import sodium, { type StateAddress } from "libsodium-wrappers"; import type { BytesOrB64, - EncryptBytes, EncryptedBlob_2, EncryptedBlobB64_2, EncryptedBlobBytes_2, EncryptedBox2, EncryptedBoxB64, - EncryptedBoxBytes, } from "./types"; /** @@ -243,10 +241,16 @@ export const encryptBoxB64 = async ( }; /** deprecated + needs rename */ -const encryptBox = async ({ +const encryptBox_Deprecated = async ({ data, keyB64, -}: EncryptBytes): Promise => { +}: { + data: Uint8Array; + keyB64: string; +}): Promise<{ + encryptedData: Uint8Array; + nonceB64: string; +}> => { await sodium.ready; const nonce = sodium.randombytes_buf(sodium.crypto_secretbox_NONCEBYTES); const encryptedData = sodium.crypto_secretbox_easy( @@ -385,7 +389,7 @@ export async function encryptFileChunk( } /** - * Decrypt the result of {@link encryptBox}. + * Decrypt the result of {@link encryptBox_Deprecated}. */ const decryptBox_Deprecated = async ({ encryptedData, @@ -533,7 +537,10 @@ export interface B64EncryptionResult { export async function encryptToB64(data: string, keyB64: string) { await sodium.ready; - const encrypted = await encryptBox({ data: await fromB64(data), keyB64 }); + const encrypted = await encryptBox_Deprecated({ + data: await fromB64(data), + keyB64, + }); return { encryptedData: await toB64(encrypted.encryptedData), diff --git a/web/packages/base/crypto/types.ts b/web/packages/base/crypto/types.ts index ea35e23f49..ba0c80c395 100644 --- a/web/packages/base/crypto/types.ts +++ b/web/packages/base/crypto/types.ts @@ -4,19 +4,6 @@ */ export type BytesOrB64 = Uint8Array | string; -/** - * Deprecated. - */ -export interface EncryptBytes { - /** - * A {@link Uint8Array} containing the bytes to encrypt. - */ - data: Uint8Array; - /** - * A base64 string containing the encryption key. - */ - keyB64: string; -} /** * Deprecated. @@ -139,29 +126,6 @@ export interface EncryptedBlobB64_2 { decryptionHeader: string; } -/** - * The result of encryption using the secretbox APIs. - * - * It contains the encrypted data (bytes) and nonce (base64 encoded string) - * pair. Both these values are needed to decrypt the data. The nonce does not - * need to be secret. - * - * See: [Note: 3 forms of encryption (Box | Blob | Stream)]. - */ -export interface EncryptedBoxBytes { - /** - * A {@link Uint8Array} containing the encrypted data. - */ - encryptedData: Uint8Array; - /** - * A base64 string containing the nonce used during encryption. - * - * A randomly generated nonce for this encryption. It does not need to be - * confidential, but it will be required to decrypt the data. - */ - nonceB64: string; -} - /** * Deprecated. * From b329085940fb00d2695c1f2581acf567ba547878 Mon Sep 17 00:00:00 2001 From: Manav Rathi Date: Sat, 17 Aug 2024 21:25:57 +0530 Subject: [PATCH 29/36] Mig --- web/packages/base/crypto/libsodium.ts | 34 ++++----------------------- 1 file changed, 5 insertions(+), 29 deletions(-) diff --git a/web/packages/base/crypto/libsodium.ts b/web/packages/base/crypto/libsodium.ts index 6bcd0f2e87..0773622479 100644 --- a/web/packages/base/crypto/libsodium.ts +++ b/web/packages/base/crypto/libsodium.ts @@ -132,7 +132,7 @@ const bytes = async (bob: BytesOrB64) => * Encrypt the given data using libsodium's secretbox APIs, using a randomly * generated nonce. * - * Use {@link decryptBox_Deprecated} to decrypt the result. + * Use {@link decryptBox} to decrypt the result. * * @param data The data to encrypt. * @@ -240,27 +240,6 @@ export const encryptBoxB64 = async ( }; }; -/** deprecated + needs rename */ -const encryptBox_Deprecated = async ({ - data, - keyB64, -}: { - data: Uint8Array; - keyB64: string; -}): Promise<{ - encryptedData: Uint8Array; - nonceB64: string; -}> => { - await sodium.ready; - const nonce = sodium.randombytes_buf(sodium.crypto_secretbox_NONCEBYTES); - const encryptedData = sodium.crypto_secretbox_easy( - data, - nonce, - await fromB64(keyB64), - ); - return { encryptedData, nonceB64: await toB64(nonce) }; -}; - /** * Encrypt the given data using libsodium's secretstream APIs in one-shot mode. * @@ -535,17 +514,14 @@ export interface B64EncryptionResult { nonce: string; } +/** Deprecated, use {@link encryptBoxB64} instead */ export async function encryptToB64(data: string, keyB64: string) { await sodium.ready; - const encrypted = await encryptBox_Deprecated({ - data: await fromB64(data), - keyB64, - }); - + const encrypted = await encryptBoxB64(data, keyB64); return { - encryptedData: await toB64(encrypted.encryptedData), + encryptedData: encrypted.encryptedData, key: keyB64, - nonce: encrypted.nonceB64, + nonce: encrypted.nonce, } as B64EncryptionResult; } From 0ee84db02f75a3c809c14bc1460cdc821242ff0d Mon Sep 17 00:00:00 2001 From: Manav Rathi Date: Sat, 17 Aug 2024 21:29:40 +0530 Subject: [PATCH 30/36] Mig --- web/packages/base/crypto/libsodium.ts | 27 +++++++-------------------- 1 file changed, 7 insertions(+), 20 deletions(-) diff --git a/web/packages/base/crypto/libsodium.ts b/web/packages/base/crypto/libsodium.ts index 0773622479..6aee5ed70b 100644 --- a/web/packages/base/crypto/libsodium.ts +++ b/web/packages/base/crypto/libsodium.ts @@ -367,9 +367,6 @@ export async function encryptFileChunk( return pushResult; } -/** - * Decrypt the result of {@link encryptBox_Deprecated}. - */ const decryptBox_Deprecated = async ({ encryptedData, nonceB64, @@ -536,35 +533,25 @@ export async function encryptUTF8(data: string, key: string) { return await encryptToB64(b64Data, key); } -/** Deprecated */ +/** Deprecated, use {@link decryptBox2} instead. */ export async function decryptB64( - data: string, - nonceB64: string, + encryptedData: string, + nonce: string, keyB64: string, ) { await sodium.ready; - const decrypted = await decryptBox_Deprecated({ - encryptedData: await fromB64(data), - nonceB64, - keyB64, - }); - + const decrypted = await decryptBox2({ encryptedData, nonce }, keyB64); return await toB64(decrypted); } /** Deprecated */ export async function decryptToUTF8( - data: string, - nonceB64: string, + encryptedData: string, + nonce: string, keyB64: string, ) { await sodium.ready; - const decrypted = await decryptBox_Deprecated({ - encryptedData: await fromB64(data), - nonceB64, - keyB64, - }); - + const decrypted = await decryptBox2({ encryptedData, nonce }, keyB64); return sodium.to_string(decrypted); } From 9ae5006a4ae32a16c5b26d032ee42c74e2044fd4 Mon Sep 17 00:00:00 2001 From: Manav Rathi Date: Sat, 17 Aug 2024 21:30:54 +0530 Subject: [PATCH 31/36] Mig --- web/packages/base/crypto/libsodium.ts | 25 +++---------------------- 1 file changed, 3 insertions(+), 22 deletions(-) diff --git a/web/packages/base/crypto/libsodium.ts b/web/packages/base/crypto/libsodium.ts index 6aee5ed70b..5a1376fa79 100644 --- a/web/packages/base/crypto/libsodium.ts +++ b/web/packages/base/crypto/libsodium.ts @@ -367,23 +367,6 @@ export async function encryptFileChunk( return pushResult; } -const decryptBox_Deprecated = async ({ - encryptedData, - nonceB64, - keyB64, -}: { - encryptedData: Uint8Array; - nonceB64: string; - keyB64: string; -}): Promise => { - await sodium.ready; - return sodium.crypto_secretbox_open_easy( - encryptedData, - await fromB64(nonceB64), - await fromB64(keyB64), - ); -}; - /** * Decrypt the result of {@link encryptBoxB64}. */ @@ -400,7 +383,7 @@ export const decryptBox2 = async ( }; /** - * Variant of {@link decryptBox_Deprecated} that returns the data as a base64 string. + * Variant of {@link decryptBox} that returns the data as a base64 string. */ export const decryptBoxB64 = ( box: EncryptedBox2, @@ -533,15 +516,13 @@ export async function encryptUTF8(data: string, key: string) { return await encryptToB64(b64Data, key); } -/** Deprecated, use {@link decryptBox2} instead. */ +/** Deprecated, use {@link decryptBoxB64} instead. */ export async function decryptB64( encryptedData: string, nonce: string, keyB64: string, ) { - await sodium.ready; - const decrypted = await decryptBox2({ encryptedData, nonce }, keyB64); - return await toB64(decrypted); + return decryptBoxB64({ encryptedData, nonce }, keyB64); } /** Deprecated */ From 0240b37032f1ea66f5c41fc5e8177841817f1302 Mon Sep 17 00:00:00 2001 From: Manav Rathi Date: Sat, 17 Aug 2024 21:33:15 +0530 Subject: [PATCH 32/36] Denoise --- web/packages/base/crypto/ente-impl.ts | 13 ++++++------- web/packages/base/crypto/ente.ts | 7 +++++-- web/packages/base/crypto/types.ts | 26 -------------------------- 3 files changed, 11 insertions(+), 35 deletions(-) diff --git a/web/packages/base/crypto/ente-impl.ts b/web/packages/base/crypto/ente-impl.ts index 8a0a01dacb..1c28e3d124 100644 --- a/web/packages/base/crypto/ente-impl.ts +++ b/web/packages/base/crypto/ente-impl.ts @@ -1,11 +1,6 @@ /** Careful when adding add other imports! */ import * as libsodium from "./libsodium"; -import type { - BytesOrB64, - DecryptBlobB64, - EncryptedBlob_2, - EncryptJSON, -} from "./types"; +import type { BytesOrB64, EncryptedBlob_2, EncryptJSON } from "./types"; export const _encryptBoxB64 = libsodium.encryptBoxB64; @@ -55,7 +50,11 @@ export const _decryptMetadataJSON_New = async ( new TextDecoder().decode(await _decryptBlob(blob, key)), ) as unknown; -export const _decryptMetadataJSON = async (r: DecryptBlobB64) => +export const _decryptMetadataJSON = async (r: { + encryptedDataB64: string; + decryptionHeaderB64: string; + keyB64: string; +}) => _decryptMetadataJSON_New( { encryptedData: r.encryptedDataB64, diff --git a/web/packages/base/crypto/ente.ts b/web/packages/base/crypto/ente.ts index 854efcfaae..8feb0ccdce 100644 --- a/web/packages/base/crypto/ente.ts +++ b/web/packages/base/crypto/ente.ts @@ -53,7 +53,6 @@ import { inWorker } from "../env"; import * as ei from "./ente-impl"; import type { BytesOrB64, - DecryptBlobB64, EncryptedBlob_2, EncryptedBox2, EncryptJSON, @@ -202,7 +201,11 @@ export const decryptMetadataJSON_New = ( /** * Deprecated, retains the old API. */ -export const decryptMetadataJSON = (r: DecryptBlobB64) => +export const decryptMetadataJSON = (r: { + encryptedDataB64: string; + decryptionHeaderB64: string; + keyB64: string; +}) => inWorker() ? ei._decryptMetadataJSON(r) : sharedCryptoWorker().then((w) => w.decryptMetadataJSON(r)); diff --git a/web/packages/base/crypto/types.ts b/web/packages/base/crypto/types.ts index ba0c80c395..fdc9542062 100644 --- a/web/packages/base/crypto/types.ts +++ b/web/packages/base/crypto/types.ts @@ -4,7 +4,6 @@ */ export type BytesOrB64 = Uint8Array | string; - /** * Deprecated. * @@ -125,28 +124,3 @@ export interface EncryptedBlobB64_2 { */ decryptionHeader: string; } - -/** - * Deprecated. - * - * A variant of {@link DecryptBlobBytes} with the encrypted Blob's data as a - * base64 encoded string. - */ -export interface DecryptBlobB64 { - /** - * A base64 string containing the data to decrypt. - */ - encryptedDataB64: string; - /** - * A base64 string containing the decryption header that was produced during - * encryption. - * - * The header contains a random nonce and other libsodium metadata. It does - * not need to be kept secret. - */ - decryptionHeaderB64: string; - /** - * A base64 string containing the encryption key. - */ - keyB64: string; -} From 76da94cf7891d4468d48028b08d0252732295aab Mon Sep 17 00:00:00 2001 From: Manav Rathi Date: Sat, 17 Aug 2024 21:41:07 +0530 Subject: [PATCH 33/36] Add the variant --- web/packages/base/crypto/ente-impl.ts | 22 ++++++++------- web/packages/base/crypto/ente.ts | 39 ++++++++++++++++++--------- web/packages/base/crypto/types.ts | 19 ------------- web/packages/base/crypto/worker.ts | 1 + 4 files changed, 39 insertions(+), 42 deletions(-) diff --git a/web/packages/base/crypto/ente-impl.ts b/web/packages/base/crypto/ente-impl.ts index 1c28e3d124..6e3115795b 100644 --- a/web/packages/base/crypto/ente-impl.ts +++ b/web/packages/base/crypto/ente-impl.ts @@ -1,6 +1,6 @@ /** Careful when adding add other imports! */ import * as libsodium from "./libsodium"; -import type { BytesOrB64, EncryptedBlob_2, EncryptJSON } from "./types"; +import type { BytesOrB64, EncryptedBlob_2 } from "./types"; export const _encryptBoxB64 = libsodium.encryptBoxB64; @@ -16,16 +16,18 @@ export const _encryptThumbnail = async (data: BytesOrB64, key: BytesOrB64) => { }; }; -export const _encryptMetadataJSON_New = ({ jsonValue, keyB64 }: EncryptJSON) => - _encryptBlobB64( - new TextEncoder().encode(JSON.stringify(jsonValue)), - keyB64, - ); +export const _encryptMetadataJSON_New = (jsonValue: unknown, key: BytesOrB64) => + _encryptBlobB64(new TextEncoder().encode(JSON.stringify(jsonValue)), key); -export const _encryptMetadataJSON = async (r: EncryptJSON) => { - // Deprecated. Keep the old API for now. - const { encryptedData, decryptionHeader } = - await _encryptMetadataJSON_New(r); +// Deprecated, translates to the old API for now. +export const _encryptMetadataJSON = async (r: { + jsonValue: unknown; + keyB64: string; +}) => { + const { encryptedData, decryptionHeader } = await _encryptMetadataJSON_New( + r.jsonValue, + r.keyB64, + ); return { encryptedDataB64: encryptedData, decryptionHeaderB64: decryptionHeader, diff --git a/web/packages/base/crypto/ente.ts b/web/packages/base/crypto/ente.ts index 8feb0ccdce..7585f961bd 100644 --- a/web/packages/base/crypto/ente.ts +++ b/web/packages/base/crypto/ente.ts @@ -51,12 +51,7 @@ import { sharedCryptoWorker } from "."; import { assertionFailed } from "../assert"; import { inWorker } from "../env"; import * as ei from "./ente-impl"; -import type { - BytesOrB64, - EncryptedBlob_2, - EncryptedBox2, - EncryptJSON, -} from "./types"; +import type { BytesOrB64, EncryptedBlob_2, EncryptedBox2 } from "./types"; /** * Some of these functions have not yet been needed on the main thread, and for @@ -129,18 +124,36 @@ export const encryptThumbnail = (data: BytesOrB64, key: BytesOrB64) => * Encrypt the JSON metadata associated with an Ente object (file, collection or * entity) using the object's key. * - * This is a variant of {@link encryptAssociatedData} tailored for encrypting - * any arbitrary metadata associated with an Ente object. For example, it is - * used for encrypting the various metadata fields (See: [Note: Metadatum]) - * associated with a file, using that file's key. + * This is a variant of {@link encryptBlobB64} tailored for encrypting any + * arbitrary metadata associated with an Ente object. For example, it is used + * for encrypting the various metadata fields associated with a file, using that + * file's key. * * Instead of raw bytes, it takes as input an arbitrary JSON object which it - * encodes into a string, and encrypts that. And instead of returning the raw - * encrypted bytes, it returns their base64 string representation. + * encodes into a string, and encrypts that. * * Use {@link decryptMetadataJSON} to decrypt the result. + * + * @param jsonValue The JSON value to encrypt. This can be an arbitrary JSON + * value, but since TypeScript currently doesn't have a native JSON type, it is + * typed as {@link unknown}. + * + * @param key The encryption key. */ -export const encryptMetadataJSON = async (r: EncryptJSON) => +export const encryptMetadataJSON_New = (jsonValue: unknown, key: BytesOrB64) => + inWorker() + ? ei._encryptMetadataJSON_New(jsonValue, key) + : sharedCryptoWorker().then((w) => + w.encryptMetadataJSON_New(jsonValue, key), + ); + +/** + * Deprecated, use {@link encryptMetadataJSON_New} instead. + */ +export const encryptMetadataJSON = async (r: { + jsonValue: unknown; + keyB64: string; +}) => inWorker() ? ei._encryptMetadataJSON(r) : sharedCryptoWorker().then((w) => w.encryptMetadataJSON(r)); diff --git a/web/packages/base/crypto/types.ts b/web/packages/base/crypto/types.ts index fdc9542062..d4c972e8c6 100644 --- a/web/packages/base/crypto/types.ts +++ b/web/packages/base/crypto/types.ts @@ -4,25 +4,6 @@ */ export type BytesOrB64 = Uint8Array | string; -/** - * Deprecated. - * - * A variant of {@link EncryptBytes} with the data as a JSON value. - */ -export interface EncryptJSON { - /** - * The JSON value to encrypt. - * - * This can be an arbitrary JSON value, but since TypeScript currently - * doesn't have a native JSON type, it is typed as {@link unknown}. - */ - jsonValue: unknown; - /** - * A base64 string containing the encryption key. - */ - keyB64: string; -} - /** * The result of encryption using the secretbox APIs. * diff --git a/web/packages/base/crypto/worker.ts b/web/packages/base/crypto/worker.ts index c87ce36b42..7ce5b56775 100644 --- a/web/packages/base/crypto/worker.ts +++ b/web/packages/base/crypto/worker.ts @@ -14,6 +14,7 @@ import * as libsodium from "./libsodium"; export class CryptoWorker { encryptBoxB64 = ei._encryptBoxB64; encryptThumbnail = ei._encryptThumbnail; + encryptMetadataJSON_New = ei._encryptMetadataJSON_New; encryptMetadataJSON = ei._encryptMetadataJSON; decryptBox = ei._decryptBox; decryptBoxB64 = ei._decryptBoxB64; From 08c3b172d974582e6bad3e7f186895df03f3237e Mon Sep 17 00:00:00 2001 From: Manav Rathi Date: Sat, 17 Aug 2024 21:49:39 +0530 Subject: [PATCH 34/36] Update types --- web/packages/base/crypto/ente-impl.ts | 4 ++-- web/packages/base/crypto/ente.ts | 14 +++++++------- web/packages/base/crypto/libsodium.ts | 20 ++++++++++---------- web/packages/base/crypto/types.ts | 16 ++++++++-------- 4 files changed, 27 insertions(+), 27 deletions(-) diff --git a/web/packages/base/crypto/ente-impl.ts b/web/packages/base/crypto/ente-impl.ts index 6e3115795b..ebd6055b5c 100644 --- a/web/packages/base/crypto/ente-impl.ts +++ b/web/packages/base/crypto/ente-impl.ts @@ -1,6 +1,6 @@ /** Careful when adding add other imports! */ import * as libsodium from "./libsodium"; -import type { BytesOrB64, EncryptedBlob_2 } from "./types"; +import type { BytesOrB64, EncryptedBlob } from "./types"; export const _encryptBoxB64 = libsodium.encryptBoxB64; @@ -45,7 +45,7 @@ export const _decryptBlobB64 = libsodium.decryptBlobB64; export const _decryptThumbnail = _decryptBlob; export const _decryptMetadataJSON_New = async ( - blob: EncryptedBlob_2, + blob: EncryptedBlob, key: BytesOrB64, ) => JSON.parse( diff --git a/web/packages/base/crypto/ente.ts b/web/packages/base/crypto/ente.ts index 7585f961bd..476e589505 100644 --- a/web/packages/base/crypto/ente.ts +++ b/web/packages/base/crypto/ente.ts @@ -51,7 +51,7 @@ import { sharedCryptoWorker } from "."; import { assertionFailed } from "../assert"; import { inWorker } from "../env"; import * as ei from "./ente-impl"; -import type { BytesOrB64, EncryptedBlob_2, EncryptedBox2 } from "./types"; +import type { BytesOrB64, EncryptedBlob, EncryptedBox } from "./types"; /** * Some of these functions have not yet been needed on the main thread, and for @@ -161,7 +161,7 @@ export const encryptMetadataJSON = async (r: { /** * Decrypt a box encrypted using {@link encryptBoxB64}. */ -export const decryptBox = (box: EncryptedBox2, key: BytesOrB64) => +export const decryptBox = (box: EncryptedBox, key: BytesOrB64) => inWorker() ? ei._decryptBox(box, key) : sharedCryptoWorker().then((w) => w.decryptBox(box, key)); @@ -169,7 +169,7 @@ export const decryptBox = (box: EncryptedBox2, key: BytesOrB64) => /** * Variant of {@link decryptBox} that returns the result as a base64 string. */ -export const decryptBoxB64 = (box: EncryptedBox2, key: BytesOrB64) => +export const decryptBoxB64 = (box: EncryptedBox, key: BytesOrB64) => inWorker() ? ei._decryptBoxB64(box, key) : sharedCryptoWorker().then((w) => w.decryptBoxB64(box, key)); @@ -178,13 +178,13 @@ export const decryptBoxB64 = (box: EncryptedBox2, key: BytesOrB64) => * Decrypt a blob encrypted using either {@link encryptBlob} or * {@link encryptBlobB64}. */ -export const decryptBlob = (blob: EncryptedBlob_2, key: BytesOrB64) => +export const decryptBlob = (blob: EncryptedBlob, key: BytesOrB64) => assertInWorker(ei._decryptBlob(blob, key)); /** * A variant of {@link decryptBlob} that returns the result as a base64 string. */ -export const decryptBlobB64 = (blob: EncryptedBlob_2, key: BytesOrB64) => +export const decryptBlobB64 = (blob: EncryptedBlob, key: BytesOrB64) => inWorker() ? ei._decryptBlobB64(blob, key) : sharedCryptoWorker().then((w) => w.decryptBlobB64(blob, key)); @@ -192,7 +192,7 @@ export const decryptBlobB64 = (blob: EncryptedBlob_2, key: BytesOrB64) => /** * Decrypt the thumbnail encrypted using {@link encryptThumbnail}. */ -export const decryptThumbnail = (blob: EncryptedBlob_2, key: BytesOrB64) => +export const decryptThumbnail = (blob: EncryptedBlob, key: BytesOrB64) => assertInWorker(ei._decryptThumbnail(blob, key)); /** @@ -202,7 +202,7 @@ export const decryptThumbnail = (blob: EncryptedBlob_2, key: BytesOrB64) => * JSON type, we need to return it as an `unknown`. */ export const decryptMetadataJSON_New = ( - blob: EncryptedBlob_2, + blob: EncryptedBlob, key: BytesOrB64, ) => inWorker() diff --git a/web/packages/base/crypto/libsodium.ts b/web/packages/base/crypto/libsodium.ts index 5a1376fa79..29546d567f 100644 --- a/web/packages/base/crypto/libsodium.ts +++ b/web/packages/base/crypto/libsodium.ts @@ -13,10 +13,10 @@ import { CustomError } from "@ente/shared/error"; import sodium, { type StateAddress } from "libsodium-wrappers"; import type { BytesOrB64, - EncryptedBlob_2, - EncryptedBlobB64_2, - EncryptedBlobBytes_2, - EncryptedBox2, + EncryptedBlob, + EncryptedBlobB64, + EncryptedBlobBytes, + EncryptedBox, EncryptedBoxB64, } from "./types"; @@ -258,7 +258,7 @@ export const encryptBoxB64 = async ( export const encryptBlob = async ( data: BytesOrB64, key: BytesOrB64, -): Promise => { +): Promise => { await sodium.ready; const uintkey = await bytes(key); @@ -285,7 +285,7 @@ export const encryptBlob = async ( export const encryptBlobB64 = async ( data: BytesOrB64, key: BytesOrB64, -): Promise => { +): Promise => { const { encryptedData, decryptionHeader } = await encryptBlob(data, key); return { encryptedData: await toB64(encryptedData), @@ -371,7 +371,7 @@ export async function encryptFileChunk( * Decrypt the result of {@link encryptBoxB64}. */ export const decryptBox2 = async ( - { encryptedData, nonce }: EncryptedBox2, + { encryptedData, nonce }: EncryptedBox, key: BytesOrB64, ): Promise => { await sodium.ready; @@ -386,7 +386,7 @@ export const decryptBox2 = async ( * Variant of {@link decryptBox} that returns the data as a base64 string. */ export const decryptBoxB64 = ( - box: EncryptedBox2, + box: EncryptedBox, key: BytesOrB64, ): Promise => decryptBox2(box, key).then(toB64); @@ -394,7 +394,7 @@ export const decryptBoxB64 = ( * Decrypt the result of {@link encryptBlob} or {@link encryptBlobB64}. */ export const decryptBlob2 = async ( - { encryptedData, decryptionHeader }: EncryptedBlob_2, + { encryptedData, decryptionHeader }: EncryptedBlob, key: BytesOrB64, ): Promise => { await sodium.ready; @@ -414,7 +414,7 @@ export const decryptBlob2 = async ( * A variant of {@link decryptBlob2} that returns the result as a base64 string. */ export const decryptBlobB64 = ( - blob: EncryptedBlob_2, + blob: EncryptedBlob, key: BytesOrB64, ): Promise => decryptBlob2(blob, key).then(toB64); diff --git a/web/packages/base/crypto/types.ts b/web/packages/base/crypto/types.ts index d4c972e8c6..a70c5b3aa9 100644 --- a/web/packages/base/crypto/types.ts +++ b/web/packages/base/crypto/types.ts @@ -13,7 +13,7 @@ export type BytesOrB64 = Uint8Array | string; * * See: [Note: 3 forms of encryption (Box | Blob | Stream)]. */ -export interface EncryptedBox2 { +export interface EncryptedBox { /** * The data to decrypt. */ @@ -49,11 +49,11 @@ export interface EncryptedBoxB64 { * * See: [Note: 3 forms of encryption (Box | Blob | Stream)]. * - * This type is a combination of {@link EncryptedBlobBytes_2} and - * {@link EncryptedBlobB64_2} which allows the decryption routines to accept + * This type is a combination of {@link EncryptedBlobBytes} and + * {@link EncryptedBlobB64} which allows the decryption routines to accept * either the bytes or the base64 variants produced by the encryption routines. */ -export interface EncryptedBlob_2 { +export interface EncryptedBlob { /** * The encrypted data. */ @@ -69,10 +69,10 @@ export interface EncryptedBlob_2 { } /** - * A variant of {@link EncryptedBlob_2} that has the encrypted data and header + * A variant of {@link EncryptedBlob} that has the encrypted data and header * as bytes ({@link Uint8Array}s). */ -export interface EncryptedBlobBytes_2 { +export interface EncryptedBlobBytes { /** * The encrypted data. */ @@ -88,10 +88,10 @@ export interface EncryptedBlobBytes_2 { } /** - * A variant of {@link EncryptedBlob_2} that has the encrypted data and header + * A variant of {@link EncryptedBlob} that has the encrypted data and header * as base64 strings. */ -export interface EncryptedBlobB64_2 { +export interface EncryptedBlobB64 { /** * The encrypted data as a base64 string. */ From 8601662ea1732c89b02ca430cfea58e05135400f Mon Sep 17 00:00:00 2001 From: Manav Rathi Date: Sat, 17 Aug 2024 21:51:50 +0530 Subject: [PATCH 35/36] Un2 --- web/packages/base/crypto/ente-impl.ts | 4 ++-- web/packages/base/crypto/libsodium.ts | 12 ++++++------ 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/web/packages/base/crypto/ente-impl.ts b/web/packages/base/crypto/ente-impl.ts index ebd6055b5c..72e31e5b87 100644 --- a/web/packages/base/crypto/ente-impl.ts +++ b/web/packages/base/crypto/ente-impl.ts @@ -34,11 +34,11 @@ export const _encryptMetadataJSON = async (r: { }; }; -export const _decryptBox = libsodium.decryptBox2; +export const _decryptBox = libsodium.decryptBox; export const _decryptBoxB64 = libsodium.decryptBoxB64; -export const _decryptBlob = libsodium.decryptBlob2; +export const _decryptBlob = libsodium.decryptBlob; export const _decryptBlobB64 = libsodium.decryptBlobB64; diff --git a/web/packages/base/crypto/libsodium.ts b/web/packages/base/crypto/libsodium.ts index 29546d567f..7463b4a62a 100644 --- a/web/packages/base/crypto/libsodium.ts +++ b/web/packages/base/crypto/libsodium.ts @@ -370,7 +370,7 @@ export async function encryptFileChunk( /** * Decrypt the result of {@link encryptBoxB64}. */ -export const decryptBox2 = async ( +export const decryptBox = async ( { encryptedData, nonce }: EncryptedBox, key: BytesOrB64, ): Promise => { @@ -388,12 +388,12 @@ export const decryptBox2 = async ( export const decryptBoxB64 = ( box: EncryptedBox, key: BytesOrB64, -): Promise => decryptBox2(box, key).then(toB64); +): Promise => decryptBox(box, key).then(toB64); /** * Decrypt the result of {@link encryptBlob} or {@link encryptBlobB64}. */ -export const decryptBlob2 = async ( +export const decryptBlob = async ( { encryptedData, decryptionHeader }: EncryptedBlob, key: BytesOrB64, ): Promise => { @@ -411,12 +411,12 @@ export const decryptBlob2 = async ( }; /** - * A variant of {@link decryptBlob2} that returns the result as a base64 string. + * A variant of {@link decryptBlob} that returns the result as a base64 string. */ export const decryptBlobB64 = ( blob: EncryptedBlob, key: BytesOrB64, -): Promise => decryptBlob2(blob, key).then(toB64); +): Promise => decryptBlob(blob, key).then(toB64); /** Decrypt Stream, but merge the results. */ export const decryptChaCha = async ( @@ -532,7 +532,7 @@ export async function decryptToUTF8( keyB64: string, ) { await sodium.ready; - const decrypted = await decryptBox2({ encryptedData, nonce }, keyB64); + const decrypted = await decryptBox({ encryptedData, nonce }, keyB64); return sodium.to_string(decrypted); } From 505dc7c20d18a4721067e698c74f7f4a951d12ca Mon Sep 17 00:00:00 2001 From: Manav Rathi Date: Sat, 17 Aug 2024 22:03:52 +0530 Subject: [PATCH 36/36] Fix --- web/packages/base/crypto/libsodium.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/web/packages/base/crypto/libsodium.ts b/web/packages/base/crypto/libsodium.ts index 7463b4a62a..31eb85141d 100644 --- a/web/packages/base/crypto/libsodium.ts +++ b/web/packages/base/crypto/libsodium.ts @@ -268,7 +268,7 @@ export const encryptBlob = async ( const pushResult = sodium.crypto_secretstream_xchacha20poly1305_push( pushState, - data, + await bytes(data), null, sodium.crypto_secretstream_xchacha20poly1305_TAG_FINAL, );