Merge pull request #959 from ente-io/crypto-internal-review

Refactor CryptoWorker
This commit is contained in:
Abhinav Kumar 2023-02-28 17:15:14 +05:30 committed by GitHub
commit 9eb904c666
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
11 changed files with 68 additions and 79 deletions

View file

@ -207,7 +207,7 @@ class DownloadManager {
); );
const fileKey = await cryptoWorker.fromB64(file.key); const fileKey = await cryptoWorker.fromB64(file.key);
const { pullState, decryptionChunkSize } = const { pullState, decryptionChunkSize } =
await cryptoWorker.initDecryption( await cryptoWorker.initChunkDecryption(
decryptionHeader, decryptionHeader,
fileKey fileKey
); );
@ -229,7 +229,7 @@ class DownloadManager {
decryptionChunkSize decryptionChunkSize
); );
const { decryptedData } = const { decryptedData } =
await cryptoWorker.decryptChunk( await cryptoWorker.decryptFileChunk(
fileData, fileData,
pullState pullState
); );
@ -242,7 +242,7 @@ class DownloadManager {
} else { } else {
if (data) { if (data) {
const { decryptedData } = const { decryptedData } =
await cryptoWorker.decryptChunk( await cryptoWorker.decryptFileChunk(
data, data,
pullState pullState
); );

View file

@ -214,7 +214,7 @@ class PublicCollectionDownloadManager {
); );
const fileKey = await cryptoWorker.fromB64(file.key); const fileKey = await cryptoWorker.fromB64(file.key);
const { pullState, decryptionChunkSize } = const { pullState, decryptionChunkSize } =
await cryptoWorker.initDecryption( await cryptoWorker.initChunkDecryption(
decryptionHeader, decryptionHeader,
fileKey fileKey
); );
@ -236,7 +236,7 @@ class PublicCollectionDownloadManager {
decryptionChunkSize decryptionChunkSize
); );
const { decryptedData } = const { decryptedData } =
await cryptoWorker.decryptChunk( await cryptoWorker.decryptFileChunk(
fileData, fileData,
pullState pullState
); );
@ -249,7 +249,7 @@ class PublicCollectionDownloadManager {
} else { } else {
if (data) { if (data) {
const { decryptedData } = const { decryptedData } =
await cryptoWorker.decryptChunk( await cryptoWorker.decryptFileChunk(
data, data,
pullState pullState
); );

View file

@ -1,7 +1,12 @@
import { Remote } from 'comlink';
import { EncryptionResult } from 'types/crypto'; import { EncryptionResult } from 'types/crypto';
import { DataStream, isDataStream } from 'types/upload'; import { DataStream, isDataStream } from 'types/upload';
import { DedicatedCryptoWorker } from 'worker/crypto.worker';
async function encryptFileStream(worker, fileData: DataStream) { async function encryptFileStream(
worker: Remote<DedicatedCryptoWorker>,
fileData: DataStream
) {
const { stream, chunkCount } = fileData; const { stream, chunkCount } = fileData;
const fileStreamReader = stream.getReader(); const fileStreamReader = stream.getReader();
const { key, decryptionHeader, pushState } = const { key, decryptionHeader, pushState } =
@ -32,7 +37,7 @@ async function encryptFileStream(worker, fileData: DataStream) {
} }
export async function encryptFiledata( export async function encryptFiledata(
worker, worker: Remote<DedicatedCryptoWorker>,
filedata: Uint8Array | DataStream filedata: Uint8Array | DataStream
): Promise<EncryptionResult<Uint8Array | DataStream>> { ): Promise<EncryptionResult<Uint8Array | DataStream>> {
return isDataStream(filedata) return isDataStream(filedata)

View file

@ -69,7 +69,7 @@ export async function readFile(
} }
export async function extractFileMetadata( export async function extractFileMetadata(
worker, worker: Remote<DedicatedCryptoWorker>,
parsedMetadataJSONMap: ParsedMetadataJSONMap, parsedMetadataJSONMap: ParsedMetadataJSONMap,
collectionID: number, collectionID: number,
fileTypeInfo: FileTypeInfo, fileTypeInfo: FileTypeInfo,

View file

@ -1,11 +1,16 @@
import { Remote } from 'comlink';
import { FILE_READER_CHUNK_SIZE } from 'constants/upload'; import { FILE_READER_CHUNK_SIZE } from 'constants/upload';
import { getFileStream, getElectronFileStream } from 'services/readerService'; import { getFileStream, getElectronFileStream } from 'services/readerService';
import { ElectronFile, DataStream } from 'types/upload'; import { ElectronFile, DataStream } from 'types/upload';
import { CustomError } from 'utils/error'; import { CustomError } from 'utils/error';
import { addLogLine, getFileNameSize } from 'utils/logging'; import { addLogLine, getFileNameSize } from 'utils/logging';
import { logError } from 'utils/sentry'; import { logError } from 'utils/sentry';
import { DedicatedCryptoWorker } from 'worker/crypto.worker';
export async function getFileHash(worker, file: File | ElectronFile) { export async function getFileHash(
worker: Remote<DedicatedCryptoWorker>,
file: File | ElectronFile
) {
try { try {
addLogLine(`getFileHash called for ${getFileNameSize(file)}`); addLogLine(`getFileHash called for ${getFileNameSize(file)}`);
let filedata: DataStream; let filedata: DataStream;

View file

@ -18,6 +18,8 @@ import { extractFileMetadata } from './fileService';
import { getFileHash } from './hashService'; import { getFileHash } from './hashService';
import { generateThumbnail } from './thumbnailService'; import { generateThumbnail } from './thumbnailService';
import uploadCancelService from './uploadCancelService'; import uploadCancelService from './uploadCancelService';
import { Remote } from 'comlink';
import { DedicatedCryptoWorker } from 'worker/crypto.worker';
interface LivePhotoIdentifier { interface LivePhotoIdentifier {
collectionID: number; collectionID: number;
@ -44,7 +46,7 @@ export async function getLivePhotoFileType(
} }
export async function extractLivePhotoMetadata( export async function extractLivePhotoMetadata(
worker, worker: Remote<DedicatedCryptoWorker>,
parsedMetadataJSONMap: ParsedMetadataJSONMap, parsedMetadataJSONMap: ParsedMetadataJSONMap,
collectionID: number, collectionID: number,
fileTypeInfo: FileTypeInfo, fileTypeInfo: FileTypeInfo,

View file

@ -17,6 +17,8 @@ import {
tryToParseDateTime, tryToParseDateTime,
} from 'utils/time'; } from 'utils/time';
import { getFileHash } from './hashService'; import { getFileHash } from './hashService';
import { Remote } from 'comlink';
import { DedicatedCryptoWorker } from 'worker/crypto.worker';
interface ParsedMetadataJSONWithTitle { interface ParsedMetadataJSONWithTitle {
title: string; title: string;
@ -30,7 +32,7 @@ const NULL_PARSED_METADATA_JSON: ParsedMetadataJSON = {
}; };
export async function extractMetadata( export async function extractMetadata(
worker, worker: Remote<DedicatedCryptoWorker>,
receivedFile: File | ElectronFile, receivedFile: File | ElectronFile,
fileTypeInfo: FileTypeInfo fileTypeInfo: FileTypeInfo
) { ) {

View file

@ -105,7 +105,7 @@ class UploadService {
} }
async extractAssetMetadata( async extractAssetMetadata(
worker, worker: Remote<DedicatedCryptoWorker>,
{ isLivePhoto, file, livePhotoAssets }: UploadAsset, { isLivePhoto, file, livePhotoAssets }: UploadAsset,
collectionID: number, collectionID: number,
fileTypeInfo: FileTypeInfo fileTypeInfo: FileTypeInfo

View file

@ -88,8 +88,11 @@ export const saveKeyInSessionStore = async (
key: string, key: string,
fromDesktop?: boolean fromDesktop?: boolean
) => { ) => {
// the key is encrypted before saving in session storage, to obfuscate it from the browser
const cryptoWorker = await ComlinkCryptoWorker.getInstance(); const cryptoWorker = await ComlinkCryptoWorker.getInstance();
const sessionKeyAttributes = await cryptoWorker.encryptToB64(key); const sessionKeyAttributes = await cryptoWorker.generateKeyAndEncryptToB64(
key
);
setKey(keyType, sessionKeyAttributes); setKey(keyType, sessionKeyAttributes);
if (isElectron() && !fromDesktop) { if (isElectron() && !fromDesktop) {
safeStorageService.setEncryptionKey(key); safeStorageService.setEncryptionKey(key);

View file

@ -69,7 +69,10 @@ export async function initChunkDecryption(header: Uint8Array, key: Uint8Array) {
return { pullState, decryptionChunkSize, tag }; return { pullState, decryptionChunkSize, tag };
} }
export async function decryptChunk(data: Uint8Array, pullState: StateAddress) { export async function decryptFileChunk(
data: Uint8Array,
pullState: StateAddress
) {
await sodium.ready; await sodium.ready;
const pullResult = sodium.crypto_secretstream_xchacha20poly1305_pull( const pullResult = sodium.crypto_secretstream_xchacha20poly1305_pull(
pullState, pullState,
@ -79,12 +82,10 @@ export async function decryptChunk(data: Uint8Array, pullState: StateAddress) {
return { decryptedData: pullResult.message, newTag }; return { decryptedData: pullResult.message, newTag };
} }
export async function encryptChaChaOneShot(data: Uint8Array, key?: string) { export async function encryptChaChaOneShot(data: Uint8Array, key: string) {
await sodium.ready; await sodium.ready;
const uintkey: Uint8Array = key const uintkey: Uint8Array = await fromB64(key);
? await fromB64(key)
: sodium.crypto_secretstream_xchacha20poly1305_keygen();
const initPushResult = const initPushResult =
sodium.crypto_secretstream_xchacha20poly1305_init_push(uintkey); sodium.crypto_secretstream_xchacha20poly1305_init_push(uintkey);
const [pushState, header] = [initPushResult.state, initPushResult.header]; const [pushState, header] = [initPushResult.state, initPushResult.header];
@ -104,12 +105,11 @@ export async function encryptChaChaOneShot(data: Uint8Array, key?: string) {
}; };
} }
export async function encryptChaCha(data: Uint8Array, key?: string) { export async function encryptChaCha(data: Uint8Array) {
await sodium.ready; await sodium.ready;
const uintkey: Uint8Array = key const uintkey: Uint8Array =
? await fromB64(key) sodium.crypto_secretstream_xchacha20poly1305_keygen();
: sodium.crypto_secretstream_xchacha20poly1305_keygen();
const initPushResult = const initPushResult =
sodium.crypto_secretstream_xchacha20poly1305_init_push(uintkey); sodium.crypto_secretstream_xchacha20poly1305_init_push(uintkey);
@ -159,14 +159,14 @@ export async function initChunkEncryption() {
pushState, pushState,
}; };
} }
export async function encryptFileChunk( export async function encryptFileChunk(
data: Uint8Array, data: Uint8Array,
pushState: sodium.StateAddress, pushState: sodium.StateAddress,
finalChunk?: boolean isFinalChunk: boolean
) { ) {
await sodium.ready; await sodium.ready;
const tag = isFinalChunk
const tag = finalChunk
? sodium.crypto_secretstream_xchacha20poly1305_TAG_FINAL ? sodium.crypto_secretstream_xchacha20poly1305_TAG_FINAL
: sodium.crypto_secretstream_xchacha20poly1305_TAG_MESSAGE; : sodium.crypto_secretstream_xchacha20poly1305_TAG_MESSAGE;
const pushResult = sodium.crypto_secretstream_xchacha20poly1305_push( const pushResult = sodium.crypto_secretstream_xchacha20poly1305_push(
@ -178,12 +178,9 @@ export async function encryptFileChunk(
return pushResult; return pushResult;
} }
export async function encryptToB64(data: string, key?: string) { export async function encryptToB64(data: string, key: string) {
await sodium.ready; await sodium.ready;
const encrypted = await encrypt( const encrypted = await encrypt(await fromB64(data), await fromB64(key));
await fromB64(data),
key ? await fromB64(key) : null
);
return { return {
encryptedData: await toB64(encrypted.encryptedData), encryptedData: await toB64(encrypted.encryptedData),
@ -191,7 +188,13 @@ export async function encryptToB64(data: string, key?: string) {
nonce: await toB64(encrypted.nonce), nonce: await toB64(encrypted.nonce),
} as B64EncryptionResult; } as B64EncryptionResult;
} }
export async function encryptUTF8(data: string, key?: string) {
export async function generateKeyAndEncryptToB64(data: string) {
const key = sodium.crypto_secretbox_keygen();
return await encryptToB64(data, await toB64(key));
}
export async function encryptUTF8(data: string, key: string) {
const b64Data = await toB64(await fromString(data)); const b64Data = await toB64(await fromString(data));
return await encryptToB64(b64Data, key); return await encryptToB64(b64Data, key);
} }
@ -218,41 +221,22 @@ export async function decryptToUTF8(data: string, nonce: string, key: string) {
return sodium.to_string(decrypted); return sodium.to_string(decrypted);
} }
export async function encrypt(data: Uint8Array, key?: Uint8Array) { async function encrypt(data: Uint8Array, key: Uint8Array) {
await sodium.ready; await sodium.ready;
const uintkey: Uint8Array = key || sodium.crypto_secretbox_keygen();
const nonce = sodium.randombytes_buf(sodium.crypto_secretbox_NONCEBYTES); const nonce = sodium.randombytes_buf(sodium.crypto_secretbox_NONCEBYTES);
const encryptedData = sodium.crypto_secretbox_easy(data, nonce, uintkey); const encryptedData = sodium.crypto_secretbox_easy(data, nonce, key);
return { return {
encryptedData, encryptedData,
key: uintkey, key,
nonce, nonce,
}; };
} }
export async function decrypt( async function decrypt(data: Uint8Array, nonce: Uint8Array, key: Uint8Array) {
data: Uint8Array,
nonce: Uint8Array,
key: Uint8Array
) {
await sodium.ready; await sodium.ready;
return sodium.crypto_secretbox_open_easy(data, nonce, key); return sodium.crypto_secretbox_open_easy(data, nonce, key);
} }
export async function verifyHash(hash: string, input: string) {
await sodium.ready;
return sodium.crypto_pwhash_str_verify(hash, await fromB64(input));
}
export async function hash(input: string) {
await sodium.ready;
return sodium.crypto_pwhash_str(
await fromB64(input),
sodium.crypto_pwhash_OPSLIMIT_SENSITIVE,
sodium.crypto_pwhash_MEMLIMIT_MODERATE
);
}
export async function initChunkHashing() { export async function initChunkHashing() {
await sodium.ready; await sodium.ready;
const hashState = sodium.crypto_generichash_init( const hashState = sodium.crypto_generichash_init(

View file

@ -49,44 +49,28 @@ export class DedicatedCryptoWorker {
return libsodium.encryptChaChaOneShot(fileData, key); return libsodium.encryptChaChaOneShot(fileData, key);
} }
async encryptFile(fileData: Uint8Array, key: string) { async encryptFile(fileData: Uint8Array) {
return libsodium.encryptChaCha(fileData, key); return libsodium.encryptChaCha(fileData);
} }
async encryptFileChunk( async encryptFileChunk(
data: Uint8Array, data: Uint8Array,
pushState: StateAddress, pushState: StateAddress,
finalChunk: boolean isFinalChunk: boolean
) { ) {
return libsodium.encryptFileChunk(data, pushState, finalChunk); return libsodium.encryptFileChunk(data, pushState, isFinalChunk);
} }
async initChunkEncryption() { async initChunkEncryption() {
return libsodium.initChunkEncryption(); return libsodium.initChunkEncryption();
} }
async initDecryption(header: Uint8Array, key: Uint8Array) { async initChunkDecryption(header: Uint8Array, key: Uint8Array) {
return libsodium.initChunkDecryption(header, key); return libsodium.initChunkDecryption(header, key);
} }
async decryptChunk(fileData: Uint8Array, pullState: StateAddress) { async decryptFileChunk(fileData: Uint8Array, pullState: StateAddress) {
return libsodium.decryptChunk(fileData, pullState); return libsodium.decryptFileChunk(fileData, pullState);
}
async encrypt(data: Uint8Array, key: Uint8Array) {
return libsodium.encrypt(data, key);
}
async decrypt(data: Uint8Array, nonce: Uint8Array, key: Uint8Array) {
return libsodium.decrypt(data, nonce, key);
}
async hash(input: string) {
return libsodium.hash(input);
}
async verifyHash(hash: string, input: string) {
return libsodium.verifyHash(hash, input);
} }
async initChunkHashing() { async initChunkHashing() {
@ -126,10 +110,14 @@ export class DedicatedCryptoWorker {
return libsodium.decryptToUTF8(data, nonce, key); return libsodium.decryptToUTF8(data, nonce, key);
} }
async encryptToB64(data: string, key?: string) { async encryptToB64(data: string, key: string) {
return libsodium.encryptToB64(data, key); return libsodium.encryptToB64(data, key);
} }
async generateKeyAndEncryptToB64(data: string) {
return libsodium.generateKeyAndEncryptToB64(data);
}
async encryptUTF8(data: string, key: string) { async encryptUTF8(data: string, key: string) {
return libsodium.encryptUTF8(data, key); return libsodium.encryptUTF8(data, key);
} }