diff --git a/package-lock.json b/package-lock.json index bb4d3bc..e552de2 100644 --- a/package-lock.json +++ b/package-lock.json @@ -10,6 +10,8 @@ "dependencies": { "@base-ui/react": "^1.7.0", "@fontsource-variable/geist": "^5.3.0", + "@noble/ciphers": "^2.4.0", + "@noble/curves": "^2.4.0", "@noble/ed25519": "^3.1.0", "@noble/hashes": "^2.3.0", "@scure/base": "^2.3.0", @@ -1306,6 +1308,33 @@ "node": "^22.20 || ^24.12 || >=25" } }, + "node_modules/@noble/ciphers": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/@noble/ciphers/-/ciphers-2.4.0.tgz", + "integrity": "sha512-AnjFn0Jv92laAkvMrghlFZq4qQCIN/4DxFV/eooqtC2YTjB7kBeLMS2T9KJX4Dn+ZVXLOwK0lSgqDtx9gvxtiw==", + "license": "MIT", + "engines": { + "node": ">= 20.19.0" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/@noble/curves": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/@noble/curves/-/curves-2.4.0.tgz", + "integrity": "sha512-P4/62zrgfH33CneE3Dn4WhJVA22YUU0eR51wKIan4NVRvwsA0YnPTwWGpNbpuacSujmSFLvyzpyuR30+fbq2Ew==", + "license": "MIT", + "dependencies": { + "@noble/hashes": "2.4.0" + }, + "engines": { + "node": ">= 20.19.0" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, "node_modules/@noble/ed25519": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/@noble/ed25519/-/ed25519-3.1.0.tgz", @@ -1316,9 +1345,9 @@ } }, "node_modules/@noble/hashes": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-2.3.0.tgz", - "integrity": "sha512-oN+QwyX7VSHotibwubG3kpzbwKrfnyR6OOO+3Nk/53ADL7FmgHHz4TgrbaYKvvOw09u6QTx0oiH1cNCIOuN0CQ==", + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-2.4.0.tgz", + "integrity": "sha512-X5XaVWZIBCT7HHZGm5I7ZQXDwLG+bGXuSrMQAW+7Zvl87h1kmc1ZB1VSRJcpUfoUrGQp4Fkoxm5kZ+Ms+aW+eA==", "license": "MIT", "engines": { "node": ">= 20.19.0" diff --git a/package.json b/package.json index 0eb2196..da53b41 100644 --- a/package.json +++ b/package.json @@ -13,6 +13,8 @@ "dependencies": { "@base-ui/react": "^1.7.0", "@fontsource-variable/geist": "^5.3.0", + "@noble/ciphers": "^2.4.0", + "@noble/curves": "^2.4.0", "@noble/ed25519": "^3.1.0", "@noble/hashes": "^2.3.0", "@scure/base": "^2.3.0", diff --git a/src/app/app.ts b/src/app/app.ts index 39b1115..2d354c3 100644 --- a/src/app/app.ts +++ b/src/app/app.ts @@ -134,8 +134,19 @@ export default class Enclave
{
if (this.server.websocket) {
this.server.websocket.send({ method: "Meta", ...account.meta });
- this.server.websocket.websocket.onmessage = (msg) => {
- this.onMessage(JSON.parse(msg.data));
+ this.server.websocket.websocket.onmessage = async (msg) => {
+ let buffer: ArrayBuffer;
+ if (msg.data instanceof Blob) {
+ buffer = await msg.data.arrayBuffer();
+ } else {
+ buffer = msg.data as ArrayBuffer;
+ }
+
+ const encrypted = new Uint8Array(buffer);
+ const plaintext = this.server?.websocket?.decrypt(encrypted);
+ const data = JSON.parse(new TextDecoder().decode(plaintext));
+
+ this.onMessage(data);
};
}
diff --git a/src/app/server.ts b/src/app/server.ts
index c4e20f5..ca67ec3 100644
--- a/src/app/server.ts
+++ b/src/app/server.ts
@@ -4,6 +4,7 @@ import EnclaveWebSocket from "./ws";
import { sha512 } from "@noble/hashes/sha2.js";
import { getWSUrl } from "@/lib/serverList";
import { ClientMeta, ServerMeta, StoredMessage } from "@/lib/types";
+import { ed25519 } from "@noble/curves/ed25519.js";
ed.hashes.sha512 = sha512;
@@ -60,6 +61,7 @@ export default class EnclaveServer {
) {
this.websocket = new EnclaveWebSocket(
getWSUrl(this.hostname, this.isSecure),
+ ed25519.utils.toMontgomerySecret(clientSecretKey),
);
const publicKeyString = base58.encode(clientPublicKey);
diff --git a/src/app/ws.ts b/src/app/ws.ts
index 9962d7b..fd34459 100644
--- a/src/app/ws.ts
+++ b/src/app/ws.ts
@@ -1,46 +1,138 @@
import { invoke } from "@tauri-apps/api/core";
+import { x25519 } from "@noble/curves/ed25519.js";
+import { chacha20poly1305 } from "@noble/ciphers/chacha.js";
import { ClientMethod, ServerMethod } from "./protocol";
/**
* A protocol-aware wrapper around the browser `WebSocket`.
*
- * Exposes typed protocol methods (`ServerMethod` / `ClientMethod`) rather
- * than raw WebSocket messages. Has no concept of channels, messages, or
- * app state — `EnclaveServer` builds on top of this to add those.
+ * Handles the x25519 key exchange handshake and ChaCha20-Poly1305
+ * encryption/decryption of every message after it. Exposes typed protocol
+ * methods (`ServerMethod` / `ClientMethod`) rather than raw WebSocket
+ * messages. Has no concept of channels, messages, or app state —
+ * `EnclaveServer` builds on top of this to add those.
*/
export default class EnclaveWebSocket {
public websocket: WebSocket;
onOpenQueue: Array<() => void>;
- public constructor(hostname: string) {
+ private sendCounter = 0n;
+ private sharedSecret: Uint8Array | null = null;
+
+ private readonly handshakeReady: Promise