diff --git a/src/lib/modules/crypto/sha.ts b/src/lib/modules/crypto/sha.ts index 61c2ed7..5e9e3fa 100644 --- a/src/lib/modules/crypto/sha.ts +++ b/src/lib/modules/crypto/sha.ts @@ -1,5 +1,5 @@ -import { hmac } from "@noble/hashes/hmac.js"; -import { sha256 } from "@noble/hashes/sha2.js"; +import HmacWorker from "$workers/hmac?worker"; +import type { ComputeMessage, ResultMessage } from "$workers/hmac"; export const digestMessage = async (message: BufferSource) => { return await crypto.subtle.digest("SHA-256", message); @@ -18,10 +18,24 @@ export const generateHmacSecret = async () => { }; }; -export const createHmacStream = async (hmacSecret: CryptoKey) => { - const h = hmac.create(sha256, new Uint8Array(await crypto.subtle.exportKey("raw", hmacSecret))); - return { - update: (data: Uint8Array) => h.update(data), - digest: () => h.digest(), - }; +export const signMessageHmac = async (message: Blob, hmacSecret: CryptoKey) => { + const worker = new HmacWorker(); + const stream = message.stream(); + const hmacSecretRaw = new Uint8Array(await crypto.subtle.exportKey("raw", hmacSecret)); + + return new Promise((resolve, reject) => { + worker.onmessage = (event: MessageEvent) => { + resolve(event.data.result); + worker.terminate(); + }; + + worker.onerror = ({ error }) => { + reject(error); + worker.terminate(); + }; + + worker.postMessage({ stream, key: hmacSecretRaw } satisfies ComputeMessage, { + transfer: [stream, hmacSecretRaw.buffer], + }); + }); }; diff --git a/src/lib/modules/file/upload.svelte.ts b/src/lib/modules/file/upload.svelte.ts index 9e9f784..d066d4f 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 { signMessageHmac } from "$lib/modules/crypto"; 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 signMessageHmac(file, hmacSecret.secret); + const fileSigned = encodeToBase64(hmacResult); const files = await trpc().file.listByHash.query({ hskVersion: hmacSecret.version, contentHmac: fileSigned, diff --git a/src/workers/hmac.ts b/src/workers/hmac.ts new file mode 100644 index 0000000..1b20235 --- /dev/null +++ b/src/workers/hmac.ts @@ -0,0 +1,25 @@ +import { hmac } from "@noble/hashes/hmac.js"; +import { sha256 } from "@noble/hashes/sha2.js"; + +export interface ComputeMessage { + stream: ReadableStream; + key: Uint8Array; +} + +export interface ResultMessage { + result: Uint8Array; +} + +self.onmessage = async (event: MessageEvent) => { + const h = hmac.create(sha256, event.data.key); + const reader = event.data.stream.getReader(); + + while (true) { + const { done, value } = await reader.read(); + if (done) break; + h.update(value); + } + + const result = h.digest(); + self.postMessage({ result } satisfies ResultMessage, { transfer: [result.buffer] }); +}; diff --git a/svelte.config.js b/svelte.config.js index 4ffc844..6562b93 100644 --- a/svelte.config.js +++ b/svelte.config.js @@ -8,6 +8,7 @@ const config = { adapter: adapter(), alias: { $trpc: "./src/trpc", + $workers: "./src/workers", }, }, };