diff --git a/web/packages/base/crypto/ente-impl.ts b/web/packages/base/crypto/ente-impl.ts index d928d3a8e1..875510f0bf 100644 --- a/web/packages/base/crypto/ente-impl.ts +++ b/web/packages/base/crypto/ente-impl.ts @@ -36,6 +36,10 @@ export const _decryptBox = libsodium.decryptBox2; export const _decryptBoxB64 = libsodium.decryptBoxB64; +export const _decryptBlob = libsodium.decryptBlob2; + +export const _decryptBlobB64 = libsodium.decryptBlobB64; + export const _decryptAssociatedData = libsodium.decryptBlob; export const _decryptThumbnail = _decryptAssociatedData; diff --git a/web/packages/base/crypto/ente.ts b/web/packages/base/crypto/ente.ts index 9265f615a9..f568e6ed5e 100644 --- a/web/packages/base/crypto/ente.ts +++ b/web/packages/base/crypto/ente.ts @@ -55,6 +55,7 @@ import type { BytesOrB64, DecryptBlobB64, DecryptBlobBytes, + EncryptedBlob_2, EncryptedBox2, EncryptJSON, } from "./types"; @@ -97,7 +98,7 @@ export const encryptBoxB64 = (data: BytesOrB64, key: BytesOrB64) => * This function is usually used to encrypt data associated with an Ente object * (file, collection, entity) using the object's key. * - * Use {@link decryptBlobB64} to decrypt the result. + * Use {@link decryptBlob} to decrypt the result. * * > The suffix "Blob" comes from our convention of naming functions that use * > the secretstream APIs in one-shot mode. @@ -155,13 +156,28 @@ export const decryptBox = (box: EncryptedBox2, key: BytesOrB64) => : sharedCryptoWorker().then((w) => w.decryptBox(box, key)); /** - * Variant of {@link decryptBoxlink} that returns the result as a base64 string. + * Variant of {@link decryptBox} that returns the result as a base64 string. */ export const decryptBoxB64 = (box: EncryptedBox2, key: BytesOrB64) => inWorker() ? ei._decryptBoxB64(box, key) : sharedCryptoWorker().then((w) => w.decryptBoxB64(box, key)); +/** + * Decrypt a blob encrypted using either {@link encryptBlob} or + * {@link encryptBlobB64}. + */ +export const decryptBlob = (blob: EncryptedBlob_2, key: BytesOrB64) => + assertInWorker(ei._decryptBlob(blob, key)); + +/** + * A variant of {@link decryptBlob} that returns the result as a base64 string. + */ +export const decryptBlobB64 = (blob: EncryptedBlob_2, key: BytesOrB64) => + inWorker() + ? ei._decryptBlobB64(blob, key) + : sharedCryptoWorker().then((w) => w.decryptBlobB64(blob, key)); + /** * Decrypt arbitrary data associated with an Ente object (file, collection or * entity) using the object's key. diff --git a/web/packages/base/crypto/libsodium.ts b/web/packages/base/crypto/libsodium.ts index 73e96a6078..f49ee4e2a1 100644 --- a/web/packages/base/crypto/libsodium.ts +++ b/web/packages/base/crypto/libsodium.ts @@ -18,6 +18,7 @@ import type { EncryptBytes, EncryptedBlob_2, EncryptedBlobB64_2, + EncryptedBlobBytes_2, EncryptedBox2, EncryptedBoxB64, EncryptedBoxBytes, @@ -276,7 +277,7 @@ const encryptBox = async ({ export const encryptBlob = async ( data: BytesOrB64, key: BytesOrB64, -): Promise => { +): Promise => { await sodium.ready; const uintkey = await bytes(key); @@ -304,14 +305,13 @@ export const encryptBlobB64 = async ( data: BytesOrB64, key: BytesOrB64, ): Promise => { - const { encryptedData, decryptionHeader} = await encryptBlob(data, key); + const { encryptedData, decryptionHeader } = await encryptBlob(data, key); return { encryptedData: await toB64(encryptedData), decryptionHeader: await toB64(decryptionHeader), }; }; - export const ENCRYPTION_CHUNK_SIZE = 4 * 1024 * 1024; export const encryptChaCha = async (data: Uint8Array) => { @@ -425,6 +425,34 @@ export const decryptBoxB64 = ( key: BytesOrB64, ): Promise => decryptBox2(box, key).then(toB64); +/** + * Decrypt the result of {@link encryptBlob} or {@link encryptBlobB64}. + */ +export const decryptBlob2 = async ( + { encryptedData, decryptionHeader }: EncryptedBlob_2, + key: BytesOrB64, +): Promise => { + await sodium.ready; + const pullState = sodium.crypto_secretstream_xchacha20poly1305_init_pull( + await bytes(decryptionHeader), + await bytes(key), + ); + const pullResult = sodium.crypto_secretstream_xchacha20poly1305_pull( + pullState, + await bytes(encryptedData), + null, + ); + return pullResult.message; +}; + +/** + * A variant of {@link decryptBlob2} that returns the result as a base64 string. + */ +export const decryptBlobB64 = ( + blob: EncryptedBlob_2, + key: BytesOrB64, +): Promise => decryptBlob2(blob, key).then(toB64); + /** * Decrypt the result of {@link encryptBlob}. */ diff --git a/web/packages/base/crypto/types.ts b/web/packages/base/crypto/types.ts index 19e4e1e7eb..9dd2e30e37 100644 --- a/web/packages/base/crypto/types.ts +++ b/web/packages/base/crypto/types.ts @@ -107,8 +107,31 @@ export interface EncryptedBoxB64 { * decryption. The header does not need to be secret. * * See: [Note: 3 forms of encryption (Box | Blob | Stream)]. + * + * This type is a combination of {@link EncryptedBlobBytes_2} and + * {@link EncryptedBlobB64_2} which allows the decryption routines to accept + * either the bytes or the base64 variants produced by the encryption routines. */ export interface EncryptedBlob_2 { + /** + * The encrypted data. + */ + encryptedData: BytesOrB64; + /** + * The decryption header. + * + * While the exact contents of the header are libsodium's internal details, + * it effectively contains a random nonce generated by libsodium. It does + * not need to be secret, but it is required to decrypt the data. + */ + decryptionHeader: BytesOrB64; +} + +/** + * A variant of {@link EncryptedBlob_2} that has the encrypted data and header + * as bytes ({@link Uint8Array}s). + */ +export interface EncryptedBlobBytes_2 { /** * The encrypted data. */ diff --git a/web/packages/base/crypto/worker.ts b/web/packages/base/crypto/worker.ts index 13e36c546d..1e8b930d38 100644 --- a/web/packages/base/crypto/worker.ts +++ b/web/packages/base/crypto/worker.ts @@ -17,6 +17,7 @@ export class CryptoWorker { encryptMetadataJSON = ei._encryptMetadataJSON; decryptBox = ei._decryptBox; decryptBoxB64 = ei._decryptBoxB64; + decryptBlobB64 = ei._decryptBlobB64; decryptThumbnail = ei._decryptThumbnail; decryptAssociatedDataB64 = ei._decryptAssociatedDataB64; decryptMetadataJSON = ei._decryptMetadataJSON;