From 0bff899713cbce82635373d59c4cc99329301fc0 Mon Sep 17 00:00:00 2001 From: Manav Rathi Date: Mon, 5 Aug 2024 10:31:07 +0530 Subject: [PATCH 01/35] Doc --- web/packages/base/crypto/ente.ts | 4 ++-- .../shared/crypto/internal/libsodium.ts | 19 +++++++++++++++++++ 2 files changed, 21 insertions(+), 2 deletions(-) diff --git a/web/packages/base/crypto/ente.ts b/web/packages/base/crypto/ente.ts index dd230d6a15..5ed1ad0209 100644 --- a/web/packages/base/crypto/ente.ts +++ b/web/packages/base/crypto/ente.ts @@ -39,8 +39,8 @@ export const encryptFileMetadata = async ( * @param headerB64 Base64 encoded string containing the decryption header * produced during encryption. * - * @param keyB64 Base64 encoded string containing the encryption key (this'll - * generally be the file's key). + * @param keyB64 Base64 encoded string containing the encryption key. This will + * generally the key of the file whose metadata this is. * * @returns The decrypted metadata bytes. */ diff --git a/web/packages/shared/crypto/internal/libsodium.ts b/web/packages/shared/crypto/internal/libsodium.ts index 857c826d5a..2ba73b786e 100644 --- a/web/packages/shared/crypto/internal/libsodium.ts +++ b/web/packages/shared/crypto/internal/libsodium.ts @@ -110,6 +110,25 @@ export async function fromHex(input: string) { return await toB64(sodium.from_hex(input)); } +/** + * Encrypt the given {@link data} using the given (Base64 encoded) key. + * + * This uses the same stream encryption algorithm pair (XChaCha20 stream cipher + * with Poly1305 MAC authentication) that we use for encrypting, well, other + * streams, like the file's contents. The difference here is that this function + * does a one shot instead of a streaming encryption. + * + * Ref: https://doc.libsodium.org/secret-key_cryptography/secretstream + * + * @param data A {@link Uint8Array} containing the bytes that we want to + * encrypt. + * + * @param keyB64 Base64 encoded string containing the encryption key. This is + * usually be the key associated with a file to which {@link data} relates to. + * + * @returns The encrypted data and decryption header pair. Both these values are + * needed to decrypt the data. The header does not need to be secret. + */ export async function encryptChaChaOneShot(data: Uint8Array, key: string) { await sodium.ready; From 32a602725aaa52c5d98d411fb973900c01f1a73c Mon Sep 17 00:00:00 2001 From: Manav Rathi Date: Mon, 5 Aug 2024 10:34:01 +0530 Subject: [PATCH 02/35] Unnest --- web/apps/photos/src/services/upload/uploadService.ts | 2 +- web/packages/base/crypto/ente.ts | 2 +- web/packages/shared/crypto/internal/crypto.worker.ts | 8 +++++--- web/packages/shared/crypto/internal/libsodium.ts | 7 ++----- 4 files changed, 9 insertions(+), 10 deletions(-) diff --git a/web/apps/photos/src/services/upload/uploadService.ts b/web/apps/photos/src/services/upload/uploadService.ts index d28d211cd4..3de3b96e85 100644 --- a/web/apps/photos/src/services/upload/uploadService.ts +++ b/web/apps/photos/src/services/upload/uploadService.ts @@ -1119,7 +1119,7 @@ const encryptFile = async ( worker, ); - const { file: encryptedThumbnail } = await worker.encryptThumbnail( + const encryptedThumbnail = await worker.encryptThumbnail( file.thumbnail, fileKey, ); diff --git a/web/packages/base/crypto/ente.ts b/web/packages/base/crypto/ente.ts index 5ed1ad0209..ff7b5a1de6 100644 --- a/web/packages/base/crypto/ente.ts +++ b/web/packages/base/crypto/ente.ts @@ -23,7 +23,7 @@ export const encryptFileMetadata = async ( metadata: Uint8Array, keyB64: string, ) => { - const { file } = await libsodium.encryptChaChaOneShot(metadata, keyB64); + const file = await libsodium.encryptChaChaOneShot(metadata, keyB64); return { encryptedMetadataB64: await libsodium.toB64(file.encryptedData), decryptionHeaderB64: file.decryptionHeader, diff --git a/web/packages/shared/crypto/internal/crypto.worker.ts b/web/packages/shared/crypto/internal/crypto.worker.ts index e709aa74d6..4366625b22 100644 --- a/web/packages/shared/crypto/internal/crypto.worker.ts +++ b/web/packages/shared/crypto/internal/crypto.worker.ts @@ -49,8 +49,10 @@ export class DedicatedCryptoWorker { async encryptMetadata(metadata: Object, key: string) { const encodedMetadata = textEncoder.encode(JSON.stringify(metadata)); - const { file: encryptedMetadata } = - await libsodium.encryptChaChaOneShot(encodedMetadata, key); + const encryptedMetadata = await libsodium.encryptChaChaOneShot( + encodedMetadata, + key, + ); const { encryptedData, ...other } = encryptedMetadata; return { file: { @@ -69,7 +71,7 @@ export class DedicatedCryptoWorker { const encodedEmbedding = textEncoder.encode( JSON.stringify(Array.from(embedding)), ); - const { file: encryptEmbedding } = await libsodium.encryptChaChaOneShot( + const encryptEmbedding = await libsodium.encryptChaChaOneShot( encodedEmbedding, key, ); diff --git a/web/packages/shared/crypto/internal/libsodium.ts b/web/packages/shared/crypto/internal/libsodium.ts index 2ba73b786e..4f1b1c2497 100644 --- a/web/packages/shared/crypto/internal/libsodium.ts +++ b/web/packages/shared/crypto/internal/libsodium.ts @@ -144,11 +144,8 @@ export async function encryptChaChaOneShot(data: Uint8Array, key: string) { sodium.crypto_secretstream_xchacha20poly1305_TAG_FINAL, ); return { - key: await toB64(uintkey), - file: { - encryptedData: pushResult, - decryptionHeader: await toB64(header), - }, + encryptedData: pushResult, + decryptionHeader: await toB64(header), }; } From 9bce3bba7c4c35f951841720417e79648359726e Mon Sep 17 00:00:00 2001 From: Manav Rathi Date: Mon, 5 Aug 2024 11:07:00 +0530 Subject: [PATCH 03/35] Rename and prune --- web/packages/base/crypto/ente.ts | 7 +-- .../shared/crypto/internal/crypto.worker.ts | 46 ++++++++----------- .../shared/crypto/internal/libsodium.ts | 23 ++++++---- 3 files changed, 39 insertions(+), 37 deletions(-) diff --git a/web/packages/base/crypto/ente.ts b/web/packages/base/crypto/ente.ts index ff7b5a1de6..a7275c596b 100644 --- a/web/packages/base/crypto/ente.ts +++ b/web/packages/base/crypto/ente.ts @@ -23,10 +23,11 @@ export const encryptFileMetadata = async ( metadata: Uint8Array, keyB64: string, ) => { - const file = await libsodium.encryptChaChaOneShot(metadata, keyB64); + const { encryptedData, decryptionHeaderB64 } = + await libsodium.encryptChaChaOneShot(metadata, keyB64); return { - encryptedMetadataB64: await libsodium.toB64(file.encryptedData), - decryptionHeaderB64: file.decryptionHeader, + encryptedMetadataB64: await libsodium.toB64(encryptedData), + decryptionHeaderB64, }; }; diff --git a/web/packages/shared/crypto/internal/crypto.worker.ts b/web/packages/shared/crypto/internal/crypto.worker.ts index 4366625b22..bada1d6958 100644 --- a/web/packages/shared/crypto/internal/crypto.worker.ts +++ b/web/packages/shared/crypto/internal/crypto.worker.ts @@ -49,40 +49,34 @@ export class DedicatedCryptoWorker { async encryptMetadata(metadata: Object, key: string) { const encodedMetadata = textEncoder.encode(JSON.stringify(metadata)); - const encryptedMetadata = await libsodium.encryptChaChaOneShot( - encodedMetadata, - key, - ); - const { encryptedData, ...other } = encryptedMetadata; + const { encryptedData, decryptionHeaderB64 } = + await libsodium.encryptChaChaOneShot(encodedMetadata, key); return { file: { encryptedData: await libsodium.toB64(encryptedData), - ...other, + // TODO: + decryptionHeader: decryptionHeaderB64, }, key, }; } - async encryptThumbnail(fileData: Uint8Array, key: string) { - return libsodium.encryptChaChaOneShot(fileData, key); - } - - async encryptEmbedding(embedding: Float32Array, key: string) { - const encodedEmbedding = textEncoder.encode( - JSON.stringify(Array.from(embedding)), - ); - const encryptEmbedding = await libsodium.encryptChaChaOneShot( - encodedEmbedding, - key, - ); - const { encryptedData, ...other } = encryptEmbedding; - return { - file: { - encryptedData: await libsodium.toB64(encryptedData), - ...other, - }, - key, - }; + /** + * Encrypt the thumbnail associated with a file. + * + * This defers to {@link encryptChaChaOneShot}. + * + * @param data The thumbnail's data. + * + * @param keyB64 The key associated with the file whose thumbnail this is. + * + * @returns The encrypted thumbnail, and the associated decryption header + * (Base64 encoded). + */ + async encryptThumbnail(data: Uint8Array, keyB64: string) { + const { encryptedData, decryptionHeaderB64: decryptionHeader } = + await libsodium.encryptChaChaOneShot(data, keyB64); + return { encryptedData, decryptionHeader }; } async encryptFile(fileData: Uint8Array) { diff --git a/web/packages/shared/crypto/internal/libsodium.ts b/web/packages/shared/crypto/internal/libsodium.ts index 4f1b1c2497..d553d8bbba 100644 --- a/web/packages/shared/crypto/internal/libsodium.ts +++ b/web/packages/shared/crypto/internal/libsodium.ts @@ -115,8 +115,11 @@ export async function fromHex(input: string) { * * This uses the same stream encryption algorithm pair (XChaCha20 stream cipher * with Poly1305 MAC authentication) that we use for encrypting, well, other - * streams, like the file's contents. The difference here is that this function - * does a one shot instead of a streaming encryption. + * streams, like the file's contents. + * + * The difference here is that this function does a one shot instead of a + * streaming encryption. As such, this is only meant to be used for relatively + * small amounts of data. * * Ref: https://doc.libsodium.org/secret-key_cryptography/secretstream * @@ -126,13 +129,17 @@ export async function fromHex(input: string) { * @param keyB64 Base64 encoded string containing the encryption key. This is * usually be the key associated with a file to which {@link data} relates to. * - * @returns The encrypted data and decryption header pair. Both these values are - * needed to decrypt the data. The header does not need to be secret. + * @returns The encrypted data (bytes) and decryption header pair (Base64 + * encoded string). Both these values are needed to decrypt the data. The header + * does not need to be secret. */ -export async function encryptChaChaOneShot(data: Uint8Array, key: string) { +export const encryptChaChaOneShot = async ( + data: Uint8Array, + keyB64: string, +) => { await sodium.ready; - const uintkey: Uint8Array = await fromB64(key); + const uintkey: Uint8Array = await fromB64(keyB64); const initPushResult = sodium.crypto_secretstream_xchacha20poly1305_init_push(uintkey); const [pushState, header] = [initPushResult.state, initPushResult.header]; @@ -145,9 +152,9 @@ export async function encryptChaChaOneShot(data: Uint8Array, key: string) { ); return { encryptedData: pushResult, - decryptionHeader: await toB64(header), + decryptionHeaderB64: await toB64(header), }; -} +}; export const ENCRYPTION_CHUNK_SIZE = 4 * 1024 * 1024; From 2dcc19955659a589905a35de7b7b4f7e6876f15b Mon Sep 17 00:00:00 2001 From: Manav Rathi Date: Mon, 5 Aug 2024 11:10:56 +0530 Subject: [PATCH 04/35] Unused --- .../shared/crypto/internal/crypto.worker.ts | 15 --------------- 1 file changed, 15 deletions(-) diff --git a/web/packages/shared/crypto/internal/crypto.worker.ts b/web/packages/shared/crypto/internal/crypto.worker.ts index bada1d6958..e2367aa7c5 100644 --- a/web/packages/shared/crypto/internal/crypto.worker.ts +++ b/web/packages/shared/crypto/internal/crypto.worker.ts @@ -27,21 +27,6 @@ export class DedicatedCryptoWorker { return libsodium.decryptChaChaOneShot(fileData, header, key); } - async decryptEmbedding( - encryptedEmbedding: string, - header: string, - key: string, - ) { - const encodedEmbedding = await libsodium.decryptChaChaOneShot( - await libsodium.fromB64(encryptedEmbedding), - await libsodium.fromB64(header), - key, - ); - return Float32Array.from( - JSON.parse(textDecoder.decode(encodedEmbedding)), - ); - } - async decryptFile(fileData: Uint8Array, header: Uint8Array, key: string) { return libsodium.decryptChaCha(fileData, header, key); } From 15f80e3fa6197bbf76f6dc331a6d6ea564a02f3e Mon Sep 17 00:00:00 2001 From: Manav Rathi Date: Mon, 5 Aug 2024 11:33:16 +0530 Subject: [PATCH 05/35] Ontology --- web/packages/base/crypto/ente.ts | 52 ++++++++++++------- .../new/photos/services/ml/embedding.ts | 13 +++-- 2 files changed, 42 insertions(+), 23 deletions(-) diff --git a/web/packages/base/crypto/ente.ts b/web/packages/base/crypto/ente.ts index a7275c596b..e7cec7cb6a 100644 --- a/web/packages/base/crypto/ente.ts +++ b/web/packages/base/crypto/ente.ts @@ -9,49 +9,65 @@ import * as libsodium from "@ente/shared/crypto/internal/libsodium"; /** - * Encrypt arbitrary metadata associated with a file using the file's key. + * Encrypt arbitrary data associated with a file using the file's key. * - * @param metadata The metadata (bytes) to encrypt. + * See {@link encryptChaChaOneShot} for the implementation details. * - * @param keyB64 Base64 encoded string containing the encryption key (this'll - * generally be the file's key). + * @param data The data (bytes) to encrypt. * - * @returns Base64 encoded strings containing the encrypted data and the - * decryption header. + * @param keyB64 Base64 encoded string containing the encryption key. This is + * expected to the key of the file with which {@link data} is associated. + * + * @returns The encrypted data and the (Base64 encoded) decryption header. */ -export const encryptFileMetadata = async ( - metadata: Uint8Array, +export const encryptFileAssociatedData = (data: Uint8Array, keyB64: string) => + libsodium.encryptChaChaOneShot(data, keyB64); + +/** + * A variant of {@link encryptFileAssociatedData} that Base64 encodes the + * encrypted data. + * + * This is the sibling of {@link decryptFileAssociatedDataFromB64}. + * + * It is useful in cases where the (encrypted) associated data needs to + * transferred as the HTTP POST body. + */ +//export const encryptFileMetadata = async ( +export const encryptFileAssociatedDataToB64 = async ( + data: Uint8Array, keyB64: string, ) => { const { encryptedData, decryptionHeaderB64 } = - await libsodium.encryptChaChaOneShot(metadata, keyB64); + await encryptFileAssociatedData(data, keyB64); return { - encryptedMetadataB64: await libsodium.toB64(encryptedData), + encryptedDataB64: await libsodium.toB64(encryptedData), decryptionHeaderB64, }; }; /** - * Decrypt arbitrary metadata associated with a file using the file's key. + * Decrypt arbitrary data associated with a file using the file's key. * - * @param encryptedMetadataB64 Base64 encoded string containing the encrypted - * data. + * This is the sibling of {@link encryptFileAssociatedDataToB64}. + * + * @param encryptedDataB64 Base64 encoded string containing the encrypted data. * * @param headerB64 Base64 encoded string containing the decryption header * produced during encryption. * - * @param keyB64 Base64 encoded string containing the encryption key. This will - * generally the key of the file whose metadata this is. + * @param keyB64 Base64 encoded string containing the encryption key. This is + * expected to be the key of the file with which {@link encryptedDataB64} is + * associated. * * @returns The decrypted metadata bytes. */ -export const decryptFileMetadata = async ( - encryptedMetadataB64: string, +export const decryptFileAssociatedDataFromB64 = async ( + encryptedDataB64: string, decryptionHeaderB64: string, keyB64: string, ) => libsodium.decryptChaChaOneShot( - await libsodium.fromB64(encryptedMetadataB64), + await libsodium.fromB64(encryptedDataB64), await libsodium.fromB64(decryptionHeaderB64), keyB64, ); diff --git a/web/packages/new/photos/services/ml/embedding.ts b/web/packages/new/photos/services/ml/embedding.ts index 32395476be..d405327513 100644 --- a/web/packages/new/photos/services/ml/embedding.ts +++ b/web/packages/new/photos/services/ml/embedding.ts @@ -1,4 +1,7 @@ -import { decryptFileMetadata, encryptFileMetadata } from "@/base/crypto/ente"; +import { + decryptFileAssociatedDataFromB64, + encryptFileAssociatedDataToB64, +} from "@/base/crypto/ente"; import { authenticatedRequestHeaders, ensureOk } from "@/base/http"; import log from "@/base/log"; import { apiURL } from "@/base/origins"; @@ -195,7 +198,7 @@ export const fetchDerivedData = async ( } try { - const decryptedBytes = await decryptFileMetadata( + const decryptedBytes = await decryptFileAssociatedDataFromB64( remoteEmbedding.encryptedEmbedding, remoteEmbedding.decryptionHeader, file.key, @@ -293,15 +296,15 @@ const putEmbedding = async ( model: EmbeddingModel, embedding: Uint8Array, ) => { - const { encryptedMetadataB64, decryptionHeaderB64 } = - await encryptFileMetadata(embedding, enteFile.key); + const { encryptedDataB64, decryptionHeaderB64 } = + await encryptFileAssociatedDataToB64(embedding, enteFile.key); const res = await fetch(await apiURL("/embeddings"), { method: "PUT", headers: await authenticatedRequestHeaders(), body: JSON.stringify({ fileID: enteFile.id, - encryptedEmbedding: encryptedMetadataB64, + encryptedEmbedding: encryptedDataB64, decryptionHeader: decryptionHeaderB64, model, }), From a9359d15d3804b29337a254856997946f56655f1 Mon Sep 17 00:00:00 2001 From: Manav Rathi Date: Mon, 5 Aug 2024 11:39:31 +0530 Subject: [PATCH 06/35] Doc --- web/packages/base/crypto/ente.ts | 20 +++++++++++++++----- 1 file changed, 15 insertions(+), 5 deletions(-) diff --git a/web/packages/base/crypto/ente.ts b/web/packages/base/crypto/ente.ts index e7cec7cb6a..f71cca3efd 100644 --- a/web/packages/base/crypto/ente.ts +++ b/web/packages/base/crypto/ente.ts @@ -1,10 +1,21 @@ /** * @file Higher level functions that use the ontology of Ente's types * - * These are thin wrappers over the (thin-) wrappers in internal/libsodium.ts. - * The main difference is that these functions don't talk in terms of the crypto - * algorithms, but rather in terms the higher-level Ente specific goal we are - * trying to accomplish. + * [Note: Crypto code hierarchy] + * + * The functions in this file (base/crypto/ente.ts) are are thin wrappers over + * the (thin-) wrappers in internal/libsodium.ts. The main difference is that + * these functions don't talk in terms of the crypto algorithms, but rather in + * terms the higher-level Ente specific goal we are trying to accomplish. + * + * Some of these are also exposed via the web worker in + * internal/crypto.worker.ts. The web worker variants should be used when we + * need to perform these operations from the main thread, so that the UI remains + * responsive while the potentially CPU-intensive encryption etc happens. + * + * 1. ente.ts or crypto.worker.ts (high level, Ente specific). + * 2. internal/libsodium.ts (wrappers over libsodium) + * 3. libsodium (JS bindings). */ import * as libsodium from "@ente/shared/crypto/internal/libsodium"; @@ -32,7 +43,6 @@ export const encryptFileAssociatedData = (data: Uint8Array, keyB64: string) => * It is useful in cases where the (encrypted) associated data needs to * transferred as the HTTP POST body. */ -//export const encryptFileMetadata = async ( export const encryptFileAssociatedDataToB64 = async ( data: Uint8Array, keyB64: string, From 124552eda33d718ba60b09a790a19e3613ddc551 Mon Sep 17 00:00:00 2001 From: Manav Rathi Date: Mon, 5 Aug 2024 12:03:16 +0530 Subject: [PATCH 07/35] New --- web/packages/base/crypto/ente.ts | 29 +++++++++++++++++++++++++++-- 1 file changed, 27 insertions(+), 2 deletions(-) diff --git a/web/packages/base/crypto/ente.ts b/web/packages/base/crypto/ente.ts index f71cca3efd..3566862d9a 100644 --- a/web/packages/base/crypto/ente.ts +++ b/web/packages/base/crypto/ente.ts @@ -35,8 +35,8 @@ export const encryptFileAssociatedData = (data: Uint8Array, keyB64: string) => libsodium.encryptChaChaOneShot(data, keyB64); /** - * A variant of {@link encryptFileAssociatedData} that Base64 encodes the - * encrypted data. + * A variant of {@link encryptFileAssociatedData} that returns the Base64 + * encoded encrypted data instead of its raw bytes. * * This is the sibling of {@link decryptFileAssociatedDataFromB64}. * @@ -81,3 +81,28 @@ export const decryptFileAssociatedDataFromB64 = async ( await libsodium.fromB64(decryptionHeaderB64), keyB64, ); + +/** + * A variant of {@link encryptFileAssociatedData} tailored for encrypting the + * various metadata fields associated with a file. + * + * Instead of raw bytes, it takes as input an arbitrary JSON object which it + * encodes into a string, and encrypts that. Also, instead of returning the raw + * encrypted bytes, it returns their Base64 encoded string representation. + */ +export const encryptFileMetadata = async ( + // Arbitrary JSON really, but since TypeScript doesn't have a native JSON + // type use a string Record as a standin replacement. + metadata: Record, + keyB64: string, +) => { + const textEncoder = new TextEncoder(); + const encodedMetadata = textEncoder.encode(JSON.stringify(metadata)); + + const { encryptedData, decryptionHeaderB64 } = + await encryptFileAssociatedData(encodedMetadata, keyB64); + return { + encryptedData: await libsodium.toB64(encryptedData), + decryptionHeaderB64, + }; +}; From 13f31a7d092bd731524a8920c70ab81605935935 Mon Sep 17 00:00:00 2001 From: Manav Rathi Date: Mon, 5 Aug 2024 12:12:09 +0530 Subject: [PATCH 08/35] Dec --- web/packages/base/crypto/ente.ts | 30 +++++++++++++++++++ .../shared/crypto/internal/libsodium.ts | 24 +++++++++++---- 2 files changed, 48 insertions(+), 6 deletions(-) diff --git a/web/packages/base/crypto/ente.ts b/web/packages/base/crypto/ente.ts index 3566862d9a..d29422fc17 100644 --- a/web/packages/base/crypto/ente.ts +++ b/web/packages/base/crypto/ente.ts @@ -22,6 +22,8 @@ import * as libsodium from "@ente/shared/crypto/internal/libsodium"; /** * Encrypt arbitrary data associated with a file using the file's key. * + * Use {@link decryptFileAssociatedData} to decrypt the result. + * * See {@link encryptChaChaOneShot} for the implementation details. * * @param data The data (bytes) to encrypt. @@ -34,6 +36,34 @@ import * as libsodium from "@ente/shared/crypto/internal/libsodium"; export const encryptFileAssociatedData = (data: Uint8Array, keyB64: string) => libsodium.encryptChaChaOneShot(data, keyB64); +/** + * Decrypt arbitrary data associated with a file using the file's key. + * + * This is the sibling of {@link encryptFileAssociatedData}. + * + * @param encryptedData A {@link Base64 encoded string containing the encrypted data. + * + * @param headerB64 Base64 encoded string containing the decryption header + * produced during encryption. + * + * @param keyB64 Base64 encoded string containing the encryption key. This is + * expected to be the key of the file with which {@link encryptedDataB64} is + * associated. + * + * @returns The decrypted metadata bytes. + */ +export const decryptFileAssociatedDataFromB64 = async ( + encryptedDataB64: string, + decryptionHeaderB64: string, + keyB64: string, +) => + libsodium.decryptChaChaOneShot( + await libsodium.fromB64(encryptedDataB64), + await libsodium.fromB64(decryptionHeaderB64), + keyB64, + ); + + /** * A variant of {@link encryptFileAssociatedData} that returns the Base64 * encoded encrypted data instead of its raw bytes. diff --git a/web/packages/shared/crypto/internal/libsodium.ts b/web/packages/shared/crypto/internal/libsodium.ts index d553d8bbba..b1896c6cb4 100644 --- a/web/packages/shared/crypto/internal/libsodium.ts +++ b/web/packages/shared/crypto/internal/libsodium.ts @@ -113,15 +113,27 @@ export async function fromHex(input: string) { /** * Encrypt the given {@link data} using the given (Base64 encoded) key. * - * This uses the same stream encryption algorithm pair (XChaCha20 stream cipher - * with Poly1305 MAC authentication) that we use for encrypting, well, other - * streams, like the file's contents. + * This uses the same stream encryption algorithm (XChaCha20 stream cipher with + * Poly1305 MAC authentication) that we use for encrypting other streams, in + * particular the actual file's contents. * * The difference here is that this function does a one shot instead of a - * streaming encryption. As such, this is only meant to be used for relatively - * small amounts of data. + * streaming encryption. This is only meant to be used for relatively small + * amounts of data (few MBs). * - * Ref: https://doc.libsodium.org/secret-key_cryptography/secretstream + * See: https://doc.libsodium.org/secret-key_cryptography/secretstream + * + * Libsodium also provides the `crypto_secretbox_easy` APIs for one shot + * encryption, which we do use in other places where we need to one shot + * encryption of independent bits of data. + * + * See: https://doc.libsodium.org/secret-key_cryptography/secretbox + * + * The difference here is that this function is meant to used for data + * associated with a file. There is no technical reason to do it that way, just + * that all data associated with a file, including its actual contents, use the + * same underlying (streaming) libsodium APIs. While other independent free + * standing encryption needs are covered using the secretbox APIs. * * @param data A {@link Uint8Array} containing the bytes that we want to * encrypt. From bd42650b9ee7e511bc513c71f6d4afdfba8ae534 Mon Sep 17 00:00:00 2001 From: Manav Rathi Date: Mon, 5 Aug 2024 13:28:02 +0530 Subject: [PATCH 09/35] More docs etc --- web/packages/base/crypto/ente.ts | 103 ++++++++---------- .../shared/crypto/internal/libsodium.ts | 50 ++++++++- 2 files changed, 92 insertions(+), 61 deletions(-) diff --git a/web/packages/base/crypto/ente.ts b/web/packages/base/crypto/ente.ts index d29422fc17..335330fde8 100644 --- a/web/packages/base/crypto/ente.ts +++ b/web/packages/base/crypto/ente.ts @@ -26,43 +26,14 @@ import * as libsodium from "@ente/shared/crypto/internal/libsodium"; * * See {@link encryptChaChaOneShot} for the implementation details. * - * @param data The data (bytes) to encrypt. + * @param data A {@link Uint8Array} containing the bytes to encrypt. * * @param keyB64 Base64 encoded string containing the encryption key. This is * expected to the key of the file with which {@link data} is associated. * * @returns The encrypted data and the (Base64 encoded) decryption header. */ -export const encryptFileAssociatedData = (data: Uint8Array, keyB64: string) => - libsodium.encryptChaChaOneShot(data, keyB64); - -/** - * Decrypt arbitrary data associated with a file using the file's key. - * - * This is the sibling of {@link encryptFileAssociatedData}. - * - * @param encryptedData A {@link Base64 encoded string containing the encrypted data. - * - * @param headerB64 Base64 encoded string containing the decryption header - * produced during encryption. - * - * @param keyB64 Base64 encoded string containing the encryption key. This is - * expected to be the key of the file with which {@link encryptedDataB64} is - * associated. - * - * @returns The decrypted metadata bytes. - */ -export const decryptFileAssociatedDataFromB64 = async ( - encryptedDataB64: string, - decryptionHeaderB64: string, - keyB64: string, -) => - libsodium.decryptChaChaOneShot( - await libsodium.fromB64(encryptedDataB64), - await libsodium.fromB64(decryptionHeaderB64), - keyB64, - ); - +export const encryptFileAssociatedData = libsodium.encryptChaChaOneShot; /** * A variant of {@link encryptFileAssociatedData} that returns the Base64 @@ -85,6 +56,51 @@ export const encryptFileAssociatedDataToB64 = async ( }; }; +/** + * A variant of {@link encryptFileAssociatedData} tailored for encrypting the + * various metadata fields associated with a file. + * + * Instead of raw bytes, it takes as input an arbitrary JSON object which it + * encodes into a string, and encrypts that. Also, instead of returning the raw + * encrypted bytes, it returns their Base64 encoded string representation. + */ +export const encryptFileMetadata = async ( + // Arbitrary JSON really, but since TypeScript doesn't have a native JSON + // type use a string Record as a standin replacement. + metadata: Record, + keyB64: string, +) => { + const textEncoder = new TextEncoder(); + const encodedMetadata = textEncoder.encode(JSON.stringify(metadata)); + + const { encryptedData, decryptionHeaderB64 } = + await encryptFileAssociatedData(encodedMetadata, keyB64); + return { + encryptedData: await libsodium.toB64(encryptedData), + decryptionHeaderB64, + }; +}; + +/** + * Decrypt arbitrary data associated with a file using the file's key. + * + * This is the sibling of {@link encryptFileAssociatedData}. + * + * See {@link decryptChaChaOneShot2} for the implementation details. + * + * @param encryptedData A {@link Uint8Array} containing the bytes to decrypt. + * + * @param header A Base64 string containing the bytes of the decryption header + * that was produced during encryption. + * + * @param keyB64 Base64 encoded string containing the encryption key. This is + * expected to be the key of the file with which {@link encryptedDataB64} is + * associated. + * + * @returns The decrypted bytes. + */ +export const decryptFileAssociatedData = libsodium.decryptChaChaOneShot2; + /** * Decrypt arbitrary data associated with a file using the file's key. * @@ -111,28 +127,3 @@ export const decryptFileAssociatedDataFromB64 = async ( await libsodium.fromB64(decryptionHeaderB64), keyB64, ); - -/** - * A variant of {@link encryptFileAssociatedData} tailored for encrypting the - * various metadata fields associated with a file. - * - * Instead of raw bytes, it takes as input an arbitrary JSON object which it - * encodes into a string, and encrypts that. Also, instead of returning the raw - * encrypted bytes, it returns their Base64 encoded string representation. - */ -export const encryptFileMetadata = async ( - // Arbitrary JSON really, but since TypeScript doesn't have a native JSON - // type use a string Record as a standin replacement. - metadata: Record, - keyB64: string, -) => { - const textEncoder = new TextEncoder(); - const encodedMetadata = textEncoder.encode(JSON.stringify(metadata)); - - const { encryptedData, decryptionHeaderB64 } = - await encryptFileAssociatedData(encodedMetadata, keyB64); - return { - encryptedData: await libsodium.toB64(encryptedData), - decryptionHeaderB64, - }; -}; diff --git a/web/packages/shared/crypto/internal/libsodium.ts b/web/packages/shared/crypto/internal/libsodium.ts index b1896c6cb4..97bb46e265 100644 --- a/web/packages/shared/crypto/internal/libsodium.ts +++ b/web/packages/shared/crypto/internal/libsodium.ts @@ -113,6 +113,10 @@ export async function fromHex(input: string) { /** * Encrypt the given {@link data} using the given (Base64 encoded) key. * + * Use {@link decryptChaChaOneShot} to decrypt the result. + * + * [Note: Salsa and ChaCha] + * * This uses the same stream encryption algorithm (XChaCha20 stream cipher with * Poly1305 MAC authentication) that we use for encrypting other streams, in * particular the actual file's contents. @@ -127,6 +131,9 @@ export async function fromHex(input: string) { * encryption, which we do use in other places where we need to one shot * encryption of independent bits of data. * + * These secretbox APIs use XSalsa20 with Poly1305. XSalsa20 is a minor variant + * (predecessor in fact) of XChaCha20. + * * See: https://doc.libsodium.org/secret-key_cryptography/secretbox * * The difference here is that this function is meant to used for data @@ -242,15 +249,48 @@ export async function encryptFileChunk( return pushResult; } -export async function decryptChaChaOneShot( +/** + * Decrypt the result of {@link encryptChaChaOneShot}. + * + * @param encryptedData A {@link Uint8Array} containing the bytes to decrypt. + * + * @param header A Base64 string containing the bytes of the decryption header + * that was produced during encryption. + * + * @param keyB64 The Base64 string containing the key that was used to encrypt + * the data. + * + * @returns The decrypted bytes. + * + * @returns The decrypted metadata bytes. + */ +export const decryptChaChaOneShot2 = async ( + encryptedData: Uint8Array, + headerB64: string, + keyB64: string, +) => { + await sodium.ready; + const pullState = sodium.crypto_secretstream_xchacha20poly1305_init_pull( + await fromB64(headerB64), + await fromB64(keyB64), + ); + const pullResult = sodium.crypto_secretstream_xchacha20poly1305_pull( + pullState, + encryptedData, + null, + ); + return pullResult.message; +}; + +export const decryptChaChaOneShot = async ( data: Uint8Array, header: Uint8Array, - key: string, -) { + keyB64: string, +) => { await sodium.ready; const pullState = sodium.crypto_secretstream_xchacha20poly1305_init_pull( header, - await fromB64(key), + await fromB64(keyB64), ); const pullResult = sodium.crypto_secretstream_xchacha20poly1305_pull( pullState, @@ -258,7 +298,7 @@ export async function decryptChaChaOneShot( null, ); return pullResult.message; -} +}; export const decryptChaCha = async ( data: Uint8Array, From 5536f7ac039d03013414c851e954cce93e669d8d Mon Sep 17 00:00:00 2001 From: Manav Rathi Date: Mon, 5 Aug 2024 13:42:55 +0530 Subject: [PATCH 10/35] Update the embedding layer --- web/packages/base/crypto/ente.ts | 23 ++++++++++--------- .../new/photos/services/ml/embedding.ts | 9 +++----- 2 files changed, 15 insertions(+), 17 deletions(-) diff --git a/web/packages/base/crypto/ente.ts b/web/packages/base/crypto/ente.ts index 335330fde8..121532e9d3 100644 --- a/web/packages/base/crypto/ente.ts +++ b/web/packages/base/crypto/ente.ts @@ -36,15 +36,16 @@ import * as libsodium from "@ente/shared/crypto/internal/libsodium"; export const encryptFileAssociatedData = libsodium.encryptChaChaOneShot; /** - * A variant of {@link encryptFileAssociatedData} that returns the Base64 - * encoded encrypted data instead of its raw bytes. + * Encrypted the embedding associated with a file using the file's key. * - * This is the sibling of {@link decryptFileAssociatedDataFromB64}. + * This as a variant of {@link encryptFileAssociatedData} tailored for + * encrypting the embeddings (a.k.a. derived data) associated with a file. In + * particular, it returns the encrypted data in the result as a Base64 string + * instead of its bytes. * - * It is useful in cases where the (encrypted) associated data needs to - * transferred as the HTTP POST body. + * Use {@link decryptFileEmbedding} to decrypt the result. */ -export const encryptFileAssociatedDataToB64 = async ( +export const encryptFileEmbedding = async ( data: Uint8Array, keyB64: string, ) => { @@ -102,9 +103,9 @@ export const encryptFileMetadata = async ( export const decryptFileAssociatedData = libsodium.decryptChaChaOneShot2; /** - * Decrypt arbitrary data associated with a file using the file's key. + * Decrypt the embedding associated with a file using the file's key. * - * This is the sibling of {@link encryptFileAssociatedDataToB64}. + * This is the sibling of {@link encryptFileEmbedding}. * * @param encryptedDataB64 Base64 encoded string containing the encrypted data. * @@ -117,13 +118,13 @@ export const decryptFileAssociatedData = libsodium.decryptChaChaOneShot2; * * @returns The decrypted metadata bytes. */ -export const decryptFileAssociatedDataFromB64 = async ( +export const decryptFileEmbedding = async ( encryptedDataB64: string, decryptionHeaderB64: string, keyB64: string, ) => - libsodium.decryptChaChaOneShot( + decryptFileAssociatedData( await libsodium.fromB64(encryptedDataB64), - await libsodium.fromB64(decryptionHeaderB64), + decryptionHeaderB64, keyB64, ); diff --git a/web/packages/new/photos/services/ml/embedding.ts b/web/packages/new/photos/services/ml/embedding.ts index d405327513..2c96f43152 100644 --- a/web/packages/new/photos/services/ml/embedding.ts +++ b/web/packages/new/photos/services/ml/embedding.ts @@ -1,7 +1,4 @@ -import { - decryptFileAssociatedDataFromB64, - encryptFileAssociatedDataToB64, -} from "@/base/crypto/ente"; +import { decryptFileEmbedding, encryptFileEmbedding } from "@/base/crypto/ente"; import { authenticatedRequestHeaders, ensureOk } from "@/base/http"; import log from "@/base/log"; import { apiURL } from "@/base/origins"; @@ -198,7 +195,7 @@ export const fetchDerivedData = async ( } try { - const decryptedBytes = await decryptFileAssociatedDataFromB64( + const decryptedBytes = await decryptFileEmbedding( remoteEmbedding.encryptedEmbedding, remoteEmbedding.decryptionHeader, file.key, @@ -297,7 +294,7 @@ const putEmbedding = async ( embedding: Uint8Array, ) => { const { encryptedDataB64, decryptionHeaderB64 } = - await encryptFileAssociatedDataToB64(embedding, enteFile.key); + await encryptFileEmbedding(embedding, enteFile.key); const res = await fetch(await apiURL("/embeddings"), { method: "PUT", From 13ab0d430936e644de7964daf854d1df47257ec0 Mon Sep 17 00:00:00 2001 From: Manav Rathi Date: Mon, 5 Aug 2024 13:49:21 +0530 Subject: [PATCH 11/35] enc new --- web/packages/base/crypto/ente.ts | 19 ++++++++++++------- 1 file changed, 12 insertions(+), 7 deletions(-) diff --git a/web/packages/base/crypto/ente.ts b/web/packages/base/crypto/ente.ts index 121532e9d3..142bf3e66c 100644 --- a/web/packages/base/crypto/ente.ts +++ b/web/packages/base/crypto/ente.ts @@ -58,16 +58,21 @@ export const encryptFileEmbedding = async ( }; /** - * A variant of {@link encryptFileAssociatedData} tailored for encrypting the - * various metadata fields associated with a file. + * Encrypt the metadata associated with a file using the file's key. * - * Instead of raw bytes, it takes as input an arbitrary JSON object which it - * encodes into a string, and encrypts that. Also, instead of returning the raw - * encrypted bytes, it returns their Base64 encoded string representation. + * This is a variant of {@link encryptFileAssociatedData} tailored for + * encrypting any of the metadata fields (See: [Note: Metadatum]) associated + * with a file. 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. + * + * Use {@link decryptFileMetadata} to decrypt the result. + * + * @param metadata The JSON value to encrypt. It can be an arbitrary JSON value, + * but since TypeScript currently doesn't have a native JSON type, it is typed + * as a `Record` (which is also what metadata fields are). */ export const encryptFileMetadata = async ( - // Arbitrary JSON really, but since TypeScript doesn't have a native JSON - // type use a string Record as a standin replacement. metadata: Record, keyB64: string, ) => { From 22b2c49b63256bc793f2ddfa9b98f9a2a29a174f Mon Sep 17 00:00:00 2001 From: Manav Rathi Date: Mon, 5 Aug 2024 13:57:05 +0530 Subject: [PATCH 12/35] metadata --- web/packages/base/crypto/ente.ts | 55 +++++++++++++++++++++++++------- 1 file changed, 43 insertions(+), 12 deletions(-) diff --git a/web/packages/base/crypto/ente.ts b/web/packages/base/crypto/ente.ts index 142bf3e66c..7c6f3a0fbf 100644 --- a/web/packages/base/crypto/ente.ts +++ b/web/packages/base/crypto/ente.ts @@ -76,8 +76,7 @@ export const encryptFileMetadata = async ( metadata: Record, keyB64: string, ) => { - const textEncoder = new TextEncoder(); - const encodedMetadata = textEncoder.encode(JSON.stringify(metadata)); + const encodedMetadata = new TextEncoder().encode(JSON.stringify(metadata)); const { encryptedData, decryptionHeaderB64 } = await encryptFileAssociatedData(encodedMetadata, keyB64); @@ -96,10 +95,10 @@ export const encryptFileMetadata = async ( * * @param encryptedData A {@link Uint8Array} containing the bytes to decrypt. * - * @param header A Base64 string containing the bytes of the decryption header - * that was produced during encryption. + * @param headerB64 A Base64 string containing the decryption header that was + * produced during encryption. * - * @param keyB64 Base64 encoded string containing the encryption key. This is + * @param keyB64 A Base64 encoded string containing the encryption key. This is * expected to be the key of the file with which {@link encryptedDataB64} is * associated. * @@ -112,16 +111,15 @@ export const decryptFileAssociatedData = libsodium.decryptChaChaOneShot2; * * This is the sibling of {@link encryptFileEmbedding}. * - * @param encryptedDataB64 Base64 encoded string containing the encrypted data. + * @param encryptedDataB64 A Base64 string containing the encrypted embedding. * - * @param headerB64 Base64 encoded string containing the decryption header - * produced during encryption. + * @param headerB64 A Base64 string containing the decryption header produced + * during encryption. * - * @param keyB64 Base64 encoded string containing the encryption key. This is - * expected to be the key of the file with which {@link encryptedDataB64} is - * associated. + * @param keyB64 A Base64 string containing the encryption key. This is expected + * to be the key of the file with which {@link encryptedDataB64} is associated. * - * @returns The decrypted metadata bytes. + * @returns The decrypted metadata JSON object. */ export const decryptFileEmbedding = async ( encryptedDataB64: string, @@ -133,3 +131,36 @@ export const decryptFileEmbedding = async ( decryptionHeaderB64, keyB64, ); + +/** + * Decrypt the metadata associated with a file using the file's key. + * + * This is the sibling of {@link decryptFileMetadata}. + * + * @param encryptedDataB64 Base64 encoded string containing the encrypted data. + * + * @param headerB64 Base64 encoded string containing the decryption header + * produced during encryption. + * + * @param keyB64 Base64 encoded string containing the encryption key. This is + * expected to be the key of the file with which {@link encryptedDataB64} is + * associated. + * + * @returns The decrypted JSON value. Since TypeScript does not have a native + * JSON type, we need to return it as an `unknown`. + */ + +export const decryptFileMetadata = async ( + encryptedDataB64: string, + decryptionHeaderB64: string, + keyB64: string, +) => + JSON.parse( + new TextDecoder().decode( + await decryptFileAssociatedData( + await libsodium.fromB64(encryptedDataB64), + decryptionHeaderB64, + keyB64, + ), + ), + ) as unknown; From 8ec2d3e87cb615480bf4fc79f841c8c344b05970 Mon Sep 17 00:00:00 2001 From: Manav Rathi Date: Mon, 5 Aug 2024 14:01:46 +0530 Subject: [PATCH 13/35] Forward --- .../shared/crypto/internal/crypto.worker.ts | 20 +++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/web/packages/shared/crypto/internal/crypto.worker.ts b/web/packages/shared/crypto/internal/crypto.worker.ts index e2367aa7c5..2bc7215dd1 100644 --- a/web/packages/shared/crypto/internal/crypto.worker.ts +++ b/web/packages/shared/crypto/internal/crypto.worker.ts @@ -1,3 +1,4 @@ +import * as ente from "@/base/crypto/ente"; import * as libsodium from "@ente/shared/crypto/internal/libsodium"; import * as Comlink from "comlink"; import type { StateAddress } from "libsodium-wrappers"; @@ -31,6 +32,25 @@ export class DedicatedCryptoWorker { return libsodium.decryptChaCha(fileData, header, key); } + async encryptFileMetadata( + metadata: Record, + keyB64: string, + ) { + return ente.encryptFileMetadata(metadata, keyB64); + } + + async decryptFileMetadata( + encryptedDataB64: string, + decryptionHeaderB64: string, + keyB64: string, + ) { + return ente.decryptFileMetadata( + encryptedDataB64, + decryptionHeaderB64, + keyB64, + ); + } + async encryptMetadata(metadata: Object, key: string) { const encodedMetadata = textEncoder.encode(JSON.stringify(metadata)); From 4c28646ecc2b0eeb0e0b5795455e6b6daf4d6743 Mon Sep 17 00:00:00 2001 From: Manav Rathi Date: Mon, 5 Aug 2024 14:18:49 +0530 Subject: [PATCH 14/35] Expand to collections --- web/apps/photos/src/services/fileService.ts | 8 +++--- web/packages/base/crypto/ente.ts | 27 ++++++++++--------- .../shared/crypto/internal/crypto.worker.ts | 11 +++----- 3 files changed, 22 insertions(+), 24 deletions(-) diff --git a/web/apps/photos/src/services/fileService.ts b/web/apps/photos/src/services/fileService.ts index 37cbb42798..b6194ac7cf 100644 --- a/web/apps/photos/src/services/fileService.ts +++ b/web/apps/photos/src/services/fileService.ts @@ -238,8 +238,8 @@ export const updateFilePublicMagicMetadata = async ( file, updatedPublicMagicMetadata: updatePublicMagicMetadata, } of fileWithUpdatedPublicMagicMetadataList) { - const { file: encryptedPubMagicMetadata } = - await cryptoWorker.encryptMetadata( + const { encryptedData, decryptionHeaderB64 } = + await cryptoWorker.encryptMetadata2( updatePublicMagicMetadata.data, file.key, ); @@ -248,8 +248,8 @@ export const updateFilePublicMagicMetadata = async ( magicMetadata: { version: updatePublicMagicMetadata.version, count: updatePublicMagicMetadata.count, - data: encryptedPubMagicMetadata.encryptedData, - header: encryptedPubMagicMetadata.decryptionHeader, + data: encryptedData, + header: decryptionHeaderB64, }, }); } diff --git a/web/packages/base/crypto/ente.ts b/web/packages/base/crypto/ente.ts index 7c6f3a0fbf..dc8313c4bf 100644 --- a/web/packages/base/crypto/ente.ts +++ b/web/packages/base/crypto/ente.ts @@ -58,24 +58,24 @@ export const encryptFileEmbedding = async ( }; /** - * Encrypt the metadata associated with a file using the file's key. + * Encrypt the metadata associated with a file or a collection using the file's + * or the collection's key, respectively. * * This is a variant of {@link encryptFileAssociatedData} tailored for * encrypting any of the metadata fields (See: [Note: Metadatum]) associated - * with a file. 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. + * with a file or a collection. * - * Use {@link decryptFileMetadata} to decrypt the result. + * 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. + * + * Use {@link decryptMetadata} to decrypt the result. * * @param metadata The JSON value to encrypt. It can be an arbitrary JSON value, * but since TypeScript currently doesn't have a native JSON type, it is typed - * as a `Record` (which is also what metadata fields are). + * as an unknown. */ -export const encryptFileMetadata = async ( - metadata: Record, - keyB64: string, -) => { +export const encryptMetadata = async (metadata: unknown, keyB64: string) => { const encodedMetadata = new TextEncoder().encode(JSON.stringify(metadata)); const { encryptedData, decryptionHeaderB64 } = @@ -133,9 +133,10 @@ export const decryptFileEmbedding = async ( ); /** - * Decrypt the metadata associated with a file using the file's key. + * Decrypt the metadata associated with a file or a collection using the file's + * key or the collection's key, respectively. * - * This is the sibling of {@link decryptFileMetadata}. + * This is the sibling of {@link decryptMetadata}. * * @param encryptedDataB64 Base64 encoded string containing the encrypted data. * @@ -150,7 +151,7 @@ export const decryptFileEmbedding = async ( * JSON type, we need to return it as an `unknown`. */ -export const decryptFileMetadata = async ( +export const decryptMetadata = async ( encryptedDataB64: string, decryptionHeaderB64: string, keyB64: string, diff --git a/web/packages/shared/crypto/internal/crypto.worker.ts b/web/packages/shared/crypto/internal/crypto.worker.ts index 2bc7215dd1..433f7f6009 100644 --- a/web/packages/shared/crypto/internal/crypto.worker.ts +++ b/web/packages/shared/crypto/internal/crypto.worker.ts @@ -32,19 +32,16 @@ export class DedicatedCryptoWorker { return libsodium.decryptChaCha(fileData, header, key); } - async encryptFileMetadata( - metadata: Record, - keyB64: string, - ) { - return ente.encryptFileMetadata(metadata, keyB64); + async encryptMetadata2(metadata: unknown, keyB64: string) { + return ente.encryptMetadata(metadata, keyB64); } - async decryptFileMetadata( + async decryptMetadata2( encryptedDataB64: string, decryptionHeaderB64: string, keyB64: string, ) { - return ente.decryptFileMetadata( + return ente.decryptMetadata( encryptedDataB64, decryptionHeaderB64, keyB64, From edbe40d2fa4183b0917aa60c2023c290f1c2020d Mon Sep 17 00:00:00 2001 From: Manav Rathi Date: Mon, 5 Aug 2024 14:21:03 +0530 Subject: [PATCH 15/35] Use --- web/apps/photos/src/services/fileService.ts | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/web/apps/photos/src/services/fileService.ts b/web/apps/photos/src/services/fileService.ts index b6194ac7cf..6d286acf72 100644 --- a/web/apps/photos/src/services/fileService.ts +++ b/web/apps/photos/src/services/fileService.ts @@ -191,8 +191,8 @@ export const updateFileMagicMetadata = async ( file, updatedMagicMetadata, } of fileWithUpdatedMagicMetadataList) { - const { file: encryptedMagicMetadata } = - await cryptoWorker.encryptMetadata( + const { encryptedData, decryptionHeaderB64 } = + await cryptoWorker.encryptMetadata2( updatedMagicMetadata.data, file.key, ); @@ -201,8 +201,8 @@ export const updateFileMagicMetadata = async ( magicMetadata: { version: updatedMagicMetadata.version, count: updatedMagicMetadata.count, - data: encryptedMagicMetadata.encryptedData, - header: encryptedMagicMetadata.decryptionHeader, + data: encryptedData, + header: decryptionHeaderB64, }, }); } From 9ff4aa47d064c77d394268a20566022cc3a0c91d Mon Sep 17 00:00:00 2001 From: Manav Rathi Date: Mon, 5 Aug 2024 14:22:28 +0530 Subject: [PATCH 16/35] Collection --- .../photos/src/services/collectionService.ts | 19 ++++++++++--------- 1 file changed, 10 insertions(+), 9 deletions(-) diff --git a/web/apps/photos/src/services/collectionService.ts b/web/apps/photos/src/services/collectionService.ts index dae58a96ae..1c6c5ab746 100644 --- a/web/apps/photos/src/services/collectionService.ts +++ b/web/apps/photos/src/services/collectionService.ts @@ -426,8 +426,8 @@ const createCollection = async ( let encryptedMagicMetadata: EncryptedMagicMetadata; if (magicMetadataProps) { const magicMetadata = await updateMagicMetadata(magicMetadataProps); - const { file: encryptedMagicMetadataProps } = - await cryptoWorker.encryptMetadata( + const encryptedMagicMetadataProps = + await cryptoWorker.encryptMetadata2( magicMetadataProps, collectionKey, ); @@ -435,7 +435,7 @@ const createCollection = async ( encryptedMagicMetadata = { ...magicMetadata, data: encryptedMagicMetadataProps.encryptedData, - header: encryptedMagicMetadataProps.decryptionHeader, + header: encryptedMagicMetadataProps.decryptionHeaderB64, }; } const newCollection: EncryptedCollection = { @@ -799,18 +799,19 @@ export const updateCollectionMagicMetadata = async ( const cryptoWorker = await ComlinkCryptoWorker.getInstance(); - const { file: encryptedMagicMetadata } = await cryptoWorker.encryptMetadata( - updatedMagicMetadata.data, - collection.key, - ); + const { encryptedData, decryptionHeaderB64 } = + await cryptoWorker.encryptMetadata2( + updatedMagicMetadata.data, + collection.key, + ); const reqBody: UpdateMagicMetadataRequest = { id: collection.id, magicMetadata: { version: updatedMagicMetadata.version, count: updatedMagicMetadata.count, - data: encryptedMagicMetadata.encryptedData, - header: encryptedMagicMetadata.decryptionHeader, + data: encryptedData, + header: decryptionHeaderB64, }, }; From 6b52f1e53bfeab610631e26cf345ed3c927b7aa7 Mon Sep 17 00:00:00 2001 From: Manav Rathi Date: Mon, 5 Aug 2024 14:26:45 +0530 Subject: [PATCH 17/35] all but 1 --- .../photos/src/services/collectionService.ts | 32 ++++++++++--------- web/apps/photos/src/services/fileService.ts | 8 ++--- web/packages/base/crypto/ente.ts | 4 ++- 3 files changed, 24 insertions(+), 20 deletions(-) diff --git a/web/apps/photos/src/services/collectionService.ts b/web/apps/photos/src/services/collectionService.ts index 1c6c5ab746..fbb8aa2ad6 100644 --- a/web/apps/photos/src/services/collectionService.ts +++ b/web/apps/photos/src/services/collectionService.ts @@ -434,7 +434,7 @@ const createCollection = async ( encryptedMagicMetadata = { ...magicMetadata, - data: encryptedMagicMetadataProps.encryptedData, + data: encryptedMagicMetadataProps.encryptedDataB64, header: encryptedMagicMetadataProps.decryptionHeaderB64, }; } @@ -799,7 +799,7 @@ export const updateCollectionMagicMetadata = async ( const cryptoWorker = await ComlinkCryptoWorker.getInstance(); - const { encryptedData, decryptionHeaderB64 } = + const { encryptedDataB64, decryptionHeaderB64 } = await cryptoWorker.encryptMetadata2( updatedMagicMetadata.data, collection.key, @@ -810,7 +810,7 @@ export const updateCollectionMagicMetadata = async ( magicMetadata: { version: updatedMagicMetadata.version, count: updatedMagicMetadata.count, - data: encryptedData, + data: encryptedDataB64, header: decryptionHeaderB64, }, }; @@ -844,18 +844,19 @@ export const updateSharedCollectionMagicMetadata = async ( const cryptoWorker = await ComlinkCryptoWorker.getInstance(); - const { file: encryptedMagicMetadata } = await cryptoWorker.encryptMetadata( - updatedMagicMetadata.data, - collection.key, - ); + const { encryptedDataB64, decryptionHeaderB64 } = + await cryptoWorker.encryptMetadata2( + updatedMagicMetadata.data, + collection.key, + ); const reqBody: UpdateMagicMetadataRequest = { id: collection.id, magicMetadata: { version: updatedMagicMetadata.version, count: updatedMagicMetadata.count, - data: encryptedMagicMetadata.encryptedData, - header: encryptedMagicMetadata.decryptionHeader, + data: encryptedDataB64, + header: decryptionHeaderB64, }, }; @@ -888,18 +889,19 @@ export const updatePublicCollectionMagicMetadata = async ( const cryptoWorker = await ComlinkCryptoWorker.getInstance(); - const { file: encryptedMagicMetadata } = await cryptoWorker.encryptMetadata( - updatedPublicMagicMetadata.data, - collection.key, - ); + const { encryptedDataB64, decryptionHeaderB64 } = + await cryptoWorker.encryptMetadata2( + updatedPublicMagicMetadata.data, + collection.key, + ); const reqBody: UpdateMagicMetadataRequest = { id: collection.id, magicMetadata: { version: updatedPublicMagicMetadata.version, count: updatedPublicMagicMetadata.count, - data: encryptedMagicMetadata.encryptedData, - header: encryptedMagicMetadata.decryptionHeader, + data: encryptedDataB64, + header: decryptionHeaderB64, }, }; diff --git a/web/apps/photos/src/services/fileService.ts b/web/apps/photos/src/services/fileService.ts index 6d286acf72..dc77efb8a8 100644 --- a/web/apps/photos/src/services/fileService.ts +++ b/web/apps/photos/src/services/fileService.ts @@ -191,7 +191,7 @@ export const updateFileMagicMetadata = async ( file, updatedMagicMetadata, } of fileWithUpdatedMagicMetadataList) { - const { encryptedData, decryptionHeaderB64 } = + const { encryptedDataB64, decryptionHeaderB64 } = await cryptoWorker.encryptMetadata2( updatedMagicMetadata.data, file.key, @@ -201,7 +201,7 @@ export const updateFileMagicMetadata = async ( magicMetadata: { version: updatedMagicMetadata.version, count: updatedMagicMetadata.count, - data: encryptedData, + data: encryptedDataB64, header: decryptionHeaderB64, }, }); @@ -238,7 +238,7 @@ export const updateFilePublicMagicMetadata = async ( file, updatedPublicMagicMetadata: updatePublicMagicMetadata, } of fileWithUpdatedPublicMagicMetadataList) { - const { encryptedData, decryptionHeaderB64 } = + const { encryptedDataB64, decryptionHeaderB64 } = await cryptoWorker.encryptMetadata2( updatePublicMagicMetadata.data, file.key, @@ -248,7 +248,7 @@ export const updateFilePublicMagicMetadata = async ( magicMetadata: { version: updatePublicMagicMetadata.version, count: updatePublicMagicMetadata.count, - data: encryptedData, + data: encryptedDataB64, header: decryptionHeaderB64, }, }); diff --git a/web/packages/base/crypto/ente.ts b/web/packages/base/crypto/ente.ts index dc8313c4bf..d23222a205 100644 --- a/web/packages/base/crypto/ente.ts +++ b/web/packages/base/crypto/ente.ts @@ -74,6 +74,8 @@ export const encryptFileEmbedding = async ( * @param metadata The JSON value to encrypt. It can be an arbitrary JSON value, * but since TypeScript currently doesn't have a native JSON type, it is typed * as an unknown. + * + * @returns The encrypted data and decryption header, both as Base64 strings. */ export const encryptMetadata = async (metadata: unknown, keyB64: string) => { const encodedMetadata = new TextEncoder().encode(JSON.stringify(metadata)); @@ -81,7 +83,7 @@ export const encryptMetadata = async (metadata: unknown, keyB64: string) => { const { encryptedData, decryptionHeaderB64 } = await encryptFileAssociatedData(encodedMetadata, keyB64); return { - encryptedData: await libsodium.toB64(encryptedData), + encryptedDataB64: await libsodium.toB64(encryptedData), decryptionHeaderB64, }; }; From 0d8a49317aea203bea9c599a0969d0c723845afc Mon Sep 17 00:00:00 2001 From: Manav Rathi Date: Mon, 5 Aug 2024 14:32:57 +0530 Subject: [PATCH 18/35] Upload --- .../src/services/upload/uploadService.ts | 24 +++++++++++++------ 1 file changed, 17 insertions(+), 7 deletions(-) diff --git a/web/apps/photos/src/services/upload/uploadService.ts b/web/apps/photos/src/services/upload/uploadService.ts index 3de3b96e85..b6e28d9d12 100644 --- a/web/apps/photos/src/services/upload/uploadService.ts +++ b/web/apps/photos/src/services/upload/uploadService.ts @@ -245,6 +245,11 @@ interface LocalFileAttributes< decryptionHeader: string; } +interface EncryptedMetadata { + encryptedDataB64: string; + decryptionHeaderB64: string; +} + interface EncryptionResult< T extends string | Uint8Array | EncryptedFileStream, > { @@ -255,7 +260,7 @@ interface EncryptionResult< interface ProcessedFile { file: LocalFileAttributes; thumbnail: LocalFileAttributes; - metadata: LocalFileAttributes; + metadata: EncryptedMetadata; pubMagicMetadata: EncryptedMagicMetadata; localID: number; } @@ -1124,20 +1129,22 @@ const encryptFile = async ( fileKey, ); - const { file: encryptedMetadata } = await worker.encryptMetadata( + const encryptedMetadata = await worker.encryptMetadata2( file.metadata, fileKey, ); let encryptedPubMagicMetadata: EncryptedMagicMetadata; if (file.pubMagicMetadata) { - const { file: encryptedPubMagicMetadataData } = - await worker.encryptMetadata(file.pubMagicMetadata.data, fileKey); + const encryptedPubMagicMetadataData = await worker.encryptMetadata2( + file.pubMagicMetadata.data, + fileKey, + ); encryptedPubMagicMetadata = { version: file.pubMagicMetadata.version, count: file.pubMagicMetadata.count, - data: encryptedPubMagicMetadataData.encryptedData, - header: encryptedPubMagicMetadataData.decryptionHeader, + data: encryptedPubMagicMetadataData.encryptedDataB64, + header: encryptedPubMagicMetadataData.decryptionHeaderB64, }; } @@ -1267,7 +1274,10 @@ const uploadToBucket = async ( decryptionHeader: file.thumbnail.decryptionHeader, objectKey: thumbnailObjectKey, }, - metadata: file.metadata, + metadata: { + encryptedData: file.metadata.encryptedDataB64, + decryptionHeader: file.metadata.decryptionHeaderB64, + }, pubMagicMetadata: file.pubMagicMetadata, }; return backupedFile; From e60506586ed57f47fcd0d99f83f3c5886bb3b7de Mon Sep 17 00:00:00 2001 From: Manav Rathi Date: Mon, 5 Aug 2024 14:33:29 +0530 Subject: [PATCH 19/35] Prune --- .../shared/crypto/internal/crypto.worker.ts | 15 --------------- 1 file changed, 15 deletions(-) diff --git a/web/packages/shared/crypto/internal/crypto.worker.ts b/web/packages/shared/crypto/internal/crypto.worker.ts index 433f7f6009..400f37c83e 100644 --- a/web/packages/shared/crypto/internal/crypto.worker.ts +++ b/web/packages/shared/crypto/internal/crypto.worker.ts @@ -48,21 +48,6 @@ export class DedicatedCryptoWorker { ); } - async encryptMetadata(metadata: Object, key: string) { - const encodedMetadata = textEncoder.encode(JSON.stringify(metadata)); - - const { encryptedData, decryptionHeaderB64 } = - await libsodium.encryptChaChaOneShot(encodedMetadata, key); - return { - file: { - encryptedData: await libsodium.toB64(encryptedData), - // TODO: - decryptionHeader: decryptionHeaderB64, - }, - key, - }; - } - /** * Encrypt the thumbnail associated with a file. * From 5e3cae39ecbec4f0537e56b0f88c14faa5518e85 Mon Sep 17 00:00:00 2001 From: Manav Rathi Date: Mon, 5 Aug 2024 14:38:21 +0530 Subject: [PATCH 20/35] Entities too --- web/apps/auth/src/services/remote.ts | 8 ++++++-- web/packages/base/crypto/ente.ts | 10 +++++----- web/packages/shared/crypto/internal/crypto.worker.ts | 1 - 3 files changed, 11 insertions(+), 8 deletions(-) diff --git a/web/apps/auth/src/services/remote.ts b/web/apps/auth/src/services/remote.ts index f9061110d9..be0881cb74 100644 --- a/web/apps/auth/src/services/remote.ts +++ b/web/apps/auth/src/services/remote.ts @@ -1,5 +1,6 @@ import log from "@/base/log"; import { apiURL } from "@/base/origins"; +import { ensureString } from "@/utils/ensure"; import ComlinkCryptoWorker from "@ente/shared/crypto"; import { ApiError, CustomError } from "@ente/shared/error"; import HTTPService from "@ente/shared/network/HTTPService"; @@ -29,12 +30,15 @@ export const getAuthCodes = async (): Promise => { if (!entity.header) return undefined; try { const decryptedCode = - await cryptoWorker.decryptMetadata( + await cryptoWorker.decryptMetadata2( entity.encryptedData, entity.header, authenticatorKey, ); - return codeFromURIString(entity.id, decryptedCode); + return codeFromURIString( + entity.id, + ensureString(decryptedCode), + ); } catch (e) { log.error(`Failed to parse codeID ${entity.id}`, e); return undefined; diff --git a/web/packages/base/crypto/ente.ts b/web/packages/base/crypto/ente.ts index d23222a205..8a341db593 100644 --- a/web/packages/base/crypto/ente.ts +++ b/web/packages/base/crypto/ente.ts @@ -58,12 +58,12 @@ export const encryptFileEmbedding = async ( }; /** - * Encrypt the metadata associated with a file or a collection using the file's - * or the collection's key, respectively. + * Encrypt the metadata associated with an Ente object (file, collection or + * entity) using that object's key. * * This is a variant of {@link encryptFileAssociatedData} tailored for * encrypting any of the metadata fields (See: [Note: Metadatum]) associated - * with a file or a collection. + * with a file, collection or entity. * * 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 @@ -135,8 +135,8 @@ export const decryptFileEmbedding = async ( ); /** - * Decrypt the metadata associated with a file or a collection using the file's - * key or the collection's key, respectively. + * Decrypt the metadata associated with an Ente object (file, collection or + * entity) using that object's key. * * This is the sibling of {@link decryptMetadata}. * diff --git a/web/packages/shared/crypto/internal/crypto.worker.ts b/web/packages/shared/crypto/internal/crypto.worker.ts index 400f37c83e..92d02fcab8 100644 --- a/web/packages/shared/crypto/internal/crypto.worker.ts +++ b/web/packages/shared/crypto/internal/crypto.worker.ts @@ -4,7 +4,6 @@ import * as Comlink from "comlink"; import type { StateAddress } from "libsodium-wrappers"; const textDecoder = new TextDecoder(); -const textEncoder = new TextEncoder(); export class DedicatedCryptoWorker { async decryptMetadata( From 25541ecd3a9f83e3eae52ef8e2b85421c78a11aa Mon Sep 17 00:00:00 2001 From: Manav Rathi Date: Mon, 5 Aug 2024 14:41:05 +0530 Subject: [PATCH 21/35] More --- web/apps/cast/src/services/render.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/web/apps/cast/src/services/render.ts b/web/apps/cast/src/services/render.ts index 8f832b320d..29a5dddc24 100644 --- a/web/apps/cast/src/services/render.ts +++ b/web/apps/cast/src/services/render.ts @@ -202,7 +202,7 @@ const decryptEnteFile = async ( keyDecryptionNonce, collectionKey, ); - const fileMetadata = await worker.decryptMetadata( + const fileMetadata = await worker.decryptMetadata2( metadata.encryptedData, metadata.decryptionHeader, fileKey, @@ -237,9 +237,11 @@ const decryptEnteFile = async ( pubMagicMetadata: filePubMagicMetadata, }; if (file.pubMagicMetadata?.data.editedTime) { + // @ts-expect-error TODO: Need to use zod here. file.metadata.creationTime = file.pubMagicMetadata.data.editedTime; } if (file.pubMagicMetadata?.data.editedName) { + // @ts-expect-error TODO: Need to use zod here. file.metadata.title = file.pubMagicMetadata.data.editedName; } // @ts-expect-error TODO: The core types need to be updated to allow the From 61935a00376e3f7ec2c56e91e7b98763299e2cb7 Mon Sep 17 00:00:00 2001 From: Manav Rathi Date: Mon, 5 Aug 2024 14:41:56 +0530 Subject: [PATCH 22/35] More --- web/apps/cast/src/services/render.ts | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/web/apps/cast/src/services/render.ts b/web/apps/cast/src/services/render.ts index 29a5dddc24..7794df3351 100644 --- a/web/apps/cast/src/services/render.ts +++ b/web/apps/cast/src/services/render.ts @@ -212,7 +212,8 @@ const decryptEnteFile = async ( if (magicMetadata?.data) { fileMagicMetadata = { ...encryptedFile.magicMetadata, - data: await worker.decryptMetadata( + // @ts-expect-error TODO: Need to use zod here. + data: await worker.decryptMetadata2( magicMetadata.data, magicMetadata.header, fileKey, @@ -222,7 +223,8 @@ const decryptEnteFile = async ( if (pubMagicMetadata?.data) { filePubMagicMetadata = { ...pubMagicMetadata, - data: await worker.decryptMetadata( + // @ts-expect-error TODO: Need to use zod here. + data: await worker.decryptMetadata2( pubMagicMetadata.data, pubMagicMetadata.header, fileKey, From 506dc36c6925adf8b998fd626714ab418b89b92f Mon Sep 17 00:00:00 2001 From: Manav Rathi Date: Mon, 5 Aug 2024 14:43:36 +0530 Subject: [PATCH 23/35] Rest --- web/apps/photos/src/services/collectionService.ts | 6 +++--- web/apps/photos/src/services/entityService.ts | 3 ++- web/apps/photos/src/services/publicCollectionService.ts | 2 +- web/apps/photos/src/utils/file/index.ts | 6 +++--- web/apps/photos/src/utils/magicMetadata/index.ts | 3 ++- 5 files changed, 11 insertions(+), 9 deletions(-) diff --git a/web/apps/photos/src/services/collectionService.ts b/web/apps/photos/src/services/collectionService.ts index fbb8aa2ad6..8cf8b0ae90 100644 --- a/web/apps/photos/src/services/collectionService.ts +++ b/web/apps/photos/src/services/collectionService.ts @@ -133,7 +133,7 @@ const getCollectionWithSecrets = async ( if (collection.magicMetadata?.data) { collectionMagicMetadata = { ...collection.magicMetadata, - data: await cryptoWorker.decryptMetadata( + data: await cryptoWorker.decryptMetadata2( collection.magicMetadata.data, collection.magicMetadata.header, collectionKey, @@ -144,7 +144,7 @@ const getCollectionWithSecrets = async ( if (collection.pubMagicMetadata?.data) { collectionPublicMagicMetadata = { ...collection.pubMagicMetadata, - data: await cryptoWorker.decryptMetadata( + data: await cryptoWorker.decryptMetadata2( collection.pubMagicMetadata.data, collection.pubMagicMetadata.header, collectionKey, @@ -156,7 +156,7 @@ const getCollectionWithSecrets = async ( if (collection.sharedMagicMetadata?.data) { collectionShareeMagicMetadata = { ...collection.sharedMagicMetadata, - data: await cryptoWorker.decryptMetadata( + data: await cryptoWorker.decryptMetadata2( collection.sharedMagicMetadata.data, collection.sharedMagicMetadata.header, collectionKey, diff --git a/web/apps/photos/src/services/entityService.ts b/web/apps/photos/src/services/entityService.ts index 1e418aa2c6..0d1851c770 100644 --- a/web/apps/photos/src/services/entityService.ts +++ b/web/apps/photos/src/services/entityService.ts @@ -143,6 +143,7 @@ const syncEntity = async (type: EntityType): Promise> => { } const entityKey = await getEntityKey(type); + // @ts-expect-error TODO: Need to use zod here. const newDecryptedEntities: Array> = await Promise.all( response.diff.map(async (entity: EncryptedEntity) => { if (entity.isDeleted) { @@ -152,7 +153,7 @@ const syncEntity = async (type: EntityType): Promise> => { } const { encryptedData, header, ...rest } = entity; const worker = await ComlinkCryptoWorker.getInstance(); - const decryptedData = await worker.decryptMetadata( + const decryptedData = await worker.decryptMetadata2( encryptedData, header, entityKey.data, diff --git a/web/apps/photos/src/services/publicCollectionService.ts b/web/apps/photos/src/services/publicCollectionService.ts index 9c1bed78df..06d24e71d8 100644 --- a/web/apps/photos/src/services/publicCollectionService.ts +++ b/web/apps/photos/src/services/publicCollectionService.ts @@ -329,7 +329,7 @@ export const getPublicCollection = async ( if (fetchedCollection.pubMagicMetadata?.data) { collectionPublicMagicMetadata = { ...fetchedCollection.pubMagicMetadata, - data: await cryptoWorker.decryptMetadata( + data: await cryptoWorker.decryptMetadata2( fetchedCollection.pubMagicMetadata.data, fetchedCollection.pubMagicMetadata.header, collectionKey, diff --git a/web/apps/photos/src/utils/file/index.ts b/web/apps/photos/src/utils/file/index.ts index d94e3d5eff..4704f20849 100644 --- a/web/apps/photos/src/utils/file/index.ts +++ b/web/apps/photos/src/utils/file/index.ts @@ -147,7 +147,7 @@ export async function decryptFile( keyDecryptionNonce, collectionKey, ); - const fileMetadata = await worker.decryptMetadata( + const fileMetadata = await worker.decryptMetadata2( metadata.encryptedData, metadata.decryptionHeader, fileKey, @@ -157,7 +157,7 @@ export async function decryptFile( if (magicMetadata?.data) { fileMagicMetadata = { ...file.magicMetadata, - data: await worker.decryptMetadata( + data: await worker.decryptMetadata2( magicMetadata.data, magicMetadata.header, fileKey, @@ -167,7 +167,7 @@ export async function decryptFile( if (pubMagicMetadata?.data) { filePubMagicMetadata = { ...pubMagicMetadata, - data: await worker.decryptMetadata( + data: await worker.decryptMetadata2( pubMagicMetadata.data, pubMagicMetadata.header, fileKey, diff --git a/web/apps/photos/src/utils/magicMetadata/index.ts b/web/apps/photos/src/utils/magicMetadata/index.ts index 8d94a574f9..d503e3f1c7 100644 --- a/web/apps/photos/src/utils/magicMetadata/index.ts +++ b/web/apps/photos/src/utils/magicMetadata/index.ts @@ -56,7 +56,8 @@ export async function updateMagicMetadata( } if (typeof originalMagicMetadata?.data === "string") { - originalMagicMetadata.data = await cryptoWorker.decryptMetadata( + // @ts-expect-error TODO: Need to use zod here. + originalMagicMetadata.data = await cryptoWorker.decryptMetadata2( originalMagicMetadata.data, originalMagicMetadata.header, decryptionKey, From 9583b31bfc435f13e123526046cad38560514d7e Mon Sep 17 00:00:00 2001 From: Manav Rathi Date: Mon, 5 Aug 2024 14:44:03 +0530 Subject: [PATCH 24/35] Prune --- .../shared/crypto/internal/crypto.worker.ts | 15 --------------- 1 file changed, 15 deletions(-) diff --git a/web/packages/shared/crypto/internal/crypto.worker.ts b/web/packages/shared/crypto/internal/crypto.worker.ts index 92d02fcab8..fdf5ab5fbf 100644 --- a/web/packages/shared/crypto/internal/crypto.worker.ts +++ b/web/packages/shared/crypto/internal/crypto.worker.ts @@ -3,22 +3,7 @@ import * as libsodium from "@ente/shared/crypto/internal/libsodium"; import * as Comlink from "comlink"; import type { StateAddress } from "libsodium-wrappers"; -const textDecoder = new TextDecoder(); - export class DedicatedCryptoWorker { - async decryptMetadata( - encryptedMetadata: string, - header: string, - key: string, - ) { - const encodedMetadata = await libsodium.decryptChaChaOneShot( - await libsodium.fromB64(encryptedMetadata), - await libsodium.fromB64(header), - key, - ); - return JSON.parse(textDecoder.decode(encodedMetadata)); - } - async decryptThumbnail( fileData: Uint8Array, header: Uint8Array, From f8c12ba127a0741ee022388bcf91f4fb07a2771f Mon Sep 17 00:00:00 2001 From: Manav Rathi Date: Mon, 5 Aug 2024 14:44:37 +0530 Subject: [PATCH 25/35] Rename --- web/apps/photos/src/services/collectionService.ts | 6 +++--- web/apps/photos/src/services/entityService.ts | 2 +- web/apps/photos/src/services/publicCollectionService.ts | 2 +- web/apps/photos/src/utils/file/index.ts | 6 +++--- web/apps/photos/src/utils/magicMetadata/index.ts | 2 +- web/packages/shared/crypto/internal/crypto.worker.ts | 2 +- 6 files changed, 10 insertions(+), 10 deletions(-) diff --git a/web/apps/photos/src/services/collectionService.ts b/web/apps/photos/src/services/collectionService.ts index 8cf8b0ae90..fbb8aa2ad6 100644 --- a/web/apps/photos/src/services/collectionService.ts +++ b/web/apps/photos/src/services/collectionService.ts @@ -133,7 +133,7 @@ const getCollectionWithSecrets = async ( if (collection.magicMetadata?.data) { collectionMagicMetadata = { ...collection.magicMetadata, - data: await cryptoWorker.decryptMetadata2( + data: await cryptoWorker.decryptMetadata( collection.magicMetadata.data, collection.magicMetadata.header, collectionKey, @@ -144,7 +144,7 @@ const getCollectionWithSecrets = async ( if (collection.pubMagicMetadata?.data) { collectionPublicMagicMetadata = { ...collection.pubMagicMetadata, - data: await cryptoWorker.decryptMetadata2( + data: await cryptoWorker.decryptMetadata( collection.pubMagicMetadata.data, collection.pubMagicMetadata.header, collectionKey, @@ -156,7 +156,7 @@ const getCollectionWithSecrets = async ( if (collection.sharedMagicMetadata?.data) { collectionShareeMagicMetadata = { ...collection.sharedMagicMetadata, - data: await cryptoWorker.decryptMetadata2( + data: await cryptoWorker.decryptMetadata( collection.sharedMagicMetadata.data, collection.sharedMagicMetadata.header, collectionKey, diff --git a/web/apps/photos/src/services/entityService.ts b/web/apps/photos/src/services/entityService.ts index 0d1851c770..401819b110 100644 --- a/web/apps/photos/src/services/entityService.ts +++ b/web/apps/photos/src/services/entityService.ts @@ -153,7 +153,7 @@ const syncEntity = async (type: EntityType): Promise> => { } const { encryptedData, header, ...rest } = entity; const worker = await ComlinkCryptoWorker.getInstance(); - const decryptedData = await worker.decryptMetadata2( + const decryptedData = await worker.decryptMetadata( encryptedData, header, entityKey.data, diff --git a/web/apps/photos/src/services/publicCollectionService.ts b/web/apps/photos/src/services/publicCollectionService.ts index 06d24e71d8..9c1bed78df 100644 --- a/web/apps/photos/src/services/publicCollectionService.ts +++ b/web/apps/photos/src/services/publicCollectionService.ts @@ -329,7 +329,7 @@ export const getPublicCollection = async ( if (fetchedCollection.pubMagicMetadata?.data) { collectionPublicMagicMetadata = { ...fetchedCollection.pubMagicMetadata, - data: await cryptoWorker.decryptMetadata2( + data: await cryptoWorker.decryptMetadata( fetchedCollection.pubMagicMetadata.data, fetchedCollection.pubMagicMetadata.header, collectionKey, diff --git a/web/apps/photos/src/utils/file/index.ts b/web/apps/photos/src/utils/file/index.ts index 4704f20849..d94e3d5eff 100644 --- a/web/apps/photos/src/utils/file/index.ts +++ b/web/apps/photos/src/utils/file/index.ts @@ -147,7 +147,7 @@ export async function decryptFile( keyDecryptionNonce, collectionKey, ); - const fileMetadata = await worker.decryptMetadata2( + const fileMetadata = await worker.decryptMetadata( metadata.encryptedData, metadata.decryptionHeader, fileKey, @@ -157,7 +157,7 @@ export async function decryptFile( if (magicMetadata?.data) { fileMagicMetadata = { ...file.magicMetadata, - data: await worker.decryptMetadata2( + data: await worker.decryptMetadata( magicMetadata.data, magicMetadata.header, fileKey, @@ -167,7 +167,7 @@ export async function decryptFile( if (pubMagicMetadata?.data) { filePubMagicMetadata = { ...pubMagicMetadata, - data: await worker.decryptMetadata2( + data: await worker.decryptMetadata( pubMagicMetadata.data, pubMagicMetadata.header, fileKey, diff --git a/web/apps/photos/src/utils/magicMetadata/index.ts b/web/apps/photos/src/utils/magicMetadata/index.ts index d503e3f1c7..aac24f9245 100644 --- a/web/apps/photos/src/utils/magicMetadata/index.ts +++ b/web/apps/photos/src/utils/magicMetadata/index.ts @@ -57,7 +57,7 @@ export async function updateMagicMetadata( if (typeof originalMagicMetadata?.data === "string") { // @ts-expect-error TODO: Need to use zod here. - originalMagicMetadata.data = await cryptoWorker.decryptMetadata2( + originalMagicMetadata.data = await cryptoWorker.decryptMetadata( originalMagicMetadata.data, originalMagicMetadata.header, decryptionKey, diff --git a/web/packages/shared/crypto/internal/crypto.worker.ts b/web/packages/shared/crypto/internal/crypto.worker.ts index fdf5ab5fbf..917fe0d0cb 100644 --- a/web/packages/shared/crypto/internal/crypto.worker.ts +++ b/web/packages/shared/crypto/internal/crypto.worker.ts @@ -20,7 +20,7 @@ export class DedicatedCryptoWorker { return ente.encryptMetadata(metadata, keyB64); } - async decryptMetadata2( + async decryptMetadata( encryptedDataB64: string, decryptionHeaderB64: string, keyB64: string, From 8cabf13e5a6f32e13ad5c839d065443a02391488 Mon Sep 17 00:00:00 2001 From: Manav Rathi Date: Mon, 5 Aug 2024 14:45:21 +0530 Subject: [PATCH 26/35] lint --- web/apps/auth/src/services/remote.ts | 2 +- web/apps/cast/src/services/render.ts | 6 +++--- web/apps/photos/src/utils/file/index.ts | 1 + 3 files changed, 5 insertions(+), 4 deletions(-) diff --git a/web/apps/auth/src/services/remote.ts b/web/apps/auth/src/services/remote.ts index be0881cb74..758bd31b66 100644 --- a/web/apps/auth/src/services/remote.ts +++ b/web/apps/auth/src/services/remote.ts @@ -30,7 +30,7 @@ export const getAuthCodes = async (): Promise => { if (!entity.header) return undefined; try { const decryptedCode = - await cryptoWorker.decryptMetadata2( + await cryptoWorker.decryptMetadata( entity.encryptedData, entity.header, authenticatorKey, diff --git a/web/apps/cast/src/services/render.ts b/web/apps/cast/src/services/render.ts index 7794df3351..d512b039ef 100644 --- a/web/apps/cast/src/services/render.ts +++ b/web/apps/cast/src/services/render.ts @@ -202,7 +202,7 @@ const decryptEnteFile = async ( keyDecryptionNonce, collectionKey, ); - const fileMetadata = await worker.decryptMetadata2( + const fileMetadata = await worker.decryptMetadata( metadata.encryptedData, metadata.decryptionHeader, fileKey, @@ -213,7 +213,7 @@ const decryptEnteFile = async ( fileMagicMetadata = { ...encryptedFile.magicMetadata, // @ts-expect-error TODO: Need to use zod here. - data: await worker.decryptMetadata2( + data: await worker.decryptMetadata( magicMetadata.data, magicMetadata.header, fileKey, @@ -224,7 +224,7 @@ const decryptEnteFile = async ( filePubMagicMetadata = { ...pubMagicMetadata, // @ts-expect-error TODO: Need to use zod here. - data: await worker.decryptMetadata2( + data: await worker.decryptMetadata( pubMagicMetadata.data, pubMagicMetadata.header, fileKey, diff --git a/web/apps/photos/src/utils/file/index.ts b/web/apps/photos/src/utils/file/index.ts index d94e3d5eff..5855a91898 100644 --- a/web/apps/photos/src/utils/file/index.ts +++ b/web/apps/photos/src/utils/file/index.ts @@ -177,6 +177,7 @@ export async function decryptFile( return { ...restFileProps, key: fileKey, + // @ts-expect-error TODO: Need to use zod here. metadata: fileMetadata, magicMetadata: fileMagicMetadata, pubMagicMetadata: filePubMagicMetadata, From 219cc405da4d13c78545f6795f9340f7b7ef1686 Mon Sep 17 00:00:00 2001 From: Manav Rathi Date: Mon, 5 Aug 2024 14:58:35 +0530 Subject: [PATCH 27/35] Terms --- web/packages/base/crypto/ente.ts | 50 ++++++++++++++++++-------------- 1 file changed, 29 insertions(+), 21 deletions(-) diff --git a/web/packages/base/crypto/ente.ts b/web/packages/base/crypto/ente.ts index 8a341db593..61bce30ed2 100644 --- a/web/packages/base/crypto/ente.ts +++ b/web/packages/base/crypto/ente.ts @@ -20,25 +20,27 @@ import * as libsodium from "@ente/shared/crypto/internal/libsodium"; /** - * Encrypt arbitrary data associated with a file using the file's key. + * Encrypt arbitrary data associated with an Ente object (file, collection, + * entity) using the object's key. * - * Use {@link decryptFileAssociatedData} to decrypt the result. + * Use {@link decryptAssociatedData} to decrypt the result. * * See {@link encryptChaChaOneShot} for the implementation details. * * @param data A {@link Uint8Array} containing the bytes to encrypt. * - * @param keyB64 Base64 encoded string containing the encryption key. This is - * expected to the key of the file with which {@link data} is associated. + * @param keyB64 Base64 string containing the encryption key. This is expected + * to the key of the object with which {@link data} is associated. For example, + * if this is data associated with a file, then this will be the file's key. * * @returns The encrypted data and the (Base64 encoded) decryption header. */ -export const encryptFileAssociatedData = libsodium.encryptChaChaOneShot; +export const encryptAssociatedData = libsodium.encryptChaChaOneShot; /** * Encrypted the embedding associated with a file using the file's key. * - * This as a variant of {@link encryptFileAssociatedData} tailored for + * This as a variant of {@link encryptAssociatedData} tailored for * encrypting the embeddings (a.k.a. derived data) associated with a file. In * particular, it returns the encrypted data in the result as a Base64 string * instead of its bytes. @@ -49,8 +51,10 @@ export const encryptFileEmbedding = async ( data: Uint8Array, keyB64: string, ) => { - const { encryptedData, decryptionHeaderB64 } = - await encryptFileAssociatedData(data, keyB64); + const { encryptedData, decryptionHeaderB64 } = await encryptAssociatedData( + data, + keyB64, + ); return { encryptedDataB64: await libsodium.toB64(encryptedData), decryptionHeaderB64, @@ -59,11 +63,12 @@ export const encryptFileEmbedding = async ( /** * Encrypt the metadata associated with an Ente object (file, collection or - * entity) using that object's key. + * entity) using the object's key. * - * This is a variant of {@link encryptFileAssociatedData} tailored for - * encrypting any of the metadata fields (See: [Note: Metadatum]) associated - * with a file, collection or entity. + * 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. * * 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 @@ -80,8 +85,10 @@ export const encryptFileEmbedding = async ( export const encryptMetadata = async (metadata: unknown, keyB64: string) => { const encodedMetadata = new TextEncoder().encode(JSON.stringify(metadata)); - const { encryptedData, decryptionHeaderB64 } = - await encryptFileAssociatedData(encodedMetadata, keyB64); + const { encryptedData, decryptionHeaderB64 } = await encryptAssociatedData( + encodedMetadata, + keyB64, + ); return { encryptedDataB64: await libsodium.toB64(encryptedData), decryptionHeaderB64, @@ -89,9 +96,10 @@ export const encryptMetadata = async (metadata: unknown, keyB64: string) => { }; /** - * Decrypt arbitrary data associated with a file using the file's key. + * Decrypt arbitrary data associated with an Ente object (file, collection or + * entity) using the object's key. * - * This is the sibling of {@link encryptFileAssociatedData}. + * This is the sibling of {@link encryptAssociatedData}. * * See {@link decryptChaChaOneShot2} for the implementation details. * @@ -106,7 +114,7 @@ export const encryptMetadata = async (metadata: unknown, keyB64: string) => { * * @returns The decrypted bytes. */ -export const decryptFileAssociatedData = libsodium.decryptChaChaOneShot2; +export const decryptAssociatedData = libsodium.decryptChaChaOneShot2; /** * Decrypt the embedding associated with a file using the file's key. @@ -128,7 +136,7 @@ export const decryptFileEmbedding = async ( decryptionHeaderB64: string, keyB64: string, ) => - decryptFileAssociatedData( + decryptAssociatedData( await libsodium.fromB64(encryptedDataB64), decryptionHeaderB64, keyB64, @@ -136,7 +144,7 @@ export const decryptFileEmbedding = async ( /** * Decrypt the metadata associated with an Ente object (file, collection or - * entity) using that object's key. + * entity) using the object's key. * * This is the sibling of {@link decryptMetadata}. * @@ -146,7 +154,7 @@ export const decryptFileEmbedding = async ( * produced during encryption. * * @param keyB64 Base64 encoded string containing the encryption key. This is - * expected to be the key of the file with which {@link encryptedDataB64} is + * expected to be the key of the object with which {@link encryptedDataB64} is * associated. * * @returns The decrypted JSON value. Since TypeScript does not have a native @@ -160,7 +168,7 @@ export const decryptMetadata = async ( ) => JSON.parse( new TextDecoder().decode( - await decryptFileAssociatedData( + await decryptAssociatedData( await libsodium.fromB64(encryptedDataB64), decryptionHeaderB64, keyB64, From d599a6dcfa972ae715613a46413972bce815f7c0 Mon Sep 17 00:00:00 2001 From: Manav Rathi Date: Mon, 5 Aug 2024 15:03:39 +0530 Subject: [PATCH 28/35] Thumb --- .../photos/src/services/upload/uploadService.ts | 12 ++++++++---- web/packages/base/crypto/ente.ts | 14 ++++++++++++++ .../shared/crypto/internal/crypto.worker.ts | 16 +--------------- 3 files changed, 23 insertions(+), 19 deletions(-) diff --git a/web/apps/photos/src/services/upload/uploadService.ts b/web/apps/photos/src/services/upload/uploadService.ts index b6e28d9d12..d07179edf7 100644 --- a/web/apps/photos/src/services/upload/uploadService.ts +++ b/web/apps/photos/src/services/upload/uploadService.ts @@ -1124,10 +1124,14 @@ const encryptFile = async ( worker, ); - const encryptedThumbnail = await worker.encryptThumbnail( - file.thumbnail, - fileKey, - ); + const { + encryptedData: thumbEncryptedData, + decryptionHeaderB64: thumbDecryptionHeader, + } = await worker.encryptThumbnail(file.thumbnail, fileKey); + const encryptedThumbnail = { + encryptedData: thumbEncryptedData, + decryptionHeader: thumbDecryptionHeader, + }; const encryptedMetadata = await worker.encryptMetadata2( file.metadata, diff --git a/web/packages/base/crypto/ente.ts b/web/packages/base/crypto/ente.ts index 61bce30ed2..fb93370ec5 100644 --- a/web/packages/base/crypto/ente.ts +++ b/web/packages/base/crypto/ente.ts @@ -37,6 +37,20 @@ import * as libsodium from "@ente/shared/crypto/internal/libsodium"; */ export const encryptAssociatedData = libsodium.encryptChaChaOneShot; +/** + * Encrypt the thumbnail for a file. + * + * This is just an alias for {@link encryptAssociatedData}. + * + * @param data The thumbnail's data. + * + * @param keyB64 The key associated with the file whose thumbnail this is. + * + * @returns The encrypted thumbnail, and the associated decryption header + * (Base64 encoded). + */ +export const encryptThumbnail = encryptAssociatedData; + /** * Encrypted the embedding associated with a file using the file's key. * diff --git a/web/packages/shared/crypto/internal/crypto.worker.ts b/web/packages/shared/crypto/internal/crypto.worker.ts index 917fe0d0cb..520e1eb589 100644 --- a/web/packages/shared/crypto/internal/crypto.worker.ts +++ b/web/packages/shared/crypto/internal/crypto.worker.ts @@ -32,22 +32,8 @@ export class DedicatedCryptoWorker { ); } - /** - * Encrypt the thumbnail associated with a file. - * - * This defers to {@link encryptChaChaOneShot}. - * - * @param data The thumbnail's data. - * - * @param keyB64 The key associated with the file whose thumbnail this is. - * - * @returns The encrypted thumbnail, and the associated decryption header - * (Base64 encoded). - */ async encryptThumbnail(data: Uint8Array, keyB64: string) { - const { encryptedData, decryptionHeaderB64: decryptionHeader } = - await libsodium.encryptChaChaOneShot(data, keyB64); - return { encryptedData, decryptionHeader }; + return ente.encryptThumbnail(data, keyB64); } async encryptFile(fileData: Uint8Array) { From 40c360a1bda766b4d0f350acdd820bad8a77857b Mon Sep 17 00:00:00 2001 From: Manav Rathi Date: Mon, 5 Aug 2024 15:03:55 +0530 Subject: [PATCH 29/35] Rename --- web/apps/photos/src/services/collectionService.ts | 8 ++++---- web/apps/photos/src/services/fileService.ts | 4 ++-- web/apps/photos/src/services/upload/uploadService.ts | 4 ++-- web/packages/shared/crypto/internal/crypto.worker.ts | 2 +- 4 files changed, 9 insertions(+), 9 deletions(-) diff --git a/web/apps/photos/src/services/collectionService.ts b/web/apps/photos/src/services/collectionService.ts index fbb8aa2ad6..3575cb9654 100644 --- a/web/apps/photos/src/services/collectionService.ts +++ b/web/apps/photos/src/services/collectionService.ts @@ -427,7 +427,7 @@ const createCollection = async ( if (magicMetadataProps) { const magicMetadata = await updateMagicMetadata(magicMetadataProps); const encryptedMagicMetadataProps = - await cryptoWorker.encryptMetadata2( + await cryptoWorker.encryptMetadata( magicMetadataProps, collectionKey, ); @@ -800,7 +800,7 @@ export const updateCollectionMagicMetadata = async ( const cryptoWorker = await ComlinkCryptoWorker.getInstance(); const { encryptedDataB64, decryptionHeaderB64 } = - await cryptoWorker.encryptMetadata2( + await cryptoWorker.encryptMetadata( updatedMagicMetadata.data, collection.key, ); @@ -845,7 +845,7 @@ export const updateSharedCollectionMagicMetadata = async ( const cryptoWorker = await ComlinkCryptoWorker.getInstance(); const { encryptedDataB64, decryptionHeaderB64 } = - await cryptoWorker.encryptMetadata2( + await cryptoWorker.encryptMetadata( updatedMagicMetadata.data, collection.key, ); @@ -890,7 +890,7 @@ export const updatePublicCollectionMagicMetadata = async ( const cryptoWorker = await ComlinkCryptoWorker.getInstance(); const { encryptedDataB64, decryptionHeaderB64 } = - await cryptoWorker.encryptMetadata2( + await cryptoWorker.encryptMetadata( updatedPublicMagicMetadata.data, collection.key, ); diff --git a/web/apps/photos/src/services/fileService.ts b/web/apps/photos/src/services/fileService.ts index dc77efb8a8..e7df1a6d99 100644 --- a/web/apps/photos/src/services/fileService.ts +++ b/web/apps/photos/src/services/fileService.ts @@ -192,7 +192,7 @@ export const updateFileMagicMetadata = async ( updatedMagicMetadata, } of fileWithUpdatedMagicMetadataList) { const { encryptedDataB64, decryptionHeaderB64 } = - await cryptoWorker.encryptMetadata2( + await cryptoWorker.encryptMetadata( updatedMagicMetadata.data, file.key, ); @@ -239,7 +239,7 @@ export const updateFilePublicMagicMetadata = async ( updatedPublicMagicMetadata: updatePublicMagicMetadata, } of fileWithUpdatedPublicMagicMetadataList) { const { encryptedDataB64, decryptionHeaderB64 } = - await cryptoWorker.encryptMetadata2( + await cryptoWorker.encryptMetadata( updatePublicMagicMetadata.data, file.key, ); diff --git a/web/apps/photos/src/services/upload/uploadService.ts b/web/apps/photos/src/services/upload/uploadService.ts index d07179edf7..8511136352 100644 --- a/web/apps/photos/src/services/upload/uploadService.ts +++ b/web/apps/photos/src/services/upload/uploadService.ts @@ -1133,14 +1133,14 @@ const encryptFile = async ( decryptionHeader: thumbDecryptionHeader, }; - const encryptedMetadata = await worker.encryptMetadata2( + const encryptedMetadata = await worker.encryptMetadata( file.metadata, fileKey, ); let encryptedPubMagicMetadata: EncryptedMagicMetadata; if (file.pubMagicMetadata) { - const encryptedPubMagicMetadataData = await worker.encryptMetadata2( + const encryptedPubMagicMetadataData = await worker.encryptMetadata( file.pubMagicMetadata.data, fileKey, ); diff --git a/web/packages/shared/crypto/internal/crypto.worker.ts b/web/packages/shared/crypto/internal/crypto.worker.ts index 520e1eb589..58cc8c2208 100644 --- a/web/packages/shared/crypto/internal/crypto.worker.ts +++ b/web/packages/shared/crypto/internal/crypto.worker.ts @@ -16,7 +16,7 @@ export class DedicatedCryptoWorker { return libsodium.decryptChaCha(fileData, header, key); } - async encryptMetadata2(metadata: unknown, keyB64: string) { + async encryptMetadata(metadata: unknown, keyB64: string) { return ente.encryptMetadata(metadata, keyB64); } From a59b11c9f85e06840545540cc5b774e5dce83efe Mon Sep 17 00:00:00 2001 From: Manav Rathi Date: Mon, 5 Aug 2024 15:04:46 +0530 Subject: [PATCH 30/35] Rearrange --- .../shared/crypto/internal/crypto.worker.ts | 17 ++++++++--------- 1 file changed, 8 insertions(+), 9 deletions(-) diff --git a/web/packages/shared/crypto/internal/crypto.worker.ts b/web/packages/shared/crypto/internal/crypto.worker.ts index 58cc8c2208..0b63ec15f3 100644 --- a/web/packages/shared/crypto/internal/crypto.worker.ts +++ b/web/packages/shared/crypto/internal/crypto.worker.ts @@ -11,15 +11,6 @@ export class DedicatedCryptoWorker { ) { return libsodium.decryptChaChaOneShot(fileData, header, key); } - - async decryptFile(fileData: Uint8Array, header: Uint8Array, key: string) { - return libsodium.decryptChaCha(fileData, header, key); - } - - async encryptMetadata(metadata: unknown, keyB64: string) { - return ente.encryptMetadata(metadata, keyB64); - } - async decryptMetadata( encryptedDataB64: string, decryptionHeaderB64: string, @@ -32,10 +23,18 @@ export class DedicatedCryptoWorker { ); } + async decryptFile(fileData: Uint8Array, header: Uint8Array, key: string) { + return libsodium.decryptChaCha(fileData, header, key); + } + async encryptThumbnail(data: Uint8Array, keyB64: string) { return ente.encryptThumbnail(data, keyB64); } + async encryptMetadata(metadata: unknown, keyB64: string) { + return ente.encryptMetadata(metadata, keyB64); + } + async encryptFile(fileData: Uint8Array) { return libsodium.encryptChaCha(fileData); } From 2d1a8e5b85c5d5e8e509948f51e1bbf266fbef43 Mon Sep 17 00:00:00 2001 From: Manav Rathi Date: Mon, 5 Aug 2024 15:06:55 +0530 Subject: [PATCH 31/35] Doc --- web/packages/shared/crypto/internal/crypto.worker.ts | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/web/packages/shared/crypto/internal/crypto.worker.ts b/web/packages/shared/crypto/internal/crypto.worker.ts index 0b63ec15f3..7b6c9bcb9f 100644 --- a/web/packages/shared/crypto/internal/crypto.worker.ts +++ b/web/packages/shared/crypto/internal/crypto.worker.ts @@ -3,6 +3,18 @@ import * as libsodium from "@ente/shared/crypto/internal/libsodium"; import * as Comlink from "comlink"; import type { StateAddress } from "libsodium-wrappers"; +/** + * A web worker that exposes some of the functions defined in either the Ente + * specific layer (base/crypto/ente.ts) or the internal libsodium layer + * (internal/libsodium.ts). + * + * Running these in a web worker allows us to use potentially CPU-intensive + * crypto operations from the main thread without stalling the UI. + * + * See: [Note: Crypto code hierarchy]. + * + * Note: Keep these methods logic free. They should just act as trivial proxies. + */ export class DedicatedCryptoWorker { async decryptThumbnail( fileData: Uint8Array, From 2952c4a4c68d8e1339fb05b61d8b0c38e89496b6 Mon Sep 17 00:00:00 2001 From: Manav Rathi Date: Mon, 5 Aug 2024 15:19:20 +0530 Subject: [PATCH 32/35] Consistency --- web/apps/accounts/src/services/passkey.ts | 2 +- web/packages/base/crypto/ente.ts | 31 +++++++++-------- .../shared/crypto/internal/libsodium.ts | 33 ++++++++++--------- 3 files changed, 33 insertions(+), 33 deletions(-) diff --git a/web/apps/accounts/src/services/passkey.ts b/web/apps/accounts/src/services/passkey.ts index 2dfecaa3b8..f470b1d1cc 100644 --- a/web/apps/accounts/src/services/passkey.ts +++ b/web/apps/accounts/src/services/passkey.ts @@ -169,7 +169,7 @@ const beginPasskeyRegistration = async (token: string) => { // binary data. // // Binary data in the returned `PublicKeyCredentialCreationOptions` are - // serialized as a "URLEncodedBase64", which is a URL-encoded Base64 string + // serialized as a "URLEncodedBase64", which is a URL-encoded base64 string // without any padding. The library is following the WebAuthn recommendation // when it does this: // diff --git a/web/packages/base/crypto/ente.ts b/web/packages/base/crypto/ente.ts index fb93370ec5..fb566c565e 100644 --- a/web/packages/base/crypto/ente.ts +++ b/web/packages/base/crypto/ente.ts @@ -29,11 +29,11 @@ import * as libsodium from "@ente/shared/crypto/internal/libsodium"; * * @param data A {@link Uint8Array} containing the bytes to encrypt. * - * @param keyB64 Base64 string containing the encryption key. This is expected + * @param keyB64 base64 string containing the encryption key. This is expected * to the key of the object with which {@link data} is associated. For example, * if this is data associated with a file, then this will be the file's key. * - * @returns The encrypted data and the (Base64 encoded) decryption header. + * @returns The encrypted data and the (base64 encoded) decryption header. */ export const encryptAssociatedData = libsodium.encryptChaChaOneShot; @@ -47,7 +47,7 @@ export const encryptAssociatedData = libsodium.encryptChaChaOneShot; * @param keyB64 The key associated with the file whose thumbnail this is. * * @returns The encrypted thumbnail, and the associated decryption header - * (Base64 encoded). + * (base64 encoded). */ export const encryptThumbnail = encryptAssociatedData; @@ -56,7 +56,7 @@ export const encryptThumbnail = encryptAssociatedData; * * This as a variant of {@link encryptAssociatedData} tailored for * encrypting the embeddings (a.k.a. derived data) associated with a file. In - * particular, it returns the encrypted data in the result as a Base64 string + * particular, it returns the encrypted data in the result as a base64 string * instead of its bytes. * * Use {@link decryptFileEmbedding} to decrypt the result. @@ -86,7 +86,7 @@ export const encryptFileEmbedding = async ( * * 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. + * encrypted bytes, it returns their base64 string representation. * * Use {@link decryptMetadata} to decrypt the result. * @@ -94,7 +94,7 @@ export const encryptFileEmbedding = async ( * but since TypeScript currently doesn't have a native JSON type, it is typed * as an unknown. * - * @returns The encrypted data and decryption header, both as Base64 strings. + * @returns The encrypted data and decryption header, both as base64 strings. */ export const encryptMetadata = async (metadata: unknown, keyB64: string) => { const encodedMetadata = new TextEncoder().encode(JSON.stringify(metadata)); @@ -119,12 +119,11 @@ export const encryptMetadata = async (metadata: unknown, keyB64: string) => { * * @param encryptedData A {@link Uint8Array} containing the bytes to decrypt. * - * @param headerB64 A Base64 string containing the decryption header that was + * @param headerB64 A base64 string containing the decryption header that was * produced during encryption. * - * @param keyB64 A Base64 encoded string containing the encryption key. This is - * expected to be the key of the file with which {@link encryptedDataB64} is - * associated. + * @param keyB64 A base64 string containing the encryption key. This is expected + * to be the key of the object to which {@link encryptedDataB64} is associated. * * @returns The decrypted bytes. */ @@ -135,12 +134,12 @@ export const decryptAssociatedData = libsodium.decryptChaChaOneShot2; * * This is the sibling of {@link encryptFileEmbedding}. * - * @param encryptedDataB64 A Base64 string containing the encrypted embedding. + * @param encryptedDataB64 A base64 string containing the encrypted embedding. * - * @param headerB64 A Base64 string containing the decryption header produced + * @param headerB64 A base64 string containing the decryption header produced * during encryption. * - * @param keyB64 A Base64 string containing the encryption key. This is expected + * @param keyB64 A base64 string containing the encryption key. This is expected * to be the key of the file with which {@link encryptedDataB64} is associated. * * @returns The decrypted metadata JSON object. @@ -162,12 +161,12 @@ export const decryptFileEmbedding = async ( * * This is the sibling of {@link decryptMetadata}. * - * @param encryptedDataB64 Base64 encoded string containing the encrypted data. + * @param encryptedDataB64 base64 encoded string containing the encrypted data. * - * @param headerB64 Base64 encoded string containing the decryption header + * @param headerB64 base64 encoded string containing the decryption header * produced during encryption. * - * @param keyB64 Base64 encoded string containing the encryption key. This is + * @param keyB64 base64 encoded string containing the encryption key. This is * expected to be the key of the object with which {@link encryptedDataB64} is * associated. * diff --git a/web/packages/shared/crypto/internal/libsodium.ts b/web/packages/shared/crypto/internal/libsodium.ts index 97bb46e265..3238e43573 100644 --- a/web/packages/shared/crypto/internal/libsodium.ts +++ b/web/packages/shared/crypto/internal/libsodium.ts @@ -11,7 +11,7 @@ import { CustomError } from "@ente/shared/error"; import sodium, { type StateAddress } from "libsodium-wrappers"; /** - * Convert a {@link Uint8Array} to a Base64 encoded string. + * Convert bytes ({@link Uint8Array}) to a base64 string. * * See also {@link toB64URLSafe} and {@link toB64URLSafeNoPadding}. */ @@ -21,7 +21,7 @@ export const toB64 = async (input: Uint8Array) => { }; /** - * Convert a Base64 encoded string to a {@link Uint8Array}. + * Convert a base64 string to bytes ({@link Uint8Array}). * * This is the converse of {@link toBase64}. */ @@ -31,7 +31,7 @@ export const fromB64 = async (input: string) => { }; /** - * Convert a {@link Uint8Array} to a URL-safe Base64 encoded string. + * Convert bytes ({@link Uint8Array}) to a URL-safe base64 string. * * See also {@link toB64URLSafe} and {@link toB64URLSafeNoPadding}. */ @@ -41,7 +41,7 @@ export const toB64URLSafe = async (input: Uint8Array) => { }; /** - * Convert a {@link Uint8Array} to a unpadded URL-safe Base64 encoded string. + * Convert bytes ({@link Uint8Array}) to a unpadded URL-safe base64 string. * * This differs from {@link toB64URLSafe} in that it does not append any * trailing padding character(s) "=" to make the resultant string's length be an @@ -62,7 +62,7 @@ export const toB64URLSafeNoPadding = async (input: Uint8Array) => { }; /** - * Convert a unpadded URL-safe Base64 encoded string to a {@link Uint8Array}. + * Convert a unpadded URL-safe base64 string to bytes ({@link Uint8Array}). * * This is the converse of {@link toB64URLSafeNoPadding}, and does not expect * its input string's length to be a an integer multiple of 4. @@ -111,7 +111,7 @@ export async function fromHex(input: string) { } /** - * Encrypt the given {@link data} using the given (Base64 encoded) key. + * Encrypt the given {@link data} using the given (base64 encoded) key. * * Use {@link decryptChaChaOneShot} to decrypt the result. * @@ -137,18 +137,19 @@ export async function fromHex(input: string) { * See: https://doc.libsodium.org/secret-key_cryptography/secretbox * * The difference here is that this function is meant to used for data - * associated with a file. There is no technical reason to do it that way, just - * that all data associated with a file, including its actual contents, use the - * same underlying (streaming) libsodium APIs. While other independent free - * standing encryption needs are covered using the secretbox APIs. + * associated with a file (or some other Ente object, like a collection or an + * entity). There is no technical reason to do it that way, just this way all + * data associated with a file, including its actual contents, use the same + * underlying (streaming) libsodium APIs. In other cases, where we have free + * standing independent data, we continue using the secretbox APIs for one shot + * encryption and decryption. * * @param data A {@link Uint8Array} containing the bytes that we want to * encrypt. * - * @param keyB64 Base64 encoded string containing the encryption key. This is - * usually be the key associated with a file to which {@link data} relates to. + * @param keyB64 A base64 string containing the encryption key. * - * @returns The encrypted data (bytes) and decryption header pair (Base64 + * @returns The encrypted data (bytes) and decryption header pair (base64 * encoded string). Both these values are needed to decrypt the data. The header * does not need to be secret. */ @@ -254,10 +255,10 @@ export async function encryptFileChunk( * * @param encryptedData A {@link Uint8Array} containing the bytes to decrypt. * - * @param header A Base64 string containing the bytes of the decryption header + * @param header A base64 string containing the bytes of the decryption header * that was produced during encryption. * - * @param keyB64 The Base64 string containing the key that was used to encrypt + * @param keyB64 The base64 string containing the key that was used to encrypt * the data. * * @returns The decrypted bytes. @@ -528,7 +529,7 @@ export async function generateSaltToDeriveKey() { } /** - * Generate a new public/private keypair, and return their Base64 + * Generate a new public/private keypair, and return their base64 * representations. */ export const generateKeyPair = async () => { From 838840bfa898ee0bb7c14397a417f3af27075213 Mon Sep 17 00:00:00 2001 From: Manav Rathi Date: Mon, 5 Aug 2024 15:36:34 +0530 Subject: [PATCH 33/35] Decrypt thumb --- web/packages/base/crypto/ente.ts | 7 +++++++ web/packages/new/photos/services/download.ts | 2 +- web/packages/shared/crypto/internal/crypto.worker.ts | 9 +++++---- 3 files changed, 13 insertions(+), 5 deletions(-) diff --git a/web/packages/base/crypto/ente.ts b/web/packages/base/crypto/ente.ts index fb566c565e..13e9cf287e 100644 --- a/web/packages/base/crypto/ente.ts +++ b/web/packages/base/crypto/ente.ts @@ -129,6 +129,13 @@ export const encryptMetadata = async (metadata: unknown, keyB64: string) => { */ export const decryptAssociatedData = libsodium.decryptChaChaOneShot2; +/** + * Decrypt the thumbnail for a file. + * + * This is just an alias for {@link decryptAssociatedData}. + */ +export const decryptThumbnail = decryptAssociatedData; + /** * Decrypt the embedding associated with a file using the file's key. * diff --git a/web/packages/new/photos/services/download.ts b/web/packages/new/photos/services/download.ts index 694fc7cda7..10b7f200bc 100644 --- a/web/packages/new/photos/services/download.ts +++ b/web/packages/new/photos/services/download.ts @@ -127,7 +127,7 @@ class DownloadManagerImpl { const encrypted = await downloadClient.downloadThumbnail(file); const decrypted = await cryptoWorker.decryptThumbnail( encrypted, - await cryptoWorker.fromB64(file.thumbnail.decryptionHeader), + file.thumbnail.decryptionHeader, file.key, ); return decrypted; diff --git a/web/packages/shared/crypto/internal/crypto.worker.ts b/web/packages/shared/crypto/internal/crypto.worker.ts index 7b6c9bcb9f..d825ba5a57 100644 --- a/web/packages/shared/crypto/internal/crypto.worker.ts +++ b/web/packages/shared/crypto/internal/crypto.worker.ts @@ -17,12 +17,13 @@ import type { StateAddress } from "libsodium-wrappers"; */ export class DedicatedCryptoWorker { async decryptThumbnail( - fileData: Uint8Array, - header: Uint8Array, - key: string, + encryptedData: Uint8Array, + headerB64: string, + keyB64: string, ) { - return libsodium.decryptChaChaOneShot(fileData, header, key); + return ente.decryptThumbnail(encryptedData, headerB64, keyB64); } + async decryptMetadata( encryptedDataB64: string, decryptionHeaderB64: string, From 9b896c5c2f6aacf355bf3333ca2574659e3ab3e8 Mon Sep 17 00:00:00 2001 From: Manav Rathi Date: Mon, 5 Aug 2024 15:37:29 +0530 Subject: [PATCH 34/35] Dedup --- .../shared/crypto/internal/libsodium.ts | 18 ------------------ 1 file changed, 18 deletions(-) diff --git a/web/packages/shared/crypto/internal/libsodium.ts b/web/packages/shared/crypto/internal/libsodium.ts index 3238e43573..2f63e6d2f5 100644 --- a/web/packages/shared/crypto/internal/libsodium.ts +++ b/web/packages/shared/crypto/internal/libsodium.ts @@ -283,24 +283,6 @@ export const decryptChaChaOneShot2 = async ( return pullResult.message; }; -export const decryptChaChaOneShot = async ( - data: Uint8Array, - header: Uint8Array, - keyB64: string, -) => { - await sodium.ready; - const pullState = sodium.crypto_secretstream_xchacha20poly1305_init_pull( - header, - await fromB64(keyB64), - ); - const pullResult = sodium.crypto_secretstream_xchacha20poly1305_pull( - pullState, - data, - null, - ); - return pullResult.message; -}; - export const decryptChaCha = async ( data: Uint8Array, header: Uint8Array, From 9640d485a218e7829339467a7d11c28dbe14124c Mon Sep 17 00:00:00 2001 From: Manav Rathi Date: Mon, 5 Aug 2024 15:37:46 +0530 Subject: [PATCH 35/35] Rename --- web/packages/base/crypto/ente.ts | 2 +- web/packages/shared/crypto/internal/libsodium.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/web/packages/base/crypto/ente.ts b/web/packages/base/crypto/ente.ts index 13e9cf287e..017c38aa2c 100644 --- a/web/packages/base/crypto/ente.ts +++ b/web/packages/base/crypto/ente.ts @@ -127,7 +127,7 @@ export const encryptMetadata = async (metadata: unknown, keyB64: string) => { * * @returns The decrypted bytes. */ -export const decryptAssociatedData = libsodium.decryptChaChaOneShot2; +export const decryptAssociatedData = libsodium.decryptChaChaOneShot; /** * Decrypt the thumbnail for a file. diff --git a/web/packages/shared/crypto/internal/libsodium.ts b/web/packages/shared/crypto/internal/libsodium.ts index 2f63e6d2f5..9e2df77dfe 100644 --- a/web/packages/shared/crypto/internal/libsodium.ts +++ b/web/packages/shared/crypto/internal/libsodium.ts @@ -265,7 +265,7 @@ export async function encryptFileChunk( * * @returns The decrypted metadata bytes. */ -export const decryptChaChaOneShot2 = async ( +export const decryptChaChaOneShot = async ( encryptedData: Uint8Array, headerB64: string, keyB64: string,