diff --git a/src/lib/modules/crypto/hmac.worker.ts b/src/lib/modules/crypto/hmac.worker.ts new file mode 100644 index 0000000..5d32189 --- /dev/null +++ b/src/lib/modules/crypto/hmac.worker.ts @@ -0,0 +1,30 @@ +import { hmac } from "@noble/hashes/hmac.js"; +import { sha256 } from "@noble/hashes/sha2.js"; + +interface ComputeMessage { + type: "compute"; + file: File; + hmacSecret: ArrayBuffer; +} + +type WorkerMessage = ComputeMessage; + +self.onmessage = async (event: MessageEvent) => { + const { type } = event.data; + + if (type === "compute") { + const { file, hmacSecret } = event.data; + + const h = hmac.create(sha256, new Uint8Array(hmacSecret)); + const reader = file.stream().getReader(); + + while (true) { + const { done, value } = await reader.read(); + if (done) break; + h.update(value); + } + + const result = h.digest(); + self.postMessage({ type: "result", hmac: result }, { transfer: [result.buffer] }); + } +}; diff --git a/src/lib/modules/crypto/hmacWorker.ts b/src/lib/modules/crypto/hmacWorker.ts new file mode 100644 index 0000000..041eb71 --- /dev/null +++ b/src/lib/modules/crypto/hmacWorker.ts @@ -0,0 +1,25 @@ +import HmacWorker from "./hmac.worker?worker"; + +export const computeFileHmac = async (file: File, hmacSecret: CryptoKey): Promise => { + const worker = new HmacWorker(); + const hmacSecretRaw = await crypto.subtle.exportKey("raw", hmacSecret); + + return new Promise((resolve, reject) => { + worker.onmessage = (event: MessageEvent<{ type: "result"; hmac: Uint8Array }>) => { + if (event.data.type === "result") { + resolve(event.data.hmac); + worker.terminate(); + } + }; + + worker.onerror = (error) => { + reject(error); + worker.terminate(); + }; + + worker.postMessage( + { type: "compute", file, hmacSecret: hmacSecretRaw }, + { transfer: [hmacSecretRaw] }, + ); + }); +}; diff --git a/src/lib/modules/file/upload.svelte.ts b/src/lib/modules/file/upload.svelte.ts index 9e9f784..ed116f5 100644 --- a/src/lib/modules/file/upload.svelte.ts +++ b/src/lib/modules/file/upload.svelte.ts @@ -1,13 +1,8 @@ import ExifReader from "exifreader"; import { limitFunction } from "p-limit"; import { CHUNK_SIZE } from "$lib/constants"; -import { - encodeToBase64, - generateDataKey, - wrapDataKey, - encryptString, - createHmacStream, -} from "$lib/modules/crypto"; +import { encodeToBase64, generateDataKey, wrapDataKey, encryptString } from "$lib/modules/crypto"; +import { computeFileHmac } from "$lib/modules/crypto/hmacWorker"; import { Scheduler } from "$lib/modules/scheduler"; import { generateThumbnail } from "$lib/modules/thumbnail"; import { uploadBlob } from "$lib/modules/upload"; @@ -56,16 +51,8 @@ export const clearUploadedFiles = () => { const requestDuplicateFileScan = limitFunction( async (file: File, hmacSecret: HmacSecret, onDuplicate: () => Promise) => { - const hmacStream = await createHmacStream(hmacSecret.secret); - const reader = file.stream().getReader(); - - while (true) { - const { done, value } = await reader.read(); - if (done) break; - hmacStream.update(value); - } - - const fileSigned = encodeToBase64(hmacStream.digest()); + const hmacResult = await computeFileHmac(file, hmacSecret.secret); + const fileSigned = encodeToBase64(hmacResult); const files = await trpc().file.listByHash.query({ hskVersion: hmacSecret.version, contentHmac: fileSigned,