From d5b2b52f9599d93e932be15f18526f41d846649e Mon Sep 17 00:00:00 2001 From: Maxime Cannoodt Date: Tue, 13 Sep 2022 23:21:26 +0200 Subject: [PATCH] fetch decrypt and render jpegs --- webapp/src/lib/crypto/decrypt.ts | 13 ++- webapp/src/lib/crypto/embedId.ts | 34 ++++++++ .../lib/marked/renderers/InternalEmbed.svelte | 80 +++++++++++++++---- webapp/src/lib/model/EncryptedEmbed.ts | 6 ++ webapp/src/lib/util/embeds.ts | 21 +++++ .../src/routes/note/[note_id]/embeds/[id].ts | 34 ++++++++ 6 files changed, 169 insertions(+), 19 deletions(-) create mode 100644 webapp/src/lib/crypto/embedId.ts create mode 100644 webapp/src/lib/model/EncryptedEmbed.ts create mode 100644 webapp/src/lib/util/embeds.ts create mode 100644 webapp/src/routes/note/[note_id]/embeds/[id].ts diff --git a/webapp/src/lib/crypto/decrypt.ts b/webapp/src/lib/crypto/decrypt.ts index d5cc472..a9f284b 100644 --- a/webapp/src/lib/crypto/decrypt.ts +++ b/webapp/src/lib/crypto/decrypt.ts @@ -36,6 +36,15 @@ export async function decrypt_v2(cryptData: { hmac: string; key: string; }): Promise { + const md = await decryptBuffer_v2(cryptData); + return new TextDecoder().decode(md); +} + +export async function decryptBuffer_v2(cryptData: { + ciphertext: string; + hmac: string; + key: string; +}): Promise { const secret = base64ToArrayBuffer(cryptData.key); const ciphertext_buf = base64ToArrayBuffer(cryptData.ciphertext); const hmac_buf = base64ToArrayBuffer(cryptData.hmac); @@ -51,12 +60,12 @@ export async function decrypt_v2(cryptData: { throw Error('Failed HMAC check'); } - const md = await window.crypto.subtle.decrypt( + const data = await window.crypto.subtle.decrypt( { name: 'AES-CBC', iv: new Uint8Array(16) }, await _getAesKey(secret), ciphertext_buf ); - return new TextDecoder().decode(md); + return data; } function _getAesKey(secret: ArrayBuffer): Promise { diff --git a/webapp/src/lib/crypto/embedId.ts b/webapp/src/lib/crypto/embedId.ts new file mode 100644 index 0000000..bf990fd --- /dev/null +++ b/webapp/src/lib/crypto/embedId.ts @@ -0,0 +1,34 @@ +export async function getEmbedId(filename: string): Promise { + // generate 64 bit id + const idBuf = new Uint32Array((await deriveKey(filename)).slice(0, 8)); + + // convert idBuf to base 32 string + const id = idBuf.reduce((acc, cur) => { + return acc + cur.toString(32); + }, ''); + + return id; +} + +async function deriveKey(seed: string): Promise { + const keyMaterial = await window.crypto.subtle.importKey( + 'raw', + new TextEncoder().encode(seed), + { name: 'PBKDF2' }, + false, + ['deriveBits'] + ); + + const masterKey = await window.crypto.subtle.deriveBits( + { + name: 'PBKDF2', + salt: new Uint8Array(16), + iterations: 100000, + hash: 'SHA-256' + }, + keyMaterial, + 256 + ); + + return new Uint8Array(masterKey); +} diff --git a/webapp/src/lib/marked/renderers/InternalEmbed.svelte b/webapp/src/lib/marked/renderers/InternalEmbed.svelte index b0efcdb..d519ec3 100644 --- a/webapp/src/lib/marked/renderers/InternalEmbed.svelte +++ b/webapp/src/lib/marked/renderers/InternalEmbed.svelte @@ -1,25 +1,71 @@ -
- -
- - +{#if imageUrl} + {text} +{:else} +
+ +
+ + + + Internal embed - Internal embed - - - {text} - - -
-
-
+ + {text} + + +
+
+
+{/if} diff --git a/webapp/src/lib/model/EncryptedEmbed.ts b/webapp/src/lib/model/EncryptedEmbed.ts new file mode 100644 index 0000000..fbc5892 --- /dev/null +++ b/webapp/src/lib/model/EncryptedEmbed.ts @@ -0,0 +1,6 @@ +export type EncryptedEmbed = { + note_id: string; + embed_id: string; + ciphertext: string; + hmac: string; +}; diff --git a/webapp/src/lib/util/embeds.ts b/webapp/src/lib/util/embeds.ts new file mode 100644 index 0000000..d49d662 --- /dev/null +++ b/webapp/src/lib/util/embeds.ts @@ -0,0 +1,21 @@ +/** + * Returns the EmbedType if embeddable, false if not. + * @param filename File extension to check. + * @returns EmbedType if embeddable, false if not. + */ +export function getEmbedType(filename: string): EmbedType | boolean { + return isImage(filename) ? EmbedType.IMAGE : false; +} + +export function getMimeType(filename: string) { + return 'image/jpeg'; +} + +export enum EmbedType { + IMAGE = 'IMAGE' +} + +function isImage(filename: string): boolean { + const match = filename.match(/(png|jpe?g|svg|bmp|gif|)$/i); + return !!match && match[0]?.length > 0; +} diff --git a/webapp/src/routes/note/[note_id]/embeds/[id].ts b/webapp/src/routes/note/[note_id]/embeds/[id].ts new file mode 100644 index 0000000..01fc738 --- /dev/null +++ b/webapp/src/routes/note/[note_id]/embeds/[id].ts @@ -0,0 +1,34 @@ +import type { EncryptedEmbed } from '$lib/model/EncryptedEmbed'; +import type { RequestHandler } from '@sveltejs/kit'; + +export const get: RequestHandler = async ({ request, clientAddress, params }) => { + const ip = (request.headers.get('x-forwarded-for') || clientAddress) as string; + const url = `${import.meta.env.VITE_SERVER_INTERNAL}/api/note/${params.note_id}/embeds/${ + params.id + }`; + const response = await fetch(url, { + headers: { + 'x-forwarded-for': ip + } + }); + + if (response.ok) { + try { + const embed: EncryptedEmbed = await response.json(); + return { + status: response.status, + body: embed + }; + } catch { + return { + status: 500, + error: response.statusText + }; + } + } else { + return { + status: response.status, + error: response.statusText + }; + } +};