From 3f6a706e9a188494beaea87b1cfabf1f7b823120 Mon Sep 17 00:00:00 2001 From: Manav Rathi Date: Thu, 5 Jun 2025 11:43:45 +0530 Subject: [PATCH 01/22] Conv --- web/apps/photos/src/services/collectionService.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/web/apps/photos/src/services/collectionService.ts b/web/apps/photos/src/services/collectionService.ts index cce59083de..77473d5924 100644 --- a/web/apps/photos/src/services/collectionService.ts +++ b/web/apps/photos/src/services/collectionService.ts @@ -72,7 +72,7 @@ const createCollection = async ( const token = getToken(); const collectionKey = await cryptoWorker.generateKey(); const { encryptedData: encryptedKey, nonce: keyDecryptionNonce } = - await cryptoWorker.encryptToB64(collectionKey, encryptionKey); + await cryptoWorker.encryptBox(collectionKey, encryptionKey); const { encryptedData: encryptedName, nonce: nameDecryptionNonce } = await cryptoWorker.encryptUTF8(collectionName, collectionKey); let encryptedMagicMetadata: EncryptedMagicMetadata; From 76cca72bec8b0d6371094388ad186d7a906da54e Mon Sep 17 00:00:00 2001 From: Manav Rathi Date: Thu, 5 Jun 2025 11:52:28 +0530 Subject: [PATCH 02/22] Conv --- .../accounts/pages/change-password.tsx | 10 ++-- web/packages/accounts/services/srp.ts | 48 +++++++++---------- 2 files changed, 26 insertions(+), 32 deletions(-) diff --git a/web/packages/accounts/pages/change-password.tsx b/web/packages/accounts/pages/change-password.tsx index f977ba8bdc..3b64fcad20 100644 --- a/web/packages/accounts/pages/change-password.tsx +++ b/web/packages/accounts/pages/change-password.tsx @@ -68,14 +68,12 @@ const Page: React.FC = () => { setFieldError("confirm", t("password_generation_failed")); return; } - const encryptedKeyAttributes = await cryptoWorker.encryptToB64( - key, - kek.key, - ); + const { encryptedData: encryptedKey, nonce: keyDecryptionNonce } = + await cryptoWorker.encryptBox(key, kek.key); const updatedKey: UpdatedKey = { kekSalt, - encryptedKey: encryptedKeyAttributes.encryptedData, - keyDecryptionNonce: encryptedKeyAttributes.nonce, + encryptedKey, + keyDecryptionNonce, opsLimit: kek.opsLimit, memLimit: kek.memLimit, }; diff --git a/web/packages/accounts/services/srp.ts b/web/packages/accounts/services/srp.ts index 1fe485a96a..dc67e316d8 100644 --- a/web/packages/accounts/services/srp.ts +++ b/web/packages/accounts/services/srp.ts @@ -176,24 +176,22 @@ export async function generateKeyAndSRPAttributes( const kekSalt = await cryptoWorker.generateSaltToDeriveKey(); const kek = await cryptoWorker.deriveSensitiveKey(passphrase, kekSalt); - const masterKeyEncryptedWithKek = await cryptoWorker.encryptToB64( - masterKey, - kek.key, - ); - const masterKeyEncryptedWithRecoveryKey = await cryptoWorker.encryptToB64( - masterKey, - recoveryKey, - ); - const recoveryKeyEncryptedWithMasterKey = await cryptoWorker.encryptToB64( - recoveryKey, - masterKey, - ); + const { encryptedData: encryptedKey, nonce: keyDecryptionNonce } = + await cryptoWorker.encryptBox(masterKey, kek.key); + const { + encryptedData: masterKeyEncryptedWithRecoveryKey, + nonce: masterKeyDecryptionNonce, + } = await cryptoWorker.encryptBox(masterKey, recoveryKey); + const { + encryptedData: recoveryKeyEncryptedWithMasterKey, + nonce: recoveryKeyDecryptionNonce, + } = await cryptoWorker.encryptBox(recoveryKey, masterKey); const keyPair = await cryptoWorker.generateKeyPair(); - const encryptedKeyPairAttributes = await cryptoWorker.encryptToB64( - keyPair.privateKey, - masterKey, - ); + const { + encryptedData: encryptedSecretKey, + nonce: secretKeyDecryptionNonce, + } = await cryptoWorker.encryptBox(keyPair.privateKey, masterKey); const loginSubKey = await generateLoginSubKey(kek.key); @@ -201,19 +199,17 @@ export async function generateKeyAndSRPAttributes( const keyAttributes: KeyAttributes = { kekSalt, - encryptedKey: masterKeyEncryptedWithKek.encryptedData, - keyDecryptionNonce: masterKeyEncryptedWithKek.nonce, + encryptedKey, + keyDecryptionNonce, publicKey: keyPair.publicKey, - encryptedSecretKey: encryptedKeyPairAttributes.encryptedData, - secretKeyDecryptionNonce: encryptedKeyPairAttributes.nonce, + encryptedSecretKey, + secretKeyDecryptionNonce, opsLimit: kek.opsLimit, memLimit: kek.memLimit, - masterKeyEncryptedWithRecoveryKey: - masterKeyEncryptedWithRecoveryKey.encryptedData, - masterKeyDecryptionNonce: masterKeyEncryptedWithRecoveryKey.nonce, - recoveryKeyEncryptedWithMasterKey: - recoveryKeyEncryptedWithMasterKey.encryptedData, - recoveryKeyDecryptionNonce: recoveryKeyEncryptedWithMasterKey.nonce, + masterKeyEncryptedWithRecoveryKey, + masterKeyDecryptionNonce, + recoveryKeyEncryptedWithMasterKey, + recoveryKeyDecryptionNonce, }; return { keyAttributes, masterKey, srpSetupAttributes }; From b656d4fe1f777904345d7c8d6cd4707e88584e49 Mon Sep 17 00:00:00 2001 From: Manav Rathi Date: Thu, 5 Jun 2025 13:06:44 +0530 Subject: [PATCH 03/22] Addendum https://github.com/jedisct1/libsodium.js/issues/112#issuecomment-337389964 --- web/packages/accounts/services/user.ts | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/web/packages/accounts/services/user.ts b/web/packages/accounts/services/user.ts index f47d8fe19e..effb1e8f0a 100644 --- a/web/packages/accounts/services/user.ts +++ b/web/packages/accounts/services/user.ts @@ -105,6 +105,14 @@ export interface KeyAttributes { * (https://doc.libsodium.org/public-key_cryptography/authenticated_encryption#key-pair-generation), * who possibly chose public + secret instead of public + private to avoid * confusion with shorthand notation (pk). + * + * However, the library author later changed their mind on this, so while + * libsodium itself (the C library) and the documentation uses "secretKey", + * the JavaScript implementation (libsodium.js) uses "privateKey". + * + * This structure uses the term "secretKey" since that is what the remote + * protocol already was based on. Within the web app codebase, we use + * "privateKey" since that is what the underlying libsodium.js uses. */ encryptedSecretKey: string; /** From 9e1b1b08504566868f663dae6b6a1ea67a4c40ed Mon Sep 17 00:00:00 2001 From: Manav Rathi Date: Thu, 5 Jun 2025 13:11:54 +0530 Subject: [PATCH 04/22] Convert --- web/packages/gallery/services/upload/upload-service.ts | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/web/packages/gallery/services/upload/upload-service.ts b/web/packages/gallery/services/upload/upload-service.ts index b31d981821..dd00f3cca7 100644 --- a/web/packages/gallery/services/upload/upload-service.ts +++ b/web/packages/gallery/services/upload/upload-service.ts @@ -1425,7 +1425,7 @@ const constructPublicMagicMetadata = async ( const encryptFile = async ( file: FileWithMetadata, - encryptionKey: string, + collectionKey: string, worker: CryptoWorker, ) => { const fileKey = await worker.generateBlobOrStreamKey(); @@ -1467,7 +1467,7 @@ const encryptFile = async ( }; } - const encryptedKey = await worker.encryptToB64(fileKey, encryptionKey); + const encryptedFileKey = await worker.encryptBox(fileKey, collectionKey); return { encryptedFilePieces: { @@ -1478,10 +1478,7 @@ const encryptFile = async ( pubMagicMetadata: encryptedPubMagicMetadata, localID: localID, }, - encryptedFileKey: { - encryptedData: encryptedKey.encryptedData, - nonce: encryptedKey.nonce, - }, + encryptedFileKey, }; }; From 938732094802a19c53f779a1a01ca67040e01d3c Mon Sep 17 00:00:00 2001 From: Manav Rathi Date: Thu, 5 Jun 2025 13:18:19 +0530 Subject: [PATCH 05/22] Conv --- web/packages/shared/crypto/helpers.ts | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/web/packages/shared/crypto/helpers.ts b/web/packages/shared/crypto/helpers.ts index d1d53f304c..a33e36ef6c 100644 --- a/web/packages/shared/crypto/helpers.ts +++ b/web/packages/shared/crypto/helpers.ts @@ -57,15 +57,13 @@ export async function generateAndSaveIntermediateKeyAttributes( passphrase, intermediateKekSalt, ); - const encryptedKeyAttributes = await cryptoWorker.encryptToB64( - key, - intermediateKek.key, - ); + const { encryptedData: encryptedKey, nonce: keyDecryptionNonce } = + await cryptoWorker.encryptBox(key, intermediateKek.key); const intermediateKeyAttributes = Object.assign(existingKeyAttributes, { kekSalt: intermediateKekSalt, - encryptedKey: encryptedKeyAttributes.encryptedData, - keyDecryptionNonce: encryptedKeyAttributes.nonce, + encryptedKey, + keyDecryptionNonce, opsLimit: intermediateKek.opsLimit, memLimit: intermediateKek.memLimit, }); From c85aac613e374269c855c640b177f1c178a90d45 Mon Sep 17 00:00:00 2001 From: Manav Rathi Date: Thu, 5 Jun 2025 13:19:30 +0530 Subject: [PATCH 06/22] Prune --- web/packages/base/crypto/libsodium.ts | 2 +- web/packages/base/crypto/worker.ts | 4 ---- 2 files changed, 1 insertion(+), 5 deletions(-) diff --git a/web/packages/base/crypto/libsodium.ts b/web/packages/base/crypto/libsodium.ts index dd8e0d69d9..8db4d29864 100644 --- a/web/packages/base/crypto/libsodium.ts +++ b/web/packages/base/crypto/libsodium.ts @@ -638,7 +638,7 @@ export interface B64EncryptionResult { } /** Deprecated, use {@link encryptBox} instead */ -export async function encryptToB64(data: string, keyB64: string) { +async function encryptToB64(data: string, keyB64: string) { await sodium.ready; const encrypted = await encryptBox(data, keyB64); return { diff --git a/web/packages/base/crypto/worker.ts b/web/packages/base/crypto/worker.ts index 2874c8cb6a..7595ce46a5 100644 --- a/web/packages/base/crypto/worker.ts +++ b/web/packages/base/crypto/worker.ts @@ -51,10 +51,6 @@ export class CryptoWorker { return libsodium.decryptToUTF8(data, nonce, key); } - async encryptToB64(data: string, key: string) { - return libsodium.encryptToB64(data, key); - } - async generateKeyAndEncryptToB64(data: string) { return libsodium.generateKeyAndEncryptToB64(data); } From 40e7d58c0b4ecd584c00621c264350babebd2c90 Mon Sep 17 00:00:00 2001 From: Manav Rathi Date: Thu, 5 Jun 2025 13:34:05 +0530 Subject: [PATCH 07/22] utf-8 --- web/packages/base/crypto/ente-impl.ts | 1 + web/packages/base/crypto/index.ts | 40 +++++++++++++++++++++++++++ web/packages/base/crypto/libsodium.ts | 12 ++++++++ web/packages/base/crypto/worker.ts | 1 + 4 files changed, 54 insertions(+) diff --git a/web/packages/base/crypto/ente-impl.ts b/web/packages/base/crypto/ente-impl.ts index 2b007391c7..e990c86d93 100644 --- a/web/packages/base/crypto/ente-impl.ts +++ b/web/packages/base/crypto/ente-impl.ts @@ -20,6 +20,7 @@ export const _encryptStreamBytes = libsodium.encryptStreamBytes; export const _initChunkEncryption = libsodium.initChunkEncryption; export const _encryptStreamChunk = libsodium.encryptStreamChunk; export const _decryptBoxBytes = libsodium.decryptBoxBytes; +export const _decryptBoxUTF8 = libsodium.decryptBoxUTF8; export const _decryptBox = libsodium.decryptBox; export const _decryptBlobBytes = libsodium.decryptBlobBytes; export const _decryptBlob = libsodium.decryptBlob; diff --git a/web/packages/base/crypto/index.ts b/web/packages/base/crypto/index.ts index 5368a270c3..e9c24cce94 100644 --- a/web/packages/base/crypto/index.ts +++ b/web/packages/base/crypto/index.ts @@ -27,6 +27,8 @@ * to functions exposed by `crypto/libsodium.ts`, but they automatically defer * to a worker thread if we're not already running on one. * + * --- + * * [Note: Using libsodium in worker thread] * * `crypto/ente-impl.ts` and `crypto/worker.ts` are logic-less internal files @@ -53,6 +55,34 @@ * Also, some code (e.g. the uploader) creates it own crypto worker instances, * and thus directly calls the functions in the web worker that it created * instead of going through this file. + * + * --- + * + * [Note: Crypto layer API data types] + * + * There are two primary types used when exchanging data with these functions: + * + * 1. Base64 strings. Unqualified strings are taken as base64 encoded + * representations of the underlying data. + * + * 2. Raw bytes. Uint8Arrays are byte arrays. + * + * Where possible and useful, functions also accept a union of these two - a + * {@link BytesOrB64} where the implementation will automatically convert + * to/from base64 to bytes if needed, thus saving on unnecessary conversions at + * the caller side. + * + * Apart from these two, there are other secondary and one off types. + * + * 1. "Regular" JavaScript strings. These are indicated by the *UTF8 suffix on + * the function that deals with them. These strings will be obtained by utf-8 + * encoding (or decoding) the underlying bytes. + * + * 2. Hex representations of the bytes. These are indicated by the *Hex suffix + * on the functions dealing with them. + * + * 2. JSON values. These are indicated by the *JSON suffix on the functions + * dealing with them. */ import { ComlinkWorker } from "ente-base/worker/comlink-worker"; import { type StateAddress } from "libsodium-wrappers-sumo"; @@ -270,6 +300,16 @@ export const decryptBoxBytes = (box: EncryptedBox, key: BytesOrB64) => ? ei._decryptBoxBytes(box, key) : sharedWorker().then((w) => w.decryptBoxBytes(box, key)); +/** + * Variant of {@link decryptBoxBytes} that returns the decrypted bytes as a + * "JavaScript string", specifically a UTF-8 string. That is, after decryption + * we obtain raw bytes, which we interpret as a UTF-8 string. + */ +export const decryptBoxUTF8 = (box: EncryptedBox, key: BytesOrB64) => + inWorker() + ? ei._decryptBoxUTF8(box, key) + : sharedWorker().then((w) => w.decryptBoxUTF8(box, key)); + /** * Decrypt a blob encrypted using either {@link encryptBlobBytes} or * {@link encryptBlob} and return it as a base64 encoded string. diff --git a/web/packages/base/crypto/libsodium.ts b/web/packages/base/crypto/libsodium.ts index 8db4d29864..116ce65ca2 100644 --- a/web/packages/base/crypto/libsodium.ts +++ b/web/packages/base/crypto/libsodium.ts @@ -501,6 +501,18 @@ export const decryptBox = ( key: BytesOrB64, ): Promise => decryptBoxBytes(box, key).then(toB64); +/** + * Variant of {@link decryptBoxBytes} that returns the data after decoding the + * decrypted bytes as a utf-8 string. + */ +export const decryptBoxUTF8 = async ( + box: EncryptedBox, + key: BytesOrB64, +): Promise => { + await sodium.ready; + return sodium.to_string(await decryptBoxBytes(box, key)); +}; + /** * Decrypt the result of {@link encryptBlobBytes} or {@link encryptBlob}. */ diff --git a/web/packages/base/crypto/worker.ts b/web/packages/base/crypto/worker.ts index 7595ce46a5..74a0da24cd 100644 --- a/web/packages/base/crypto/worker.ts +++ b/web/packages/base/crypto/worker.ts @@ -28,6 +28,7 @@ export class CryptoWorker { initChunkEncryption = ei._initChunkEncryption; encryptStreamChunk = ei._encryptStreamChunk; decryptBoxBytes = ei._decryptBoxBytes; + decryptBoxUTF8 = ei._decryptBoxUTF8; decryptBox = ei._decryptBox; decryptBlobBytes = ei._decryptBlobBytes; decryptBlob = ei._decryptBlob; From 6eab85b7e105a170d704ad8ca3c94cbc8a4d5976 Mon Sep 17 00:00:00 2001 From: Manav Rathi Date: Thu, 5 Jun 2025 13:59:15 +0530 Subject: [PATCH 08/22] enc --- web/packages/base/crypto/ente-impl.ts | 7 ++++--- web/packages/base/crypto/index.ts | 16 ++++++++++++++-- web/packages/base/crypto/libsodium.ts | 12 ++++++++++++ web/packages/base/crypto/worker.ts | 7 ++++--- 4 files changed, 34 insertions(+), 8 deletions(-) diff --git a/web/packages/base/crypto/ente-impl.ts b/web/packages/base/crypto/ente-impl.ts index e990c86d93..dca6ac112b 100644 --- a/web/packages/base/crypto/ente-impl.ts +++ b/web/packages/base/crypto/ente-impl.ts @@ -13,17 +13,18 @@ export const _fromHex = libsodium.fromHex; export const _generateKey = libsodium.generateKey; export const _generateBlobOrStreamKey = libsodium.generateBlobOrStreamKey; export const _encryptBox = libsodium.encryptBox; -export const _encryptBlobBytes = libsodium.encryptBlobBytes; +export const _encryptBoxUTF8 = libsodium.encryptBoxUTF8; export const _encryptBlob = libsodium.encryptBlob; +export const _encryptBlobBytes = libsodium.encryptBlobBytes; export const _encryptMetadataJSON = libsodium.encryptMetadataJSON; export const _encryptStreamBytes = libsodium.encryptStreamBytes; export const _initChunkEncryption = libsodium.initChunkEncryption; export const _encryptStreamChunk = libsodium.encryptStreamChunk; +export const _decryptBox = libsodium.decryptBox; export const _decryptBoxBytes = libsodium.decryptBoxBytes; export const _decryptBoxUTF8 = libsodium.decryptBoxUTF8; -export const _decryptBox = libsodium.decryptBox; -export const _decryptBlobBytes = libsodium.decryptBlobBytes; export const _decryptBlob = libsodium.decryptBlob; +export const _decryptBlobBytes = libsodium.decryptBlobBytes; export const _decryptMetadataJSON = libsodium.decryptMetadataJSON; export const _decryptStreamBytes = libsodium.decryptStreamBytes; export const _initChunkDecryption = libsodium.initChunkDecryption; diff --git a/web/packages/base/crypto/index.ts b/web/packages/base/crypto/index.ts index e9c24cce94..606dd1d4cb 100644 --- a/web/packages/base/crypto/index.ts +++ b/web/packages/base/crypto/index.ts @@ -63,9 +63,12 @@ * There are two primary types used when exchanging data with these functions: * * 1. Base64 strings. Unqualified strings are taken as base64 encoded - * representations of the underlying data. + * representations of the underlying data. Usually, the unqualified "base" + * function deals with Base64 strings, since they also are the data type in + * which we usually send the encryted data etc to remote. * - * 2. Raw bytes. Uint8Arrays are byte arrays. + * 2. Raw bytes. Uint8Arrays are byte arrays. The functions that deal with bytes + * are indicated by a *Bytes suffix in their name. * * Where possible and useful, functions also accept a union of these two - a * {@link BytesOrB64} where the implementation will automatically convert @@ -196,6 +199,15 @@ export const encryptBox = (data: BytesOrB64, key: BytesOrB64) => ? ei._encryptBox(data, key) : sharedWorker().then((w) => w.encryptBox(data, key)); +/** + * A variant of {@link encryptBox} that first UTF-8 encodes the input string to + * obtain bytes, which it then encrypts. + */ +export const encryptBoxUTF8 = (data: string, key: BytesOrB64) => + inWorker() + ? ei._encryptBoxUTF8(data, key) + : sharedWorker().then((w) => w.encryptBoxUTF8(data, key)); + /** * Encrypt the given data, returning a blob containing the encrypted data and a * decryption header as base64 strings. diff --git a/web/packages/base/crypto/libsodium.ts b/web/packages/base/crypto/libsodium.ts index 116ce65ca2..ab83b1e10d 100644 --- a/web/packages/base/crypto/libsodium.ts +++ b/web/packages/base/crypto/libsodium.ts @@ -283,6 +283,18 @@ export const encryptBox = async ( }; }; +/** + * A variant of {@link encryptBox} that first converts the input string into + * bytes using a UTF-8 encoding, and then encrypts those bytes. + */ +export const encryptBoxUTF8 = async ( + data: string, + key: BytesOrB64, +): Promise => { + await sodium.ready; + return encryptBox(sodium.from_string(data), key); +}; + /** * Encrypt the given data using libsodium's secretstream APIs without chunking. * diff --git a/web/packages/base/crypto/worker.ts b/web/packages/base/crypto/worker.ts index 74a0da24cd..b0bf092b40 100644 --- a/web/packages/base/crypto/worker.ts +++ b/web/packages/base/crypto/worker.ts @@ -21,17 +21,18 @@ export class CryptoWorker { generateKey = ei._generateKey; generateBlobOrStreamKey = ei._generateBlobOrStreamKey; encryptBox = ei._encryptBox; - encryptBlobBytes = ei._encryptBlobBytes; + encryptBoxUTF8 = ei._encryptBoxUTF8; encryptBlob = ei._encryptBlob; + encryptBlobBytes = ei._encryptBlobBytes; encryptMetadataJSON = ei._encryptMetadataJSON; encryptStreamBytes = ei._encryptStreamBytes; initChunkEncryption = ei._initChunkEncryption; encryptStreamChunk = ei._encryptStreamChunk; + decryptBox = ei._decryptBox; decryptBoxBytes = ei._decryptBoxBytes; decryptBoxUTF8 = ei._decryptBoxUTF8; - decryptBox = ei._decryptBox; - decryptBlobBytes = ei._decryptBlobBytes; decryptBlob = ei._decryptBlob; + decryptBlobBytes = ei._decryptBlobBytes; decryptMetadataJSON = ei._decryptMetadataJSON; decryptStreamBytes = ei._decryptStreamBytes; initChunkDecryption = ei._initChunkDecryption; From 5de4b3c1b05302c3b03f67c4729fa45d68cb8b01 Mon Sep 17 00:00:00 2001 From: Manav Rathi Date: Thu, 5 Jun 2025 14:04:59 +0530 Subject: [PATCH 09/22] Swap --- web/apps/photos/src/services/collectionService.ts | 5 +++-- web/packages/base/crypto/libsodium.ts | 6 ------ web/packages/base/crypto/worker.ts | 4 ---- 3 files changed, 3 insertions(+), 12 deletions(-) diff --git a/web/apps/photos/src/services/collectionService.ts b/web/apps/photos/src/services/collectionService.ts index 77473d5924..43646c8b45 100644 --- a/web/apps/photos/src/services/collectionService.ts +++ b/web/apps/photos/src/services/collectionService.ts @@ -74,7 +74,8 @@ const createCollection = async ( const { encryptedData: encryptedKey, nonce: keyDecryptionNonce } = await cryptoWorker.encryptBox(collectionKey, encryptionKey); const { encryptedData: encryptedName, nonce: nameDecryptionNonce } = - await cryptoWorker.encryptUTF8(collectionName, collectionKey); + await cryptoWorker.encryptBoxUTF8(collectionName, collectionKey); + let encryptedMagicMetadata: EncryptedMagicMetadata; if (magicMetadataProps) { const magicMetadata = await updateMagicMetadata(magicMetadataProps); @@ -476,7 +477,7 @@ export const renameCollection = async ( const token = getToken(); const cryptoWorker = await sharedCryptoWorker(); const { encryptedData: encryptedName, nonce: nameDecryptionNonce } = - await cryptoWorker.encryptUTF8(newCollectionName, collection.key); + await cryptoWorker.encryptBoxUTF8(newCollectionName, collection.key); const collectionRenameRequest = { collectionID: collection.id, encryptedName, diff --git a/web/packages/base/crypto/libsodium.ts b/web/packages/base/crypto/libsodium.ts index ab83b1e10d..7107a9a98c 100644 --- a/web/packages/base/crypto/libsodium.ts +++ b/web/packages/base/crypto/libsodium.ts @@ -678,12 +678,6 @@ export async function generateKeyAndEncryptToB64(data: string) { return await encryptToB64(data, await toB64(key)); } -export async function encryptUTF8(data: string, key: string) { - await sodium.ready; - const b64Data = await toB64(sodium.from_string(data)); - return await encryptToB64(b64Data, key); -} - /** Deprecated */ export async function decryptToUTF8( encryptedData: string, diff --git a/web/packages/base/crypto/worker.ts b/web/packages/base/crypto/worker.ts index b0bf092b40..39a12d19be 100644 --- a/web/packages/base/crypto/worker.ts +++ b/web/packages/base/crypto/worker.ts @@ -57,10 +57,6 @@ export class CryptoWorker { return libsodium.generateKeyAndEncryptToB64(data); } - async encryptUTF8(data: string, key: string) { - return libsodium.encryptUTF8(data, key); - } - async generateSaltToDeriveKey() { return libsodium.generateSaltToDeriveKey(); } From 94c5cf316b78c7b647ded32e9d29c1967732974d Mon Sep 17 00:00:00 2001 From: Manav Rathi Date: Thu, 5 Jun 2025 14:07:29 +0530 Subject: [PATCH 10/22] Swap --- .../photos/src/services/publicCollectionService.ts | 8 +++++--- web/packages/base/crypto/libsodium.ts | 11 ----------- web/packages/base/crypto/worker.ts | 4 ---- web/packages/new/photos/services/collections.ts | 8 +++++--- 4 files changed, 10 insertions(+), 21 deletions(-) diff --git a/web/apps/photos/src/services/publicCollectionService.ts b/web/apps/photos/src/services/publicCollectionService.ts index 59f904b5bd..09966f3d0d 100644 --- a/web/apps/photos/src/services/publicCollectionService.ts +++ b/web/apps/photos/src/services/publicCollectionService.ts @@ -325,9 +325,11 @@ export const getPublicCollection = async ( const collectionName = (fetchedCollection.name = fetchedCollection.name || - (await cryptoWorker.decryptToUTF8( - fetchedCollection.encryptedName, - fetchedCollection.nameDecryptionNonce, + (await cryptoWorker.decryptBoxUTF8( + { + encryptedData: fetchedCollection.encryptedName, + nonce: fetchedCollection.nameDecryptionNonce, + }, collectionKey, ))); diff --git a/web/packages/base/crypto/libsodium.ts b/web/packages/base/crypto/libsodium.ts index 7107a9a98c..b2c273bb77 100644 --- a/web/packages/base/crypto/libsodium.ts +++ b/web/packages/base/crypto/libsodium.ts @@ -678,17 +678,6 @@ export async function generateKeyAndEncryptToB64(data: string) { return await encryptToB64(data, await toB64(key)); } -/** Deprecated */ -export async function decryptToUTF8( - encryptedData: string, - nonce: string, - keyB64: string, -) { - await sodium.ready; - const decrypted = await decryptBoxBytes({ encryptedData, nonce }, keyB64); - return sodium.to_string(decrypted); -} - /** * An opaque object meant to be threaded through {@link chunkHashInit}, * {@link chunkHashUpdate} and {@link chunkHashFinal}. diff --git a/web/packages/base/crypto/worker.ts b/web/packages/base/crypto/worker.ts index 39a12d19be..555becb4e4 100644 --- a/web/packages/base/crypto/worker.ts +++ b/web/packages/base/crypto/worker.ts @@ -49,10 +49,6 @@ export class CryptoWorker { // TODO: -- AUDIT BELOW -- - async decryptToUTF8(data: string, nonce: string, key: string) { - return libsodium.decryptToUTF8(data, nonce, key); - } - async generateKeyAndEncryptToB64(data: string) { return libsodium.generateKeyAndEncryptToB64(data); } diff --git a/web/packages/new/photos/services/collections.ts b/web/packages/new/photos/services/collections.ts index fa67c710d9..ed58b32544 100644 --- a/web/packages/new/photos/services/collections.ts +++ b/web/packages/new/photos/services/collections.ts @@ -226,9 +226,11 @@ export const getCollectionWithSecrets = async ( } const collectionName = collection.name || - (await cryptoWorker.decryptToUTF8( - collection.encryptedName, - collection.nameDecryptionNonce, + (await cryptoWorker.decryptBoxUTF8( + { + encryptedData: collection.encryptedName, + nonce: collection.nameDecryptionNonce, + }, collectionKey, )); From 723362fc335d4aab62280770d707095c82506789 Mon Sep 17 00:00:00 2001 From: Manav Rathi Date: Thu, 5 Jun 2025 14:32:40 +0530 Subject: [PATCH 11/22] Rename --- .../components/Collections/CollectionShare.tsx | 2 +- web/packages/accounts/pages/change-password.tsx | 2 +- web/packages/accounts/services/srp.ts | 4 ++-- web/packages/base/crypto/ente-impl.ts | 1 + web/packages/base/crypto/index.ts | 11 +++++++++++ web/packages/base/crypto/libsodium.ts | 16 +++++++++++----- web/packages/base/crypto/worker.ts | 5 +---- web/packages/shared/crypto/helpers.ts | 2 +- 8 files changed, 29 insertions(+), 14 deletions(-) diff --git a/web/apps/photos/src/components/Collections/CollectionShare.tsx b/web/apps/photos/src/components/Collections/CollectionShare.tsx index f374766780..a477226cf9 100644 --- a/web/apps/photos/src/components/Collections/CollectionShare.tsx +++ b/web/apps/photos/src/components/Collections/CollectionShare.tsx @@ -1660,7 +1660,7 @@ const SetPublicLinkPassword: React.FC = ({ const enablePublicUrlPassword = async (password: string) => { const cryptoWorker = await sharedCryptoWorker(); - const kekSalt = await cryptoWorker.generateSaltToDeriveKey(); + const kekSalt = await cryptoWorker.generateDeriveKeySalt(); const kek = await cryptoWorker.deriveInteractiveKey(password, kekSalt); return updatePublicShareURLHelper({ diff --git a/web/packages/accounts/pages/change-password.tsx b/web/packages/accounts/pages/change-password.tsx index 3b64fcad20..e89a99782c 100644 --- a/web/packages/accounts/pages/change-password.tsx +++ b/web/packages/accounts/pages/change-password.tsx @@ -60,7 +60,7 @@ const Page: React.FC = () => { const cryptoWorker = await sharedCryptoWorker(); const key = await getActualKey(); const keyAttributes: KeyAttributes = getData("keyAttributes"); - const kekSalt = await cryptoWorker.generateSaltToDeriveKey(); + const kekSalt = await cryptoWorker.generateDeriveKeySalt(); let kek: { key: string; opsLimit: number; memLimit: number }; try { kek = await cryptoWorker.deriveSensitiveKey(passphrase, kekSalt); diff --git a/web/packages/accounts/services/srp.ts b/web/packages/accounts/services/srp.ts index dc67e316d8..5d1e8c4a30 100644 --- a/web/packages/accounts/services/srp.ts +++ b/web/packages/accounts/services/srp.ts @@ -59,7 +59,7 @@ export const generateSRPSetupAttributes = async ( ): Promise => { const cryptoWorker = await sharedCryptoWorker(); - const srpSalt = await cryptoWorker.generateSaltToDeriveKey(); + const srpSalt = await cryptoWorker.generateDeriveKeySalt(); // Museum schema requires this to be a UUID. const srpUserID = uuidv4(); @@ -173,7 +173,7 @@ export async function generateKeyAndSRPAttributes( const cryptoWorker = await sharedCryptoWorker(); const masterKey = await cryptoWorker.generateKey(); const recoveryKey = await cryptoWorker.generateKey(); - const kekSalt = await cryptoWorker.generateSaltToDeriveKey(); + const kekSalt = await cryptoWorker.generateDeriveKeySalt(); const kek = await cryptoWorker.deriveSensitiveKey(passphrase, kekSalt); const { encryptedData: encryptedKey, nonce: keyDecryptionNonce } = diff --git a/web/packages/base/crypto/ente-impl.ts b/web/packages/base/crypto/ente-impl.ts index dca6ac112b..0a909254b1 100644 --- a/web/packages/base/crypto/ente-impl.ts +++ b/web/packages/base/crypto/ente-impl.ts @@ -35,6 +35,7 @@ export const _chunkHashFinal = libsodium.chunkHashFinal; export const _generateKeyPair = libsodium.generateKeyPair; export const _boxSeal = libsodium.boxSeal; export const _boxSealOpen = libsodium.boxSealOpen; +export const _generateDeriveKeySalt = libsodium.generateDeriveKeySalt; export const _deriveKey = libsodium.deriveKey; export const _deriveSensitiveKey = libsodium.deriveSensitiveKey; export const _deriveInteractiveKey = libsodium.deriveInteractiveKey; diff --git a/web/packages/base/crypto/index.ts b/web/packages/base/crypto/index.ts index 606dd1d4cb..785071fcdb 100644 --- a/web/packages/base/crypto/index.ts +++ b/web/packages/base/crypto/index.ts @@ -414,6 +414,17 @@ export const boxSealOpen = async ( w.boxSealOpen(encryptedData, publicKey, secretKey), ); +/** + * Return a new randomly generated 128-bit salt (as a base64 string). + * + * The returned salt is suitable for use with {@link deriveKey}, and also as a + * general 128-bit salt. + */ +export const generateDeriveKeySalt = async () => + inWorker() + ? ei._generateDeriveKeySalt() + : sharedWorker().then((w) => w.generateDeriveKeySalt()); + /** * Derive a key by hashing the given {@link passphrase} using Argon 2id. */ diff --git a/web/packages/base/crypto/libsodium.ts b/web/packages/base/crypto/libsodium.ts index b2c273bb77..a9b4f2d829 100644 --- a/web/packages/base/crypto/libsodium.ts +++ b/web/packages/base/crypto/libsodium.ts @@ -802,6 +802,17 @@ export const boxSealOpen = async ( ); }; +/** + * Generate a new randomly generated 128-bit salt suitable for use with the key + * derivation functions ({@link deriveKey} and its variants). + * + * @returns The base64 representation of a randomly generated 128-bit salt. + */ +export const generateDeriveKeySalt = async () => { + await sodium.ready; + return await toB64(sodium.randombytes_buf(sodium.crypto_pwhash_SALTBYTES)); +}; + /** * Derive a key by hashing the given {@link passphrase} using Argon 2id. * @@ -909,11 +920,6 @@ export const deriveInteractiveKey = async ( return { key, opsLimit, memLimit }; }; -export async function generateSaltToDeriveKey() { - await sodium.ready; - return await toB64(sodium.randombytes_buf(sodium.crypto_pwhash_SALTBYTES)); -} - export async function generateSubKey( key: string, subKeyLength: number, diff --git a/web/packages/base/crypto/worker.ts b/web/packages/base/crypto/worker.ts index 555becb4e4..f1c46f7571 100644 --- a/web/packages/base/crypto/worker.ts +++ b/web/packages/base/crypto/worker.ts @@ -43,6 +43,7 @@ export class CryptoWorker { generateKeyPair = ei._generateKeyPair; boxSeal = ei._boxSeal; boxSealOpen = ei._boxSealOpen; + generateDeriveKeySalt = ei._generateDeriveKeySalt; deriveKey = ei._deriveKey; deriveSensitiveKey = ei._deriveSensitiveKey; deriveInteractiveKey = ei._deriveInteractiveKey; @@ -53,10 +54,6 @@ export class CryptoWorker { return libsodium.generateKeyAndEncryptToB64(data); } - async generateSaltToDeriveKey() { - return libsodium.generateSaltToDeriveKey(); - } - async generateSubKey( key: string, subKeyLength: number, diff --git a/web/packages/shared/crypto/helpers.ts b/web/packages/shared/crypto/helpers.ts index a33e36ef6c..cc3e3f5947 100644 --- a/web/packages/shared/crypto/helpers.ts +++ b/web/packages/shared/crypto/helpers.ts @@ -52,7 +52,7 @@ export async function generateAndSaveIntermediateKeyAttributes( key: string, ): Promise { const cryptoWorker = await sharedCryptoWorker(); - const intermediateKekSalt = await cryptoWorker.generateSaltToDeriveKey(); + const intermediateKekSalt = await cryptoWorker.generateDeriveKeySalt(); const intermediateKek = await cryptoWorker.deriveInteractiveKey( passphrase, intermediateKekSalt, From 40f3ad592fb7101708932eeb1150fff58d7efa56 Mon Sep 17 00:00:00 2001 From: Manav Rathi Date: Thu, 5 Jun 2025 14:38:38 +0530 Subject: [PATCH 12/22] type --- web/packages/base/crypto/libsodium.ts | 12 ++++++++---- web/packages/base/crypto/types.ts | 23 +++++++++++++++++++++++ 2 files changed, 31 insertions(+), 4 deletions(-) diff --git a/web/packages/base/crypto/libsodium.ts b/web/packages/base/crypto/libsodium.ts index a9b4f2d829..3a1714b35a 100644 --- a/web/packages/base/crypto/libsodium.ts +++ b/web/packages/base/crypto/libsodium.ts @@ -12,6 +12,7 @@ import { mergeUint8Arrays } from "ente-utils/array"; import sodium, { type StateAddress } from "libsodium-wrappers-sumo"; import type { BytesOrB64, + DerivedKey, EncryptedBlob, EncryptedBlobB64, EncryptedBlobBytes, @@ -865,7 +866,10 @@ export const deriveKey = async ( * during the derivation (this information will be needed the user's other * clients to derive the same result). */ -export const deriveSensitiveKey = async (passphrase: string, salt: string) => { +export const deriveSensitiveKey = async ( + passphrase: string, + salt: string, +): Promise => { await sodium.ready; const desiredStrength = @@ -896,7 +900,7 @@ export const deriveSensitiveKey = async (passphrase: string, salt: string) => { while (memLimit > minMemLimit) { try { const key = await deriveKey(passphrase, salt, opsLimit, memLimit); - return { key, opsLimit, memLimit }; + return { key, salt, opsLimit, memLimit }; } catch { opsLimit *= 2; memLimit /= 2; @@ -912,12 +916,12 @@ export const deriveSensitiveKey = async (passphrase: string, salt: string) => { export const deriveInteractiveKey = async ( passphrase: string, salt: string, -) => { +): Promise => { const opsLimit = sodium.crypto_pwhash_OPSLIMIT_INTERACTIVE; const memLimit = sodium.crypto_pwhash_MEMLIMIT_INTERACTIVE; const key = await deriveKey(passphrase, salt, opsLimit, memLimit); - return { key, opsLimit, memLimit }; + return { key, salt, opsLimit, memLimit }; }; export async function generateSubKey( diff --git a/web/packages/base/crypto/types.ts b/web/packages/base/crypto/types.ts index d79a32f85e..64b29248b5 100644 --- a/web/packages/base/crypto/types.ts +++ b/web/packages/base/crypto/types.ts @@ -129,3 +129,26 @@ export interface EncryptedFile { */ decryptionHeader: string; } + +/** + * A key derived from a user provided passphrase, and the various attributes + * that were used during the key derivation. + */ +export interface DerivedKey { + /** + * The newly derived key itself, as a base64 encoded string. + */ + key: string; + /** + * The randomly generated salt (as a base64 string) that was used when deriving the key. + */ + salt: string; + /** + * opsLimit used during key derivation. + */ + opsLimit: number; + /** + * memLimit used during key derivation. + * */ + memLimit: number; +} From 931dafd2643e37ce5e7a8b75810180636a906057 Mon Sep 17 00:00:00 2001 From: Manav Rathi Date: Thu, 5 Jun 2025 14:40:08 +0530 Subject: [PATCH 13/22] Reroute --- .../Collections/CollectionShare.tsx | 9 +++------ .../accounts/pages/change-password.tsx | 8 ++++---- web/packages/accounts/services/srp.ts | 9 ++++----- web/packages/base/crypto/index.ts | 19 ++++++++++++------- web/packages/base/crypto/libsodium.ts | 5 +++-- web/packages/shared/crypto/helpers.ts | 8 ++------ 6 files changed, 28 insertions(+), 30 deletions(-) diff --git a/web/apps/photos/src/components/Collections/CollectionShare.tsx b/web/apps/photos/src/components/Collections/CollectionShare.tsx index a477226cf9..14a965a1f9 100644 --- a/web/apps/photos/src/components/Collections/CollectionShare.tsx +++ b/web/apps/photos/src/components/Collections/CollectionShare.tsx @@ -41,7 +41,7 @@ import { type ModalVisibilityProps, } from "ente-base/components/utils/modal"; import { useBaseContext } from "ente-base/context"; -import { sharedCryptoWorker } from "ente-base/crypto"; +import { deriveInteractiveKey } from "ente-base/crypto"; import { isHTTP4xxError } from "ente-base/http"; import { formattedDateTime } from "ente-base/i18n-date"; import log from "ente-base/log"; @@ -1659,14 +1659,11 @@ const SetPublicLinkPassword: React.FC = ({ }; const enablePublicUrlPassword = async (password: string) => { - const cryptoWorker = await sharedCryptoWorker(); - const kekSalt = await cryptoWorker.generateDeriveKeySalt(); - const kek = await cryptoWorker.deriveInteractiveKey(password, kekSalt); - + const kek = await deriveInteractiveKey(password); return updatePublicShareURLHelper({ collectionID: collection.id, passHash: kek.key, - nonce: kekSalt, + nonce: kek.salt, opsLimit: kek.opsLimit, memLimit: kek.memLimit, }); diff --git a/web/packages/accounts/pages/change-password.tsx b/web/packages/accounts/pages/change-password.tsx index e89a99782c..a74aa31422 100644 --- a/web/packages/accounts/pages/change-password.tsx +++ b/web/packages/accounts/pages/change-password.tsx @@ -25,6 +25,7 @@ import type { } from "ente-accounts/services/user"; import { LinkButton } from "ente-base/components/LinkButton"; import { sharedCryptoWorker } from "ente-base/crypto"; +import type { DerivedKey } from "ente-base/crypto/types"; import { generateAndSaveIntermediateKeyAttributes, generateLoginSubKey, @@ -60,10 +61,9 @@ const Page: React.FC = () => { const cryptoWorker = await sharedCryptoWorker(); const key = await getActualKey(); const keyAttributes: KeyAttributes = getData("keyAttributes"); - const kekSalt = await cryptoWorker.generateDeriveKeySalt(); - let kek: { key: string; opsLimit: number; memLimit: number }; + let kek: DerivedKey; try { - kek = await cryptoWorker.deriveSensitiveKey(passphrase, kekSalt); + kek = await cryptoWorker.deriveSensitiveKey(passphrase); } catch { setFieldError("confirm", t("password_generation_failed")); return; @@ -71,9 +71,9 @@ const Page: React.FC = () => { const { encryptedData: encryptedKey, nonce: keyDecryptionNonce } = await cryptoWorker.encryptBox(key, kek.key); const updatedKey: UpdatedKey = { - kekSalt, encryptedKey, keyDecryptionNonce, + kekSalt: kek.salt, opsLimit: kek.opsLimit, memLimit: kek.memLimit, }; diff --git a/web/packages/accounts/services/srp.ts b/web/packages/accounts/services/srp.ts index 5d1e8c4a30..a41035fc0c 100644 --- a/web/packages/accounts/services/srp.ts +++ b/web/packages/accounts/services/srp.ts @@ -173,8 +173,7 @@ export async function generateKeyAndSRPAttributes( const cryptoWorker = await sharedCryptoWorker(); const masterKey = await cryptoWorker.generateKey(); const recoveryKey = await cryptoWorker.generateKey(); - const kekSalt = await cryptoWorker.generateDeriveKeySalt(); - const kek = await cryptoWorker.deriveSensitiveKey(passphrase, kekSalt); + const kek = await cryptoWorker.deriveSensitiveKey(passphrase); const { encryptedData: encryptedKey, nonce: keyDecryptionNonce } = await cryptoWorker.encryptBox(masterKey, kek.key); @@ -198,14 +197,14 @@ export async function generateKeyAndSRPAttributes( const srpSetupAttributes = await generateSRPSetupAttributes(loginSubKey); const keyAttributes: KeyAttributes = { - kekSalt, encryptedKey, keyDecryptionNonce, + kekSalt: kek.salt, + opsLimit: kek.opsLimit, + memLimit: kek.memLimit, publicKey: keyPair.publicKey, encryptedSecretKey, secretKeyDecryptionNonce, - opsLimit: kek.opsLimit, - memLimit: kek.memLimit, masterKeyEncryptedWithRecoveryKey, masterKeyDecryptionNonce, recoveryKeyEncryptedWithMasterKey, diff --git a/web/packages/base/crypto/index.ts b/web/packages/base/crypto/index.ts index 785071fcdb..6f30dfd33b 100644 --- a/web/packages/base/crypto/index.ts +++ b/web/packages/base/crypto/index.ts @@ -93,6 +93,7 @@ import { inWorker } from "../env"; import * as ei from "./ente-impl"; import type { BytesOrB64, + DerivedKey, EncryptedBlob, EncryptedBox, EncryptedFile, @@ -443,15 +444,19 @@ export const deriveKey = async ( /** * Derive a sensitive key from the given {@link passphrase}. */ -export const deriveSensitiveKey = async (passphrase: string, salt: string) => +export const deriveSensitiveKey = async ( + passphrase: string, +): Promise => inWorker() - ? ei._deriveSensitiveKey(passphrase, salt) - : sharedWorker().then((w) => w.deriveSensitiveKey(passphrase, salt)); + ? ei._deriveSensitiveKey(passphrase) + : sharedWorker().then((w) => w.deriveSensitiveKey(passphrase)); /** - * Derive an interactive key from the given {@link passphrase}. + * Derive an key suitable for interactive use from the given {@link passphrase}. */ -export const deriveInteractiveKey = async (passphrase: string, salt: string) => +export const deriveInteractiveKey = async ( + passphrase: string, +): Promise => inWorker() - ? ei._deriveInteractiveKey(passphrase, salt) - : sharedWorker().then((w) => w.deriveInteractiveKey(passphrase, salt)); + ? ei._deriveInteractiveKey(passphrase) + : sharedWorker().then((w) => w.deriveInteractiveKey(passphrase)); diff --git a/web/packages/base/crypto/libsodium.ts b/web/packages/base/crypto/libsodium.ts index 3a1714b35a..601bbeebd7 100644 --- a/web/packages/base/crypto/libsodium.ts +++ b/web/packages/base/crypto/libsodium.ts @@ -868,10 +868,11 @@ export const deriveKey = async ( */ export const deriveSensitiveKey = async ( passphrase: string, - salt: string, ): Promise => { await sodium.ready; + const salt = await generateDeriveKeySalt(); + const desiredStrength = sodium.crypto_pwhash_MEMLIMIT_SENSITIVE * sodium.crypto_pwhash_OPSLIMIT_SENSITIVE; @@ -915,8 +916,8 @@ export const deriveSensitiveKey = async ( */ export const deriveInteractiveKey = async ( passphrase: string, - salt: string, ): Promise => { + const salt = await generateDeriveKeySalt(); const opsLimit = sodium.crypto_pwhash_OPSLIMIT_INTERACTIVE; const memLimit = sodium.crypto_pwhash_MEMLIMIT_INTERACTIVE; diff --git a/web/packages/shared/crypto/helpers.ts b/web/packages/shared/crypto/helpers.ts index cc3e3f5947..02f4a66317 100644 --- a/web/packages/shared/crypto/helpers.ts +++ b/web/packages/shared/crypto/helpers.ts @@ -52,18 +52,14 @@ export async function generateAndSaveIntermediateKeyAttributes( key: string, ): Promise { const cryptoWorker = await sharedCryptoWorker(); - const intermediateKekSalt = await cryptoWorker.generateDeriveKeySalt(); - const intermediateKek = await cryptoWorker.deriveInteractiveKey( - passphrase, - intermediateKekSalt, - ); + const intermediateKek = await cryptoWorker.deriveInteractiveKey(passphrase); const { encryptedData: encryptedKey, nonce: keyDecryptionNonce } = await cryptoWorker.encryptBox(key, intermediateKek.key); const intermediateKeyAttributes = Object.assign(existingKeyAttributes, { - kekSalt: intermediateKekSalt, encryptedKey, keyDecryptionNonce, + kekSalt: intermediateKek.salt, opsLimit: intermediateKek.opsLimit, memLimit: intermediateKek.memLimit, }); From f1d9fc61c57c98e55409e232cdc18e31f55fc4e4 Mon Sep 17 00:00:00 2001 From: Manav Rathi Date: Thu, 5 Jun 2025 14:52:30 +0530 Subject: [PATCH 14/22] Annotate --- web/packages/base/crypto/index.ts | 134 +++++++++++++++++++----------- 1 file changed, 84 insertions(+), 50 deletions(-) diff --git a/web/packages/base/crypto/index.ts b/web/packages/base/crypto/index.ts index 6f30dfd33b..98f577eaa7 100644 --- a/web/packages/base/crypto/index.ts +++ b/web/packages/base/crypto/index.ts @@ -95,7 +95,10 @@ import type { BytesOrB64, DerivedKey, EncryptedBlob, + EncryptedBlobB64, + EncryptedBlobBytes, EncryptedBox, + EncryptedBoxB64, EncryptedFile, } from "./types"; import type { CryptoWorker } from "./worker"; @@ -127,13 +130,13 @@ export const createComlinkCryptoWorker = () => /** * Convert bytes ({@link Uint8Array}) to a base64 string. */ -export const toB64 = (bytes: Uint8Array) => +export const toB64 = (bytes: Uint8Array): Promise => inWorker() ? ei._toB64(bytes) : sharedWorker().then((w) => w.toB64(bytes)); /** * URL safe variant of {@link toB64}. */ -export const toB64URLSafe = (bytes: Uint8Array) => +export const toB64URLSafe = (bytes: Uint8Array): Promise => inWorker() ? ei._toB64URLSafe(bytes) : sharedWorker().then((w) => w.toB64URLSafe(bytes)); @@ -149,7 +152,7 @@ export const fromB64 = (b64String: string) => /** * Convert a base64 string to the hex representation of the underlying bytes. */ -export const toHex = (b64String: string) => +export const toHex = (b64String: string): Promise => inWorker() ? ei._toHex(b64String) : sharedWorker().then((w) => w.toHex(b64String)); @@ -168,7 +171,7 @@ export const fromHex = (hexString: string) => * The returned key is suitable for use with the *Box encryption functions, and * as a general encryption key (e.g. as the user's master key or recovery key). */ -export const generateKey = () => +export const generateKey = (): Promise => inWorker() ? ei._generateKey() : sharedWorker().then((w) => w.generateKey()); @@ -177,7 +180,7 @@ export const generateKey = () => * Return a new randomly generated 256-bit key (as a base64 string) suitable for * use with the *Blob or *Stream encryption functions. */ -export const generateBlobOrStreamKey = () => +export const generateBlobOrStreamKey = (): Promise => inWorker() ? ei._generateBlobOrStreamKey() : sharedWorker().then((w) => w.generateBlobOrStreamKey()); @@ -195,7 +198,10 @@ export const generateBlobOrStreamKey = () => * > * > See: [Note: 3 forms of encryption (Box | Blob | Stream)] */ -export const encryptBox = (data: BytesOrB64, key: BytesOrB64) => +export const encryptBox = ( + data: BytesOrB64, + key: BytesOrB64, +): Promise => inWorker() ? ei._encryptBox(data, key) : sharedWorker().then((w) => w.encryptBox(data, key)); @@ -204,7 +210,10 @@ export const encryptBox = (data: BytesOrB64, key: BytesOrB64) => * A variant of {@link encryptBox} that first UTF-8 encodes the input string to * obtain bytes, which it then encrypts. */ -export const encryptBoxUTF8 = (data: string, key: BytesOrB64) => +export const encryptBoxUTF8 = ( + data: string, + key: BytesOrB64, +): Promise => inWorker() ? ei._encryptBoxUTF8(data, key) : sharedWorker().then((w) => w.encryptBoxUTF8(data, key)); @@ -223,7 +232,10 @@ export const encryptBoxUTF8 = (data: string, key: BytesOrB64) => * > * > See: [Note: 3 forms of encryption (Box | Blob | Stream)] */ -export const encryptBlob = (data: BytesOrB64, key: BytesOrB64) => +export const encryptBlob = ( + data: BytesOrB64, + key: BytesOrB64, +): Promise => inWorker() ? ei._encryptBlob(data, key) : sharedWorker().then((w) => w.encryptBlob(data, key)); @@ -234,16 +246,50 @@ export const encryptBlob = (data: BytesOrB64, key: BytesOrB64) => * * Use {@link decryptBlob} or {@link decryptBlobBytes} to decrypt the result. */ -export const encryptBlobBytes = (data: BytesOrB64, key: BytesOrB64) => +export const encryptBlobBytes = ( + data: BytesOrB64, + key: BytesOrB64, +): Promise => inWorker() ? ei._encryptBlobBytes(data, key) : sharedWorker().then((w) => w.encryptBlobBytes(data, key)); +/** + * Encrypt the JSON metadata associated with an Ente object (file, collection or + * entity) using the object's key. + * + * This is a variant of {@link encryptBlob} tailored for encrypting any + * arbitrary metadata associated with an Ente object. For example, it is used + * for encrypting the various metadata fields associated with a file, using that + * file's key. + * + * Instead of raw bytes, it takes as input an arbitrary JSON object which it + * encodes into a string, and encrypts that. + * + * Use {@link decryptMetadataJSON} to decrypt the result. + * + * @param jsonValue The JSON value to encrypt. This can be an arbitrary JSON + * value, but since TypeScript currently doesn't have a native JSON type, it is + * typed as {@link unknown}. + * + * @param key The encryption key. + */ +export const encryptMetadataJSON = ( + jsonValue: unknown, + key: BytesOrB64, +): Promise => + inWorker() + ? ei._encryptMetadataJSON(jsonValue, key) + : sharedWorker().then((w) => w.encryptMetadataJSON(jsonValue, key)); + /** * Encrypt the given data using chunked streaming encryption, but process all * the chunks in one go. */ -export const encryptStreamBytes = async (data: Uint8Array, key: BytesOrB64) => +export const encryptStreamBytes = async ( + data: Uint8Array, + key: BytesOrB64, +): Promise => inWorker() ? ei._encryptStreamBytes(data, key) : sharedWorker().then((w) => w.encryptStreamBytes(data, key)); @@ -270,36 +316,14 @@ export const encryptStreamChunk = async ( w.encryptStreamChunk(data, state, isFinalChunk), ); -/** - * Encrypt the JSON metadata associated with an Ente object (file, collection or - * entity) using the object's key. - * - * This is a variant of {@link encryptBlob} tailored for encrypting any - * arbitrary metadata associated with an Ente object. For example, it is used - * for encrypting the various metadata fields associated with a file, using that - * file's key. - * - * Instead of raw bytes, it takes as input an arbitrary JSON object which it - * encodes into a string, and encrypts that. - * - * Use {@link decryptMetadataJSON} to decrypt the result. - * - * @param jsonValue The JSON value to encrypt. This can be an arbitrary JSON - * value, but since TypeScript currently doesn't have a native JSON type, it is - * typed as {@link unknown}. - * - * @param key The encryption key. - */ -export const encryptMetadataJSON = (jsonValue: unknown, key: BytesOrB64) => - inWorker() - ? ei._encryptMetadataJSON(jsonValue, key) - : sharedWorker().then((w) => w.encryptMetadataJSON(jsonValue, key)); - /** * Decrypt a box encrypted using {@link encryptBox} and returns the decrypted * bytes as a base64 string. */ -export const decryptBox = (box: EncryptedBox, key: BytesOrB64) => +export const decryptBox = ( + box: EncryptedBox, + key: BytesOrB64, +): Promise => inWorker() ? ei._decryptBox(box, key) : sharedWorker().then((w) => w.decryptBox(box, key)); @@ -318,7 +342,10 @@ export const decryptBoxBytes = (box: EncryptedBox, key: BytesOrB64) => * "JavaScript string", specifically a UTF-8 string. That is, after decryption * we obtain raw bytes, which we interpret as a UTF-8 string. */ -export const decryptBoxUTF8 = (box: EncryptedBox, key: BytesOrB64) => +export const decryptBoxUTF8 = ( + box: EncryptedBox, + key: BytesOrB64, +): Promise => inWorker() ? ei._decryptBoxUTF8(box, key) : sharedWorker().then((w) => w.decryptBoxUTF8(box, key)); @@ -327,7 +354,10 @@ export const decryptBoxUTF8 = (box: EncryptedBox, key: BytesOrB64) => * Decrypt a blob encrypted using either {@link encryptBlobBytes} or * {@link encryptBlob} and return it as a base64 encoded string. */ -export const decryptBlob = (blob: EncryptedBlob, key: BytesOrB64) => +export const decryptBlob = ( + blob: EncryptedBlob, + key: BytesOrB64, +): Promise => inWorker() ? ei._decryptBlob(blob, key) : sharedWorker().then((w) => w.decryptBlob(blob, key)); @@ -380,7 +410,10 @@ export const decryptStreamChunk = async ( * @returns The decrypted JSON value. Since TypeScript does not have a native * JSON type, we need to return it as an `unknown`. */ -export const decryptMetadataJSON = (blob: EncryptedBlob, key: BytesOrB64) => +export const decryptMetadataJSON = ( + blob: EncryptedBlob, + key: BytesOrB64, +): Promise => inWorker() ? ei._decryptMetadataJSON(blob, key) : sharedWorker().then((w) => w.decryptMetadataJSON(blob, key)); @@ -388,7 +421,10 @@ export const decryptMetadataJSON = (blob: EncryptedBlob, key: BytesOrB64) => /** * Generate a new public/private keypair. */ -export const generateKeyPair = async () => +export const generateKeyPair = (): Promise<{ + publicKey: string; + privateKey: string; +}> => inWorker() ? ei._generateKeyPair() : sharedWorker().then((w) => w.generateKeyPair()); @@ -396,7 +432,7 @@ export const generateKeyPair = async () => /** * Public key encryption. */ -export const boxSeal = async (data: string, publicKey: string) => +export const boxSeal = (data: string, publicKey: string): Promise => inWorker() ? ei._boxSeal(data, publicKey) : sharedWorker().then((w) => w.boxSeal(data, publicKey)); @@ -404,11 +440,11 @@ export const boxSeal = async (data: string, publicKey: string) => /** * Decrypt the result of {@link boxSeal}. */ -export const boxSealOpen = async ( +export const boxSealOpen = ( encryptedData: string, publicKey: string, secretKey: string, -) => +): Promise => inWorker() ? ei._boxSealOpen(encryptedData, publicKey, secretKey) : sharedWorker().then((w) => @@ -421,7 +457,7 @@ export const boxSealOpen = async ( * The returned salt is suitable for use with {@link deriveKey}, and also as a * general 128-bit salt. */ -export const generateDeriveKeySalt = async () => +export const generateDeriveKeySalt = (): Promise => inWorker() ? ei._generateDeriveKeySalt() : sharedWorker().then((w) => w.generateDeriveKeySalt()); @@ -429,12 +465,12 @@ export const generateDeriveKeySalt = async () => /** * Derive a key by hashing the given {@link passphrase} using Argon 2id. */ -export const deriveKey = async ( +export const deriveKey = ( passphrase: string, salt: string, opsLimit: number, memLimit: number, -) => +): Promise => inWorker() ? ei._deriveKey(passphrase, salt, opsLimit, memLimit) : sharedWorker().then((w) => @@ -444,9 +480,7 @@ export const deriveKey = async ( /** * Derive a sensitive key from the given {@link passphrase}. */ -export const deriveSensitiveKey = async ( - passphrase: string, -): Promise => +export const deriveSensitiveKey = (passphrase: string): Promise => inWorker() ? ei._deriveSensitiveKey(passphrase) : sharedWorker().then((w) => w.deriveSensitiveKey(passphrase)); @@ -454,7 +488,7 @@ export const deriveSensitiveKey = async ( /** * Derive an key suitable for interactive use from the given {@link passphrase}. */ -export const deriveInteractiveKey = async ( +export const deriveInteractiveKey = ( passphrase: string, ): Promise => inWorker() From d858fdef751e42183444cd616082e60e24939adf Mon Sep 17 00:00:00 2001 From: Manav Rathi Date: Thu, 5 Jun 2025 15:24:48 +0530 Subject: [PATCH 15/22] More annotation --- web/packages/base/crypto/index.ts | 30 ++++++++++++++++----------- web/packages/base/crypto/libsodium.ts | 10 ++++----- 2 files changed, 23 insertions(+), 17 deletions(-) diff --git a/web/packages/base/crypto/index.ts b/web/packages/base/crypto/index.ts index 98f577eaa7..a43c7673d7 100644 --- a/web/packages/base/crypto/index.ts +++ b/web/packages/base/crypto/index.ts @@ -111,7 +111,7 @@ let _comlinkWorker: ComlinkWorker | undefined; /** * Lazily created, cached, instance of a CryptoWorker web worker. */ -export const sharedCryptoWorker = async () => +export const sharedCryptoWorker = () => (_comlinkWorker ??= createComlinkCryptoWorker()).remote; /** A shorter alias of {@link sharedCryptoWorker} for use within this file. */ @@ -144,7 +144,7 @@ export const toB64URLSafe = (bytes: Uint8Array): Promise => /** * Convert a base64 string to bytes ({@link Uint8Array}). */ -export const fromB64 = (b64String: string) => +export const fromB64 = (b64String: string): Promise => inWorker() ? ei._fromB64(b64String) : sharedWorker().then((w) => w.fromB64(b64String)); @@ -160,7 +160,7 @@ export const toHex = (b64String: string): Promise => /** * Convert a hex string to the base64 representation of the underlying bytes. */ -export const fromHex = (hexString: string) => +export const fromHex = (hexString: string): Promise => inWorker() ? ei._fromHex(hexString) : sharedWorker().then((w) => w.fromHex(hexString)); @@ -286,7 +286,7 @@ export const encryptMetadataJSON = ( * Encrypt the given data using chunked streaming encryption, but process all * the chunks in one go. */ -export const encryptStreamBytes = async ( +export const encryptStreamBytes = ( data: Uint8Array, key: BytesOrB64, ): Promise => @@ -305,11 +305,11 @@ export const initChunkEncryption = async (key: BytesOrB64) => /** * Encrypt a chunk as part of a chunked streaming encryption. */ -export const encryptStreamChunk = async ( +export const encryptStreamChunk = ( data: Uint8Array, state: StateAddress, isFinalChunk: boolean, -) => +): Promise => inWorker() ? ei._encryptStreamChunk(data, state, isFinalChunk) : sharedWorker().then((w) => @@ -332,7 +332,10 @@ export const decryptBox = ( * Variant of {@link decryptBox} that returns the decrypted bytes as it is * (without encoding them to base64). */ -export const decryptBoxBytes = (box: EncryptedBox, key: BytesOrB64) => +export const decryptBoxBytes = ( + box: EncryptedBox, + key: BytesOrB64, +): Promise => inWorker() ? ei._decryptBoxBytes(box, key) : sharedWorker().then((w) => w.decryptBoxBytes(box, key)); @@ -366,7 +369,10 @@ export const decryptBlob = ( * A variant of {@link decryptBlobBytes} that returns the result bytes directly * (instead of encoding them as a base64 string). */ -export const decryptBlobBytes = (blob: EncryptedBlob, key: BytesOrB64) => +export const decryptBlobBytes = ( + blob: EncryptedBlob, + key: BytesOrB64, +): Promise => inWorker() ? ei._decryptBlobBytes(blob, key) : sharedWorker().then((w) => w.decryptBlobBytes(blob, key)); @@ -374,10 +380,10 @@ export const decryptBlobBytes = (blob: EncryptedBlob, key: BytesOrB64) => /** * Decrypt the result of {@link encryptStreamBytes}. */ -export const decryptStreamBytes = async ( +export const decryptStreamBytes = ( file: EncryptedFile, key: BytesOrB64, -) => +): Promise => inWorker() ? ei._decryptStreamBytes(file, key) : sharedWorker().then((w) => w.decryptStreamBytes(file, key)); @@ -396,10 +402,10 @@ export const initChunkDecryption = async (header: string, key: BytesOrB64) => * * This function is used in tandem with {@link initChunkDecryption}. */ -export const decryptStreamChunk = async ( +export const decryptStreamChunk = ( data: Uint8Array, state: StateAddress, -) => +): Promise => inWorker() ? ei._decryptStreamChunk(data, state) : sharedWorker().then((w) => w.decryptStreamChunk(data, state)); diff --git a/web/packages/base/crypto/libsodium.ts b/web/packages/base/crypto/libsodium.ts index 601bbeebd7..f2305398f3 100644 --- a/web/packages/base/crypto/libsodium.ts +++ b/web/packages/base/crypto/libsodium.ts @@ -36,7 +36,7 @@ export const toB64 = async (input: Uint8Array) => { * * This is the converse of {@link toBase64}. */ -export const fromB64 = async (input: string) => { +export const fromB64 = async (input: string): Promise => { await sodium.ready; return sodium.from_base64(input, sodium.base64_variants.ORIGINAL); }; @@ -117,7 +117,7 @@ export const toHex = async (input: string) => { * * This is the inverse of {@link toHex}. */ -export const fromHex = async (input: string) => { +export const fromHex = async (input: string): Promise => { await sodium.ready; return await toB64(sodium.from_hex(input)); }; @@ -478,7 +478,7 @@ export const encryptStreamChunk = async ( data: Uint8Array, pushState: sodium.StateAddress, isFinalChunk: boolean, -) => { +): Promise => { await sodium.ready; const tag = isFinalChunk ? sodium.crypto_secretstream_xchacha20poly1305_TAG_FINAL @@ -577,7 +577,7 @@ export const decryptMetadataJSON = async ( export const decryptStreamBytes = async ( { encryptedData, decryptionHeader }: EncryptedFile, key: BytesOrB64, -) => { +): Promise => { await sodium.ready; const pullState = sodium.crypto_secretstream_xchacha20poly1305_init_pull( await fromB64(decryptionHeader), @@ -647,7 +647,7 @@ export const initChunkDecryption = async ( export const decryptStreamChunk = async ( data: Uint8Array, pullState: StateAddress, -) => { +): Promise => { await sodium.ready; const pullResult = sodium.crypto_secretstream_xchacha20poly1305_pull( pullState, From 4be5ac878078213b106c7c7d3a711c70352baeee Mon Sep 17 00:00:00 2001 From: Manav Rathi Date: Thu, 5 Jun 2025 15:49:37 +0530 Subject: [PATCH 16/22] types --- web/packages/base/crypto/index.ts | 17 +++++++---- web/packages/base/crypto/libsodium.ts | 27 +++++++++-------- web/packages/base/crypto/types.ts | 42 +++++++++++++++++++++++++++ 3 files changed, 67 insertions(+), 19 deletions(-) diff --git a/web/packages/base/crypto/index.ts b/web/packages/base/crypto/index.ts index a43c7673d7..0862454d19 100644 --- a/web/packages/base/crypto/index.ts +++ b/web/packages/base/crypto/index.ts @@ -88,7 +88,6 @@ * dealing with them. */ import { ComlinkWorker } from "ente-base/worker/comlink-worker"; -import { type StateAddress } from "libsodium-wrappers-sumo"; import { inWorker } from "../env"; import * as ei from "./ente-impl"; import type { @@ -100,6 +99,9 @@ import type { EncryptedBox, EncryptedBoxB64, EncryptedFile, + InitChunkDecryptionResult, + InitChunkEncryptionResult, + SodiumStateAddress, } from "./types"; import type { CryptoWorker } from "./worker"; @@ -297,7 +299,9 @@ export const encryptStreamBytes = ( /** * Prepare for chunked streaming encryption using {@link encryptStreamChunk}. */ -export const initChunkEncryption = async (key: BytesOrB64) => +export const initChunkEncryption = ( + key: BytesOrB64, +): Promise => inWorker() ? ei._initChunkEncryption(key) : sharedWorker().then((w) => w.initChunkEncryption(key)); @@ -307,7 +311,7 @@ export const initChunkEncryption = async (key: BytesOrB64) => */ export const encryptStreamChunk = ( data: Uint8Array, - state: StateAddress, + state: SodiumStateAddress, isFinalChunk: boolean, ): Promise => inWorker() @@ -392,7 +396,10 @@ export const decryptStreamBytes = ( * Prepare to decrypt the encrypted result produced using {@link initChunkEncryption} and * {@link encryptStreamChunk}. */ -export const initChunkDecryption = async (header: string, key: BytesOrB64) => +export const initChunkDecryption = ( + header: string, + key: BytesOrB64, +): Promise => inWorker() ? ei._initChunkDecryption(header, key) : sharedWorker().then((w) => w.initChunkDecryption(header, key)); @@ -404,7 +411,7 @@ export const initChunkDecryption = async (header: string, key: BytesOrB64) => */ export const decryptStreamChunk = ( data: Uint8Array, - state: StateAddress, + state: SodiumStateAddress, ): Promise => inWorker() ? ei._decryptStreamChunk(data, state) diff --git a/web/packages/base/crypto/libsodium.ts b/web/packages/base/crypto/libsodium.ts index f2305398f3..77b1e517c2 100644 --- a/web/packages/base/crypto/libsodium.ts +++ b/web/packages/base/crypto/libsodium.ts @@ -9,7 +9,7 @@ * To see where this code fits, see [Note: Crypto code hierarchy]. */ import { mergeUint8Arrays } from "ente-utils/array"; -import sodium, { type StateAddress } from "libsodium-wrappers-sumo"; +import sodium from "libsodium-wrappers-sumo"; import type { BytesOrB64, DerivedKey, @@ -19,6 +19,9 @@ import type { EncryptedBox, EncryptedBoxB64, EncryptedFile, + InitChunkDecryptionResult, + InitChunkEncryptionResult, + SodiumStateAddress, } from "./types"; /** @@ -448,7 +451,9 @@ export const encryptStreamBytes = async ( * to subsequent calls to {@link encryptStreamChunk} along with the chunks's * contents. */ -export const initChunkEncryption = async (key: BytesOrB64) => { +export const initChunkEncryption = async ( + key: BytesOrB64, +): Promise => { await sodium.ready; const keyBytes = await bytes(key); const { state, header } = @@ -476,7 +481,7 @@ export const initChunkEncryption = async (key: BytesOrB64) => { */ export const encryptStreamChunk = async ( data: Uint8Array, - pushState: sodium.StateAddress, + pushState: SodiumStateAddress, isFinalChunk: boolean, ): Promise => { await sodium.ready; @@ -623,7 +628,7 @@ export const decryptStreamBytes = async ( export const initChunkDecryption = async ( decryptionHeader: string, key: BytesOrB64, -) => { +): Promise => { await sodium.ready; const pullState = sodium.crypto_secretstream_xchacha20poly1305_init_pull( await fromB64(decryptionHeader), @@ -646,7 +651,7 @@ export const initChunkDecryption = async ( */ export const decryptStreamChunk = async ( data: Uint8Array, - pullState: StateAddress, + pullState: SodiumStateAddress, ): Promise => { await sodium.ready; const pullResult = sodium.crypto_secretstream_xchacha20poly1305_pull( @@ -679,12 +684,6 @@ export async function generateKeyAndEncryptToB64(data: string) { return await encryptToB64(data, await toB64(key)); } -/** - * An opaque object meant to be threaded through {@link chunkHashInit}, - * {@link chunkHashUpdate} and {@link chunkHashFinal}. - */ -export type ChunkHashState = sodium.StateAddress; - /** * Initialize and return new state that can be used to hash the chunks of data * in a streaming manner. @@ -701,7 +700,7 @@ export type ChunkHashState = sodium.StateAddress; * (along with the data to hash) to {@link chunkHashUpdate}, and the final hash * obtained using {@link chunkHashFinal}. */ -export const chunkHashInit = async (): Promise => { +export const chunkHashInit = async (): Promise => { await sodium.ready; return sodium.crypto_generichash_init( null, @@ -719,7 +718,7 @@ export const chunkHashInit = async (): Promise => { * @param chunk The data (bytes) to hash. */ export const chunkHashUpdate = async ( - hashState: ChunkHashState, + hashState: SodiumStateAddress, chunk: Uint8Array, ) => { await sodium.ready; @@ -736,7 +735,7 @@ export const chunkHashUpdate = async ( * * @returns The hash of all the chunks (as a base64 string). */ -export const chunkHashFinal = async (hashState: ChunkHashState) => { +export const chunkHashFinal = async (hashState: SodiumStateAddress) => { await sodium.ready; const hash = sodium.crypto_generichash_final( hashState, diff --git a/web/packages/base/crypto/types.ts b/web/packages/base/crypto/types.ts index 64b29248b5..f884c1fc71 100644 --- a/web/packages/base/crypto/types.ts +++ b/web/packages/base/crypto/types.ts @@ -1,3 +1,11 @@ +import { type StateAddress } from "libsodium-wrappers-sumo"; + +/** + * An opaque object meant to be threaded through various functions that deal + * with resumable chunk based processing. + */ +export type SodiumStateAddress = StateAddress; + /** * Data provided either as bytes ({@link Uint8Array}) or their base64 string * representation. @@ -130,6 +138,40 @@ export interface EncryptedFile { decryptionHeader: string; } +/** + * An object returned by the init function of chunked encryption routines. + */ +export interface InitChunkEncryptionResult { + /** + * A base64 string containing the decryption header. + * + * While the exact contents of the header are libsodium's internal details, + * it effectively contains a random nonce generated by libsodium. It does + * not need to be secret, but it is required to decrypt the data. + */ + decryptionHeader: string; + /** + * An opaque value that refers to the internal state used by the resumable + * calls in the encryption sequence. + */ + pushState: SodiumStateAddress; +} + +/** + * An object returned by the init function of chunked decryption routines. + */ +export interface InitChunkDecryptionResult { + /** + * An opaque value that refers to the internal state used by the resumable + * calls in the decryption sequence. + */ + pullState: SodiumStateAddress; + /** + * The expected size of each chunk. + */ + decryptionChunkSize: number; +} + /** * A key derived from a user provided passphrase, and the various attributes * that were used during the key derivation. From 3dffebf7333334971f4672afa3937c4e7ff7265a Mon Sep 17 00:00:00 2001 From: Manav Rathi Date: Thu, 5 Jun 2025 15:55:31 +0530 Subject: [PATCH 17/22] Same place both --- web/packages/base/session.ts | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/web/packages/base/session.ts b/web/packages/base/session.ts index d895068d52..9b2cc1e3c5 100644 --- a/web/packages/base/session.ts +++ b/web/packages/base/session.ts @@ -1,6 +1,5 @@ import { z } from "zod/v4"; -import { decryptBoxBytes } from "./crypto"; -import { toB64 } from "./crypto/libsodium"; +import { decryptBoxBytes, toB64 } from "./crypto"; /** * Remove all data stored in session storage (data tied to the browser tab). From a5fe20b0e988b9eedb40dc543e6f65f36fd32d90 Mon Sep 17 00:00:00 2001 From: Manav Rathi Date: Thu, 5 Jun 2025 16:11:50 +0530 Subject: [PATCH 18/22] Rename --- web/packages/base/crypto/ente-impl.ts | 1 + web/packages/base/crypto/index.ts | 15 +++++++++++++++ web/packages/base/crypto/libsodium.ts | 27 ++++++++++++++++++++++++--- web/packages/base/crypto/worker.ts | 10 +--------- 4 files changed, 41 insertions(+), 12 deletions(-) diff --git a/web/packages/base/crypto/ente-impl.ts b/web/packages/base/crypto/ente-impl.ts index 0a909254b1..4498208df8 100644 --- a/web/packages/base/crypto/ente-impl.ts +++ b/web/packages/base/crypto/ente-impl.ts @@ -39,3 +39,4 @@ export const _generateDeriveKeySalt = libsodium.generateDeriveKeySalt; export const _deriveKey = libsodium.deriveKey; export const _deriveSensitiveKey = libsodium.deriveSensitiveKey; export const _deriveInteractiveKey = libsodium.deriveInteractiveKey; +export const _deriveSubKey = libsodium.deriveSubKey; diff --git a/web/packages/base/crypto/index.ts b/web/packages/base/crypto/index.ts index 0862454d19..2fb20e5a01 100644 --- a/web/packages/base/crypto/index.ts +++ b/web/packages/base/crypto/index.ts @@ -507,3 +507,18 @@ export const deriveInteractiveKey = ( inWorker() ? ei._deriveInteractiveKey(passphrase) : sharedWorker().then((w) => w.deriveInteractiveKey(passphrase)); + +/** + * Derive a subkey of the given {@link key} using the specified parameters. + */ +export const deriveSubKey = async ( + key: string, + subKeyLength: number, + subKeyID: number, + context: string, +) => + inWorker() + ? ei._deriveSubKey(key, subKeyLength, subKeyID, context) + : sharedWorker().then((w) => + w.deriveSubKey(key, subKeyLength, subKeyID, context), + ); diff --git a/web/packages/base/crypto/libsodium.ts b/web/packages/base/crypto/libsodium.ts index 77b1e517c2..45f511c9f8 100644 --- a/web/packages/base/crypto/libsodium.ts +++ b/web/packages/base/crypto/libsodium.ts @@ -924,12 +924,33 @@ export const deriveInteractiveKey = async ( return { key, salt, opsLimit, memLimit }; }; -export async function generateSubKey( +/** + * Derive a {@link subKeyID}-th subkey of length {@link subKeyLength} bytes + * using the given {@link key} and the {@link context}. + * + * Multiple secret subkeys can be (deterministically) derived from a single + * high-entropy key. Knowledge of the derived key does not impact the security + * of the key from which it was derived, or of its potential sibling subkeys. + * + * See: https://doc.libsodium.org/key_derivation + * + * @param key The key whose subkey we are deriving. In the context of key + * derivation, this is usually referred to as the "master key", but we try to + * deemphasize that nomenclature to avoid confusion with the user's master key. + * + * @param subKeyLength The length of the required subkey. + * + * @param subKeyID An identifier of the subkey. + * + * @param context A short but otherwise arbitrary string (non-secret) used to + * separate domains in which the subkeys are going to be used. + */ +export const deriveSubKey = async ( key: string, subKeyLength: number, subKeyID: number, context: string, -) { +) => { await sodium.ready; return await toB64( sodium.crypto_kdf_derive_from_key( @@ -939,4 +960,4 @@ export async function generateSubKey( await fromB64(key), ), ); -} +}; diff --git a/web/packages/base/crypto/worker.ts b/web/packages/base/crypto/worker.ts index f1c46f7571..99b890539d 100644 --- a/web/packages/base/crypto/worker.ts +++ b/web/packages/base/crypto/worker.ts @@ -47,21 +47,13 @@ export class CryptoWorker { deriveKey = ei._deriveKey; deriveSensitiveKey = ei._deriveSensitiveKey; deriveInteractiveKey = ei._deriveInteractiveKey; + deriveSubKey = ei._deriveSubKey; // TODO: -- AUDIT BELOW -- async generateKeyAndEncryptToB64(data: string) { return libsodium.generateKeyAndEncryptToB64(data); } - - async generateSubKey( - key: string, - subKeyLength: number, - subKeyID: number, - context: string, - ) { - return libsodium.generateSubKey(key, subKeyLength, subKeyID, context); - } } expose(CryptoWorker); From c88a43d2dcffd77668862ca29d10159ab295fdef Mon Sep 17 00:00:00 2001 From: Manav Rathi Date: Thu, 5 Jun 2025 16:17:42 +0530 Subject: [PATCH 19/22] Use --- web/packages/base/crypto/libsodium.ts | 13 ++++++++----- web/packages/shared/crypto/helpers.ts | 2 +- 2 files changed, 9 insertions(+), 6 deletions(-) diff --git a/web/packages/base/crypto/libsodium.ts b/web/packages/base/crypto/libsodium.ts index 45f511c9f8..d3a6b8df2e 100644 --- a/web/packages/base/crypto/libsodium.ts +++ b/web/packages/base/crypto/libsodium.ts @@ -925,8 +925,9 @@ export const deriveInteractiveKey = async ( }; /** - * Derive a {@link subKeyID}-th subkey of length {@link subKeyLength} bytes - * using the given {@link key} and the {@link context}. + * Derive a {@link subKeyID}-th subkey of length {@link subKeyLength} bytes by + * applying a KDF (Key Derivation Function) for the given {@link key} and the + * {@link context}. * * Multiple secret subkeys can be (deterministically) derived from a single * high-entropy key. Knowledge of the derived key does not impact the security @@ -934,8 +935,8 @@ export const deriveInteractiveKey = async ( * * See: https://doc.libsodium.org/key_derivation * - * @param key The key whose subkey we are deriving. In the context of key - * derivation, this is usually referred to as the "master key", but we try to + * @param key The key (base64) whose subkey we are deriving. In the context of + * key derivation, this is usually referred to as the "master key", but we * deemphasize that nomenclature to avoid confusion with the user's master key. * * @param subKeyLength The length of the required subkey. @@ -944,6 +945,8 @@ export const deriveInteractiveKey = async ( * * @param context A short but otherwise arbitrary string (non-secret) used to * separate domains in which the subkeys are going to be used. + * + * @returns The subkey as a base64 string. */ export const deriveSubKey = async ( key: string, @@ -952,7 +955,7 @@ export const deriveSubKey = async ( context: string, ) => { await sodium.ready; - return await toB64( + return toB64( sodium.crypto_kdf_derive_from_key( subKeyLength, subKeyID, diff --git a/web/packages/shared/crypto/helpers.ts b/web/packages/shared/crypto/helpers.ts index 02f4a66317..a3d4f2d045 100644 --- a/web/packages/shared/crypto/helpers.ts +++ b/web/packages/shared/crypto/helpers.ts @@ -69,7 +69,7 @@ export async function generateAndSaveIntermediateKeyAttributes( export const generateLoginSubKey = async (kek: string) => { const cryptoWorker = await sharedCryptoWorker(); - const kekSubKeyString = await cryptoWorker.generateSubKey( + const kekSubKeyString = await cryptoWorker.deriveSubKey( kek, LOGIN_SUB_KEY_LENGTH, LOGIN_SUB_KEY_ID, From f7dcaffc32d184e23c8e1341e1a81658e7e5914b Mon Sep 17 00:00:00 2001 From: Manav Rathi Date: Thu, 5 Jun 2025 16:26:40 +0530 Subject: [PATCH 20/22] Move --- .../accounts/pages/change-password.tsx | 2 +- web/packages/accounts/pages/credentials.tsx | 2 +- web/packages/accounts/services/srp.ts | 24 ++++++++++++++++++- web/packages/shared/crypto/helpers.ts | 21 ---------------- 4 files changed, 25 insertions(+), 24 deletions(-) diff --git a/web/packages/accounts/pages/change-password.tsx b/web/packages/accounts/pages/change-password.tsx index a74aa31422..871b800fc6 100644 --- a/web/packages/accounts/pages/change-password.tsx +++ b/web/packages/accounts/pages/change-password.tsx @@ -10,6 +10,7 @@ import { appHomeRoute, stashRedirect } from "ente-accounts/services/redirect"; import { convertBase64ToBuffer, convertBufferToBase64, + generateLoginSubKey, generateSRPClient, generateSRPSetupAttributes, } from "ente-accounts/services/srp"; @@ -28,7 +29,6 @@ import { sharedCryptoWorker } from "ente-base/crypto"; import type { DerivedKey } from "ente-base/crypto/types"; import { generateAndSaveIntermediateKeyAttributes, - generateLoginSubKey, saveKeyInSessionStore, } from "ente-shared/crypto/helpers"; import { getData, setData } from "ente-shared/storage/localStorage"; diff --git a/web/packages/accounts/pages/credentials.tsx b/web/packages/accounts/pages/credentials.tsx index 0e8820512f..e281293f4f 100644 --- a/web/packages/accounts/pages/credentials.tsx +++ b/web/packages/accounts/pages/credentials.tsx @@ -23,6 +23,7 @@ import { import { checkSessionValidity } from "ente-accounts/services/session"; import { configureSRP, + generateLoginSubKey, generateSRPSetupAttributes, loginViaSRP, } from "ente-accounts/services/srp"; @@ -39,7 +40,6 @@ import log from "ente-base/log"; import { decryptAndStoreToken, generateAndSaveIntermediateKeyAttributes, - generateLoginSubKey, saveKeyInSessionStore, } from "ente-shared/crypto/helpers"; import { CustomError } from "ente-shared/error"; diff --git a/web/packages/accounts/services/srp.ts b/web/packages/accounts/services/srp.ts index a41035fc0c..db6ab78477 100644 --- a/web/packages/accounts/services/srp.ts +++ b/web/packages/accounts/services/srp.ts @@ -1,7 +1,6 @@ import type { KeyAttributes } from "ente-accounts/services/user"; import { sharedCryptoWorker } from "ente-base/crypto"; import log from "ente-base/log"; -import { generateLoginSubKey } from "ente-shared/crypto/helpers"; import { getToken } from "ente-shared/storage/localStorage/helpers"; import { SRP, SrpClient } from "fast-srp-hap"; import { v4 as uuidv4 } from "uuid"; @@ -17,6 +16,29 @@ import type { UserVerificationResponse } from "./user"; const SRP_PARAMS = SRP.params["4096"]; +const LOGIN_SUB_KEY_LENGTH = 32; +const LOGIN_SUB_KEY_ID = 1; +const LOGIN_SUB_KEY_CONTEXT = "loginctx"; +const LOGIN_SUB_KEY_BYTE_LENGTH = 16; + +export const generateLoginSubKey = async (kek: string) => { + const cryptoWorker = await sharedCryptoWorker(); + const kekSubKeyString = await cryptoWorker.deriveSubKey( + kek, + LOGIN_SUB_KEY_LENGTH, + LOGIN_SUB_KEY_ID, + LOGIN_SUB_KEY_CONTEXT, + ); + const kekSubKey = await cryptoWorker.fromB64(kekSubKeyString); + + // use first 16 bytes of generated kekSubKey as loginSubKey + const loginSubKey = await cryptoWorker.toB64( + kekSubKey.slice(0, LOGIN_SUB_KEY_BYTE_LENGTH), + ); + + return loginSubKey; +}; + export const configureSRP = async ({ srpSalt, srpUserID, diff --git a/web/packages/shared/crypto/helpers.ts b/web/packages/shared/crypto/helpers.ts index a3d4f2d045..5136903e04 100644 --- a/web/packages/shared/crypto/helpers.ts +++ b/web/packages/shared/crypto/helpers.ts @@ -4,10 +4,6 @@ import { masterKeyFromSession } from "ente-base/session"; import { getData, setData, setLSUser } from "ente-shared/storage/localStorage"; import { type SessionKey, setKey } from "ente-shared/storage/sessionStorage"; -const LOGIN_SUB_KEY_LENGTH = 32; -const LOGIN_SUB_KEY_ID = 1; -const LOGIN_SUB_KEY_CONTEXT = "loginctx"; -const LOGIN_SUB_KEY_BYTE_LENGTH = 16; export async function decryptAndStoreToken( keyAttributes: KeyAttributes, @@ -67,23 +63,6 @@ export async function generateAndSaveIntermediateKeyAttributes( return intermediateKeyAttributes; } -export const generateLoginSubKey = async (kek: string) => { - const cryptoWorker = await sharedCryptoWorker(); - const kekSubKeyString = await cryptoWorker.deriveSubKey( - kek, - LOGIN_SUB_KEY_LENGTH, - LOGIN_SUB_KEY_ID, - LOGIN_SUB_KEY_CONTEXT, - ); - const kekSubKey = await cryptoWorker.fromB64(kekSubKeyString); - - // use first 16 bytes of generated kekSubKey as loginSubKey - const loginSubKey = await cryptoWorker.toB64( - kekSubKey.slice(0, LOGIN_SUB_KEY_BYTE_LENGTH), - ); - - return loginSubKey; -}; export const saveKeyInSessionStore = async ( keyType: SessionKey, From d62865f9e56397d4fe67c1fdfb7d7ac9d8e3f035 Mon Sep 17 00:00:00 2001 From: Manav Rathi Date: Thu, 5 Jun 2025 16:33:04 +0530 Subject: [PATCH 21/22] Inline --- .../accounts/pages/change-password.tsx | 4 +-- web/packages/accounts/pages/credentials.tsx | 4 +-- web/packages/accounts/services/srp.ts | 35 ++++++++++--------- web/packages/shared/crypto/helpers.ts | 2 -- 4 files changed, 22 insertions(+), 23 deletions(-) diff --git a/web/packages/accounts/pages/change-password.tsx b/web/packages/accounts/pages/change-password.tsx index 871b800fc6..d05da4a044 100644 --- a/web/packages/accounts/pages/change-password.tsx +++ b/web/packages/accounts/pages/change-password.tsx @@ -10,7 +10,7 @@ import { appHomeRoute, stashRedirect } from "ente-accounts/services/redirect"; import { convertBase64ToBuffer, convertBufferToBase64, - generateLoginSubKey, + deriveSRPPassword, generateSRPClient, generateSRPSetupAttributes, } from "ente-accounts/services/srp"; @@ -78,7 +78,7 @@ const Page: React.FC = () => { memLimit: kek.memLimit, }; - const loginSubKey = await generateLoginSubKey(kek.key); + const loginSubKey = await deriveSRPPassword(kek.key); const { srpUserID, srpSalt, srpVerifier } = await generateSRPSetupAttributes(loginSubKey); diff --git a/web/packages/accounts/pages/credentials.tsx b/web/packages/accounts/pages/credentials.tsx index e281293f4f..2dc62a5b37 100644 --- a/web/packages/accounts/pages/credentials.tsx +++ b/web/packages/accounts/pages/credentials.tsx @@ -23,7 +23,7 @@ import { import { checkSessionValidity } from "ente-accounts/services/session"; import { configureSRP, - generateLoginSubKey, + deriveSRPPassword, generateSRPSetupAttributes, loginViaSRP, } from "ente-accounts/services/srp"; @@ -292,7 +292,7 @@ const Page: React.FC = () => { } log.debug(() => `userSRPSetupPending ${!srpAttributes}`); if (!srpAttributes) { - const loginSubKey = await generateLoginSubKey(kek); + const loginSubKey = await deriveSRPPassword(kek); const srpSetupAttributes = await generateSRPSetupAttributes(loginSubKey); await configureSRP(srpSetupAttributes); diff --git a/web/packages/accounts/services/srp.ts b/web/packages/accounts/services/srp.ts index db6ab78477..32f2ad2d71 100644 --- a/web/packages/accounts/services/srp.ts +++ b/web/packages/accounts/services/srp.ts @@ -16,27 +16,28 @@ import type { UserVerificationResponse } from "./user"; const SRP_PARAMS = SRP.params["4096"]; -const LOGIN_SUB_KEY_LENGTH = 32; -const LOGIN_SUB_KEY_ID = 1; -const LOGIN_SUB_KEY_CONTEXT = "loginctx"; -const LOGIN_SUB_KEY_BYTE_LENGTH = 16; - -export const generateLoginSubKey = async (kek: string) => { +/** + * Derive a "password" (which is really an arbitrary binary value, not human + * generated) for use as the SRP user password by applying a deterministic KDF + * (Key Derivation Function) to the provided {@link kek}. + * + * @param kek The user's kek (key encryption key) as a base64 string. + * + * @returns A string that can be used as the SRP user password. + */ +export const deriveSRPPassword = async (kek: string) => { const cryptoWorker = await sharedCryptoWorker(); const kekSubKeyString = await cryptoWorker.deriveSubKey( kek, - LOGIN_SUB_KEY_LENGTH, - LOGIN_SUB_KEY_ID, - LOGIN_SUB_KEY_CONTEXT, + 32, + 1, + "loginctx", ); const kekSubKey = await cryptoWorker.fromB64(kekSubKeyString); - // use first 16 bytes of generated kekSubKey as loginSubKey - const loginSubKey = await cryptoWorker.toB64( - kekSubKey.slice(0, LOGIN_SUB_KEY_BYTE_LENGTH), - ); - - return loginSubKey; + // Use the first 16 bytes (128 bits) of the KEK's KDF subkey as the SRP + // password (instead of entire 32 bytes). + return await cryptoWorker.toB64(kekSubKey.slice(0, 16)); }; export const configureSRP = async ({ @@ -109,7 +110,7 @@ export const loginViaSRP = async ( kek: string, ): Promise => { try { - const loginSubKey = await generateLoginSubKey(kek); + const loginSubKey = await deriveSRPPassword(kek); const srpClient = await generateSRPClient( srpAttributes.srpSalt, srpAttributes.srpUserID, @@ -214,7 +215,7 @@ export async function generateKeyAndSRPAttributes( nonce: secretKeyDecryptionNonce, } = await cryptoWorker.encryptBox(keyPair.privateKey, masterKey); - const loginSubKey = await generateLoginSubKey(kek.key); + const loginSubKey = await deriveSRPPassword(kek.key); const srpSetupAttributes = await generateSRPSetupAttributes(loginSubKey); diff --git a/web/packages/shared/crypto/helpers.ts b/web/packages/shared/crypto/helpers.ts index 5136903e04..a39596309a 100644 --- a/web/packages/shared/crypto/helpers.ts +++ b/web/packages/shared/crypto/helpers.ts @@ -4,7 +4,6 @@ import { masterKeyFromSession } from "ente-base/session"; import { getData, setData, setLSUser } from "ente-shared/storage/localStorage"; import { type SessionKey, setKey } from "ente-shared/storage/sessionStorage"; - export async function decryptAndStoreToken( keyAttributes: KeyAttributes, masterKey: string, @@ -63,7 +62,6 @@ export async function generateAndSaveIntermediateKeyAttributes( return intermediateKeyAttributes; } - export const saveKeyInSessionStore = async ( keyType: SessionKey, key: string, From 51a736dbce92ffa90c31d2eb2676beb58bc48073 Mon Sep 17 00:00:00 2001 From: Manav Rathi Date: Thu, 5 Jun 2025 17:25:38 +0530 Subject: [PATCH 22/22] Adapt --- web/packages/accounts/services/srp.ts | 14 +++----------- web/packages/base/crypto/ente-impl.ts | 2 +- web/packages/base/crypto/index.ts | 10 ++++++---- web/packages/base/crypto/libsodium.ts | 26 ++++++++++++++------------ web/packages/base/crypto/worker.ts | 2 +- 5 files changed, 25 insertions(+), 29 deletions(-) diff --git a/web/packages/accounts/services/srp.ts b/web/packages/accounts/services/srp.ts index 32f2ad2d71..82867ba439 100644 --- a/web/packages/accounts/services/srp.ts +++ b/web/packages/accounts/services/srp.ts @@ -1,5 +1,5 @@ import type { KeyAttributes } from "ente-accounts/services/user"; -import { sharedCryptoWorker } from "ente-base/crypto"; +import { deriveSubKeyBytes, sharedCryptoWorker, toB64 } from "ente-base/crypto"; import log from "ente-base/log"; import { getToken } from "ente-shared/storage/localStorage/helpers"; import { SRP, SrpClient } from "fast-srp-hap"; @@ -26,18 +26,10 @@ const SRP_PARAMS = SRP.params["4096"]; * @returns A string that can be used as the SRP user password. */ export const deriveSRPPassword = async (kek: string) => { - const cryptoWorker = await sharedCryptoWorker(); - const kekSubKeyString = await cryptoWorker.deriveSubKey( - kek, - 32, - 1, - "loginctx", - ); - const kekSubKey = await cryptoWorker.fromB64(kekSubKeyString); - + const kekSubKeyBytes = await deriveSubKeyBytes(kek, 32, 1, "loginctx"); // Use the first 16 bytes (128 bits) of the KEK's KDF subkey as the SRP // password (instead of entire 32 bytes). - return await cryptoWorker.toB64(kekSubKey.slice(0, 16)); + return toB64(kekSubKeyBytes.slice(0, 16)); }; export const configureSRP = async ({ diff --git a/web/packages/base/crypto/ente-impl.ts b/web/packages/base/crypto/ente-impl.ts index 4498208df8..b5f428d701 100644 --- a/web/packages/base/crypto/ente-impl.ts +++ b/web/packages/base/crypto/ente-impl.ts @@ -39,4 +39,4 @@ export const _generateDeriveKeySalt = libsodium.generateDeriveKeySalt; export const _deriveKey = libsodium.deriveKey; export const _deriveSensitiveKey = libsodium.deriveSensitiveKey; export const _deriveInteractiveKey = libsodium.deriveInteractiveKey; -export const _deriveSubKey = libsodium.deriveSubKey; +export const _deriveSubKeyBytes = libsodium.deriveSubKeyBytes; diff --git a/web/packages/base/crypto/index.ts b/web/packages/base/crypto/index.ts index 2fb20e5a01..4ddada04c2 100644 --- a/web/packages/base/crypto/index.ts +++ b/web/packages/base/crypto/index.ts @@ -510,15 +510,17 @@ export const deriveInteractiveKey = ( /** * Derive a subkey of the given {@link key} using the specified parameters. + * + * @returns the bytes of the derived subkey. */ -export const deriveSubKey = async ( +export const deriveSubKeyBytes = async ( key: string, subKeyLength: number, subKeyID: number, context: string, -) => +): Promise => inWorker() - ? ei._deriveSubKey(key, subKeyLength, subKeyID, context) + ? ei._deriveSubKeyBytes(key, subKeyLength, subKeyID, context) : sharedWorker().then((w) => - w.deriveSubKey(key, subKeyLength, subKeyID, context), + w.deriveSubKeyBytes(key, subKeyLength, subKeyID, context), ); diff --git a/web/packages/base/crypto/libsodium.ts b/web/packages/base/crypto/libsodium.ts index d3a6b8df2e..9bf37dd390 100644 --- a/web/packages/base/crypto/libsodium.ts +++ b/web/packages/base/crypto/libsodium.ts @@ -935,7 +935,7 @@ export const deriveInteractiveKey = async ( * * See: https://doc.libsodium.org/key_derivation * - * @param key The key (base64) whose subkey we are deriving. In the context of + * @param key The key whose subkey we are deriving. In the context of * key derivation, this is usually referred to as the "master key", but we * deemphasize that nomenclature to avoid confusion with the user's master key. * @@ -946,21 +946,23 @@ export const deriveInteractiveKey = async ( * @param context A short but otherwise arbitrary string (non-secret) used to * separate domains in which the subkeys are going to be used. * - * @returns The subkey as a base64 string. + * @returns The bytes of the subkey. + * + * Note that returning bytes is a bit unusual, usually we'd return the base64 + * string from functions. However, this particular function is used in only one + * place in our code, and so we adapt the interface for its convenience. */ -export const deriveSubKey = async ( - key: string, +export const deriveSubKeyBytes = async ( + key: BytesOrB64, subKeyLength: number, subKeyID: number, context: string, -) => { +): Promise => { await sodium.ready; - return toB64( - sodium.crypto_kdf_derive_from_key( - subKeyLength, - subKeyID, - context, - await fromB64(key), - ), + return sodium.crypto_kdf_derive_from_key( + subKeyLength, + subKeyID, + context, + await bytes(key), ); }; diff --git a/web/packages/base/crypto/worker.ts b/web/packages/base/crypto/worker.ts index 99b890539d..22fa693d79 100644 --- a/web/packages/base/crypto/worker.ts +++ b/web/packages/base/crypto/worker.ts @@ -47,7 +47,7 @@ export class CryptoWorker { deriveKey = ei._deriveKey; deriveSensitiveKey = ei._deriveSensitiveKey; deriveInteractiveKey = ei._deriveInteractiveKey; - deriveSubKey = ei._deriveSubKey; + deriveSubKeyBytes = ei._deriveSubKeyBytes; // TODO: -- AUDIT BELOW --