diff options
-rw-r--r-- | client-web/source/index.ts | 6 | ||||
-rw-r--r-- | client-web/source/logger.ts | 14 | ||||
-rw-r--r-- | client-web/source/protocol/crypto.ts | 80 | ||||
-rw-r--r-- | client-web/source/protocol/mod.ts | 15 | ||||
-rw-r--r-- | client-web/source/room.ts | 36 | ||||
-rw-r--r-- | common/packets.d.ts | 5 |
6 files changed, 116 insertions, 40 deletions
diff --git a/client-web/source/index.ts b/client-web/source/index.ts index 670cb46..21bf4b8 100644 --- a/client-web/source/index.ts +++ b/client-web/source/index.ts @@ -2,6 +2,7 @@ import { log } from "./logger.ts" import { create_menu } from "./menu.ts"; +import { SignalingConnection } from "./protocol/mod.ts"; import { Room } from "./room.ts" export const servers: RTCConfiguration = { @@ -17,11 +18,12 @@ export interface User { window.onload = () => main() -export function main() { +export async function main() { document.body.querySelector("p")?.remove() log("*", "starting up") const room_name = window.location.pathname.substring("/".length) - const room = new Room(room_name) + const conn = await (new SignalingConnection().connect(room_name)) + const room = new Room(conn) create_menu() document.body.append(room.el) } diff --git a/client-web/source/logger.ts b/client-web/source/logger.ts index e00b1d0..952355e 100644 --- a/client-web/source/logger.ts +++ b/client-web/source/logger.ts @@ -1,14 +1,16 @@ /// <reference lib="dom" /> const log_tag_color = { - "*": "#FF4444", - webrtc: "#FF44FF", - media: "#FFFF44", - ws: "#44FFFF", - rnnoise: "#2222FF", - usermodel: "#44FF44", + "*": "#ff4a7c", + crypto: "#c14aff", + webrtc: "#ff4ade", + ws: "#544aff", + media: "#4af5ff", + rnnoise: "#4aff7e", + usermodel: "#a6ff4a", error: "#FF0000", } + export type LogTag = keyof typeof log_tag_color let logger_container: HTMLDivElement diff --git a/client-web/source/protocol/crypto.ts b/client-web/source/protocol/crypto.ts new file mode 100644 index 0000000..6cd2ba3 --- /dev/null +++ b/client-web/source/protocol/crypto.ts @@ -0,0 +1,80 @@ +import { log } from "../logger.ts"; + +//! I am not a crypto expert at all! Please read carefully and report any issues to me. + +export async function crypto_seeded_key(seed: string): Promise<CryptoKey> { + log("crypto", "importing seed…") + const seed_key = await window.crypto.subtle.importKey( + "raw", + new TextEncoder().encode(seed), + "PBKDF2", + false, + ["deriveKey"] + ) + //? TODO is it possible to use a unique seed per session here? + // const salt = window.crypto.getRandomValues(new Uint8Array(16)); + const salt = base64_to_buf("thisisagoodsaltAAAAAAA==") // valid "unique" 16-byte base-64 string + log("crypto", "deriving key…") + const key = await window.crypto.subtle.deriveKey( + { + name: "PBKDF2", + salt, + iterations: 250000, + hash: "SHA-256", + }, + seed_key, + { name: "AES-GCM", length: 256 }, + false, + ["encrypt", "decrypt"] + ) + console.log(key); + log("crypto", "ready") + return key +} + +export async function crypto_encrypt(key: CryptoKey, data: string): Promise<string> { + const iv = window.crypto.getRandomValues(new Uint8Array(12)); + const ciphertext = new Uint8Array(await window.crypto.subtle.encrypt( + { name: "AES-GCM", iv }, + key, + new TextEncoder().encode(data) + )); + const buf = new Uint8Array(iv.byteLength + ciphertext.byteLength); + buf.set(iv, 0); + buf.set(ciphertext, iv.byteLength); + const b64 = buf_to_base64(buf); + return b64; +} + +export async function crypt_decrypt(key: CryptoKey, data: string): Promise<string> { + const buf = base64_to_buf(data); + const iv = buf.slice(0, 12); + const ciphertext = buf.slice(12); + const decryptedContent = await window.crypto.subtle.decrypt( + { name: "AES-GCM", iv }, + key, + ciphertext + ); + const plain = new TextDecoder().decode(decryptedContent); + return plain +} + +// const buf_to_base64 = (buf: Uint8Array) => btoa(String.fromCharCode.apply(null, buf)); +// const base64_to_buf = (b64: string) => Uint8Array.from(atob(b64), (c) => c.charCodeAt(0)); + +export function base64_to_buf(data: string): Uint8Array { + const binary_string = globalThis.atob(data); + const bytes = new Uint8Array(binary_string.length); + for (let i = 0; i < binary_string.length; i++) { + bytes[i] = binary_string.charCodeAt(i); + } + return bytes; +} + +export function buf_to_base64(bytes: Uint8Array): string { + let binary = ''; + for (let i = 0; i < bytes.byteLength; i++) { + binary += String.fromCharCode(bytes[i]); + } + return globalThis.btoa(binary); +}
\ No newline at end of file diff --git a/client-web/source/protocol/mod.ts b/client-web/source/protocol/mod.ts index 76b1290..f86e96f 100644 --- a/client-web/source/protocol/mod.ts +++ b/client-web/source/protocol/mod.ts @@ -1,6 +1,17 @@ +import { crypto_seeded_key } from "./crypto.ts" export class SignalingConnection { - constructor(room: string) { - + room!: string + websocket!: WebSocket + signaling_id!: string + key!: CryptoKey + + constructor() { } + async connect(room: string): Promise<SignalingConnection> { + this.key = await crypto_seeded_key(room) + const ws_url = new URL(`${window.location.protocol.endsWith("s:") ? "wss" : "ws"}://${window.location.host}/signaling/${encodeURIComponent(this.signaling_id)}`) + this.websocket = new WebSocket(ws_url) + await new Promise(r => this.websocket!.onopen = r) + return this } } diff --git a/client-web/source/room.ts b/client-web/source/room.ts index 78454bb..6c9a7ba 100644 --- a/client-web/source/room.ts +++ b/client-web/source/room.ts @@ -5,43 +5,23 @@ import { RemoteUser } from "./remote_user.ts"; import { User } from "./user.ts"; import { LocalUser } from "./local_user.ts"; import { ServerboundPacket, ClientboundPacket } from "../../common/packets.d.ts"; -import { PREFS } from "./preferences.ts"; -import { ep } from "./helper.ts"; +import { SignalingConnection } from "./protocol/mod.ts"; export class Room { el: HTMLElement - name: string users: Map<number, User> = new Map() remote_users: Map<number, RemoteUser> = new Map() local_user!: LocalUser my_id!: number - websocket: WebSocket - constructor(name: string) { - this.name = name + constructor(public signaling: SignalingConnection) { this.el = document.createElement("div") this.el.classList.add("room") - - const ws_url = new URL(`${window.location.protocol.endsWith("s:") ? "wss" : "ws"}://${window.location.host}/${encodeURIComponent(name)}/signaling`) - ws_url.searchParams.set("username", PREFS.username) - this.websocket = new WebSocket(ws_url) - this.websocket.onclose = () => this.websocket_close() - - // const connecting_text = ep("Upgrading to a websocket connection…") - // this.el.append(connecting_text) - - this.websocket.onopen = () => { - // connecting_text.remove() - this.websocket_open() - } - this.websocket.onmessage = (ev) => { - this.websocket_message(JSON.parse(ev.data)) - } } websocket_send(data: ServerboundPacket) { log("ws", `-> ${data.relay?.recipient ?? "*"}`, data) - this.websocket.send(JSON.stringify(data)) + // this.websocket.send(JSON.stringify(data)) } websocket_message(packet: ClientboundPacket) { log("ws", `<- ${packet.message?.sender ?? "control packet"}: `, packet); @@ -51,7 +31,7 @@ export class Room { log("*", `server: ${packet.init.version}`) } else if (packet.client_join) { const p = packet.client_join - log("*", `${this.name} ${p.id} joined`); + log("*", `${p.id} joined`); if (p.id == this.my_id) { this.local_user = new LocalUser(this, p.id, p.name); } else { @@ -63,7 +43,7 @@ export class Room { } } else if (packet.client_leave) { const p = packet.client_leave; - log("*", `${this.name} ${p.id} left`); + log("*", `${p.id} left`); this.remote_users.get(p.id)!.leave() this.users.delete(p.id) this.remote_users.delete(p.id) @@ -72,9 +52,9 @@ export class Room { const p = packet.message; const sender = this.users.get(p.sender) if (sender instanceof RemoteUser) { - if (p.message.ice_candidate) sender.add_ice_candidate(p.message.ice_candidate) - if (p.message.offer) sender.on_offer(p.message.offer) - if (p.message.answer) sender.on_answer(p.message.answer) + // if (p.message.ice_candidate) sender.add_ice_candidate(p.message.ice_candidate) + // if (p.message.offer) sender.on_offer(p.message.offer) + // if (p.message.answer) sender.on_answer(p.message.answer) } else { console.log("!", p, sender); } diff --git a/common/packets.d.ts b/common/packets.d.ts index 6ee4b9a..49ab295 100644 --- a/common/packets.d.ts +++ b/common/packets.d.ts @@ -8,15 +8,16 @@ export interface ClientboundPacket { init?: { your_id: number, version: string }, client_join?: { id: number, name: string }, client_leave?: { id: number }, - message?: { sender: number, message: RelayMessage }, + message?: { sender: number, message: string }, } export interface ServerboundPacket { ping?: null, - relay?: { recipient?: number, message: RelayMessage }, + relay?: { recipient?: number, message: string }, } export interface RelayMessage { + identify?: { name: string } offer?: F_RTCSessionDescriptionInit, answer?: F_RTCSessionDescriptionInit, ice_candidate?: F_RTCIceCandidateInit, |