diff --git a/static/js/modules/crypto/main.js b/static/js/modules/crypto/main.js index c748d40..02f666b 100644 --- a/static/js/modules/crypto/main.js +++ b/static/js/modules/crypto/main.js @@ -1,4 +1,4 @@ import { generate_keypair } from "./paillier.js"; -import { generate_rsa_keypair } from "./rsa.js"; +import { generate_rsa_keypair, RsaPubKey } from "./rsa.js"; -export { generate_keypair, generate_rsa_keypair }; +export { generate_keypair, generate_rsa_keypair, RsaPubKey }; diff --git a/static/js/modules/crypto/rsa.js b/static/js/modules/crypto/rsa.js index 5828d17..1094dae 100644 --- a/static/js/modules/crypto/rsa.js +++ b/static/js/modules/crypto/rsa.js @@ -3,18 +3,29 @@ import { mod_exp, mod_inv } from "./math.js"; let p, q, pubKey, privKey; -class PubKey { - constructor(p, q) { - this.n = p * q; - this.e = 65537n; +export class RsaPubKey { + constructor(n, e) { + this.n = n; + this.e = e; } encrypt(m) { return mod_exp(m, this.e, this.n); } + + toJSON() { + return { + n: "0x" + this.n.toString(16), + e: "0x" + this.e.toString(16), + }; + } + + static fromJSON(data) { + return new RsaPubKey(BigInt(data.n), BigInt(data.e)); + } } -class PrivKey { +class RsaPrivKey { constructor(p, q) { this.n = p * q; this.d = mod_inv(65537n, (q - 1n) * (p - 1n)); @@ -40,8 +51,8 @@ export function generate_rsa_keypair() { q = BigInt(window.sessionStorage.getItem("rsa_q")); } - pubKey = new PubKey(p, q); - privKey = new PrivKey(p, q); + pubKey = new RsaPubKey(p * q, 65537n); + privKey = new RsaPrivKey(p, q); return { pubKey, privKey }; } diff --git a/static/js/modules/interface/game.js b/static/js/modules/interface/game.js index 93919a6..3357022 100644 --- a/static/js/modules/interface/game.js +++ b/static/js/modules/interface/game.js @@ -36,11 +36,11 @@ export class Game { return Object.values(this.players).filter((p) => p.isPlaying)[0]; } - addPlayer(id, is_us) { + addPlayer(id, is_us, pubkey) { let is_new = this.players[id] === undefined; if (this.isWaiting()) { - this.players[id] = new Player(id, is_us); + this.players[id] = new Player(id, is_us, pubkey); if (is_us) { this.us = this.players[id]; } diff --git a/static/js/modules/interface/main.js b/static/js/modules/interface/main.js index 62d092c..7a79a07 100644 --- a/static/js/modules/interface/main.js +++ b/static/js/modules/interface/main.js @@ -26,14 +26,29 @@ document.addEventListener("DOMContentLoaded", () => { socket.on("connect", () => { window.console.log("Connected!"); window.console.log(`We are: ${ID}`); - socket.emit("message", Packet.createAnnounce()); - game.addPlayer(ID, true); + socket.emit("message", Packet.createAnnounce(window.rsa)); + game.addPlayer(ID, true, window.rsa.pubKey); }); - socket.on("message", async (data) => { + socket.on("message", async (packet) => { + let data = packet.payload; window.console.log(data); - // todo validate signature + const sender = game.players[data.author]; + if (sender === undefined) { + window.console.warn( + `Not able to verify signature on packet ${data.id} as the sender is unknown.` + ); + } else { + let sig = BigInt(packet.sig); + // decrypt and compare signature + let dehash = sender.rsaPubKey.encrypt(sig).toString(16); + let hash = CryptoJS.SHA3(JSON.stringify(data)).toString(); + if (dehash !== hash) { + window.console.error(`Signature invalid! Ignoring packet ${data.id}.`); + return; + } + } switch (data.type) { case "RANDOM": @@ -67,11 +82,11 @@ document.addEventListener("ANNOUNCE", (ev) => { const data = ev.detail; if (data.author === ID) return; - let is_new = game.addPlayer(data.author, false); + let is_new = game.addPlayer(data.author, false, data.rsaPubKey); // When a new player is seen, all announce to ensure they know all players. if (is_new) { - socket.emit("message", Packet.createAnnounce()); + socket.emit("message", Packet.createAnnounce(window.rsa)); } }); diff --git a/static/js/modules/interface/packet.js b/static/js/modules/interface/packet.js index 6aa2c22..8fa4734 100644 --- a/static/js/modules/interface/packet.js +++ b/static/js/modules/interface/packet.js @@ -9,30 +9,51 @@ export class Packet { }; } - static createAnnounce() { + static _sign(data) { + // compute hash of data + let hash = CryptoJS.SHA3(JSON.stringify(data)); + let res = 0n; + for (let word32 of hash.words) { + // remove any sign + let bi = BigInt.asUintN(32, BigInt(word32)); + res = (res << 32n) | bi; + } + + // sign hash + let sig = window.rsa.privKey.decrypt(res); + + // return new packet return { - ...this._createBase("ANNOUNCE"), + sig: "0x" + sig.toString(16), + payload: data, }; } + static createAnnounce(rsaKeyPair) { + return this._sign({ + ...this._createBase("ANNOUNCE"), + rsaPubKey: rsaKeyPair.pubKey.toJSON(), + }); + } + static createDisconnect() { - return this._createBase("DISCONNECT"); + return this._sign(this._createBase("DISCONNECT")); } static createKeepAlive() { - return this._createBase("KEEPALIVE"); + return this._sign(this._createBase("KEEPALIVE")); } static createSetReady(nowReady) { - return { + return this._sign({ ...this._createBase("ACT"), action: "READY", ready: nowReady, - }; + }); } static createBarrierSignal() { - return this._createBase("BARRIER"); + return this._sign(this._createBase("BARRIER")); } static createRegionClaim(region) { @@ -43,27 +64,27 @@ export class Packet { } static createAction(action, startRegion, endRegion, amount) { - return { + return this._sign({ ...this._createBase("ACT"), startRegion: startRegion, endRegion: endRegion, strength: amount, action: action, - }; + }); } static createDefense(amount) { - return { + return this._sign({ ...this._createBase("ACT"), action: "DEFENSE", amount: amount, - }; + }); } static createEndTurn() { - return { + return this._sign({ ...this._createBase("ACT"), action: "END", - }; + }); } } diff --git a/static/js/modules/interface/player.js b/static/js/modules/interface/player.js index c9db5ea..01d4dbc 100644 --- a/static/js/modules/interface/player.js +++ b/static/js/modules/interface/player.js @@ -1,5 +1,6 @@ import { Packet } from "./packet.js"; import { socket } from "./main.js"; +import { RsaPubKey } from "../crypto/rsa.js"; // Timeout to consider a player disconnected const TIMEOUT = 30_000; @@ -11,10 +12,11 @@ const PHASE_FORTIFY = 3; let totalDice = 0; export class Player { - constructor(id, local) { + constructor(id, local, pubkey) { this.timeout = null; this.id = id; this.ready = false; + this.rsaPubKey = RsaPubKey.fromJSON(pubkey); // Data which is reset every turn this.isPlaying = false;