diff --git a/frontend/src/chat/dm.ts b/frontend/src/chat/dm.ts index 7714eb3..13450c3 100644 --- a/frontend/src/chat/dm.ts +++ b/frontend/src/chat/dm.ts @@ -6,7 +6,7 @@ import { importAesGcmKey, aesGcmEncrypt, aesGcmDecrypt } from "../crypto/symmetr import { randomBytes } from "../crypto/kdf"; import { getCurrentKeys } from "../auth/crypto"; import { request, websocket } from "../websocket"; -import type { FetchDMResponse, SendDMRequest, WebSocketMessage, User } from "../core/types"; +import type { FetchDMResponse, SendDMRequest, WebSocketMessage, User, DmEnvelope } from "../core/types"; import type { Tabs } from "mdui/components/tabs"; import { b64, ub64 } from "../utils/utils"; @@ -35,17 +35,7 @@ export async function sendDm(recipientId: number, recipientPublicKeyB64: string, }); } -export interface DmEnvelope { - id: number; - senderId: number; - recipientId: number; - iv: string; - ciphertext: string; - salt: string; - iv2: string; - wrappedMk: string; - timestamp: string; -} + export async function fetchDm(since?: number): Promise { const url = new URL(`${API_BASE_URL}/dm/fetch`); diff --git a/frontend/src/core/types.d.ts b/frontend/src/core/types.d.ts index 8f3bf4a..b4cb17a 100644 --- a/frontend/src/core/types.d.ts +++ b/frontend/src/core/types.d.ts @@ -5,7 +5,6 @@ * @version 1.0.0 */ -import type { DmEnvelope } from "../chat/dm"; /** * HTTP headers object type @@ -169,6 +168,18 @@ export interface BackupBlob { blob: string; } +export interface DmEnvelope { + id: number; + senderId: number; + recipientId: number; + iv: string; + ciphertext: string; + salt: string; + iv2: string; + wrappedMk: string; + timestamp: string; +} + export interface FetchDMResponse { messages: DmEnvelope[] } diff --git a/frontend/src/crypto/symmetric.ts b/frontend/src/crypto/symmetric.ts index 4de472b..45b85d0 100644 --- a/frontend/src/crypto/symmetric.ts +++ b/frontend/src/crypto/symmetric.ts @@ -11,11 +11,18 @@ export async function aesGcmEncrypt(key: CryptoKey, plaintext: Uint8Array | Arra } export async function aesGcmDecrypt(key: CryptoKey, iv: Uint8Array | ArrayBuffer, ciphertext: Uint8Array | ArrayBuffer): Promise { - // WebCrypto AES-GCM requires Uint8Array for iv and data; passing ArrayBuffer slices can break auth tag boundaries - const ivBytes = iv instanceof Uint8Array ? iv : new Uint8Array(iv); - const ctBytes = ciphertext instanceof Uint8Array ? ciphertext : new Uint8Array(ciphertext); - const pt = await crypto.subtle.decrypt({ name: "AES-GCM", iv: ivBytes }, key, ctBytes); - return new Uint8Array(pt); + // Normalize IV to ArrayBuffer (12 bytes for AES-GCM) + const ivBuf: ArrayBuffer = iv instanceof Uint8Array + ? (iv.buffer as ArrayBuffer).slice(iv.byteOffset, iv.byteOffset + iv.byteLength) + : (iv as ArrayBuffer); + + // Normalize ciphertext to a contiguous ArrayBuffer slice + const ctBuf: ArrayBuffer = ciphertext instanceof Uint8Array + ? (ciphertext.buffer as ArrayBuffer).slice(ciphertext.byteOffset, ciphertext.byteOffset + ciphertext.byteLength) + : (ciphertext as ArrayBuffer); + + const pt = await crypto.subtle.decrypt({ name: "AES-GCM", iv: ivBuf }, key, ctBuf); + return new Uint8Array(pt as ArrayBuffer); } export async function importAesGcmKey(rawKey: Uint8Array | ArrayBuffer): Promise { diff --git a/frontend/src/crypto/types.d.ts b/frontend/src/crypto/types.d.ts deleted file mode 100644 index 13bbec3..0000000 --- a/frontend/src/crypto/types.d.ts +++ /dev/null @@ -1,4 +0,0 @@ -declare module "tweetnacl" { - const nacl: any; - export default nacl; -} \ No newline at end of file