From: Michael D. Lowis Date: Fri, 20 Aug 2021 01:31:34 +0000 (+0000) Subject: echo back of encrypted messages is working. Import of public keys from file is not... X-Git-Url: https://git.mdlowis.com/?a=commitdiff_plain;h=1ab59cba305620bb153779c7ff11deb776a3ff6c;p=proto%2Fachat.git echo back of encrypted messages is working. Import of public keys from file is not... --- diff --git a/static/client.js b/static/client.js index 21cae8f..c7406c9 100644 --- a/static/client.js +++ b/static/client.js @@ -36,124 +36,139 @@ const Data = { clr: () => localStorage.clear(), }; - - /* Cryptographic API */ const Crypto = { api: window.crypto.subtle, - stoa: (str)=>{ - const buf = new Uint8Array(new ArrayBuffer(str.length)); - for (let i = 0; i < str.length; i++) { - bufView[i] = str.charCodeAt(i); - } - return buf; + keys: { public: {} }, + + sign_alg: { + name: "RSA-PSS", + modulusLength: 4096, + publicExponent: new Uint8Array([1, 0, 1]), + hash: "SHA-256", + saltLength: 32 + }, + + encr_alg: { + name: "RSA-OAEP", + modulusLength: 4096, + publicExponent: new Uint8Array([1, 0, 1]), + hash: "SHA-256", }, - atos: (array)=> String.fromCharCode.apply(null, new Uint8Array(array)), - - generate: ()=> Crypto.api.generateKey( - { - name: "RSA-OAEP", - modulusLength: 4096, - publicExponent: new Uint8Array([1, 0, 1]), - hash: "SHA-256", - }, - true, - ["encrypt", "decrypt", "sign", "verify"] - ), - - import: (key)=>{ - const binkey = window.atob(key); - const keybuf = Crypto.stoa(binkey); - return Crypto.api.importKey( - "pkcs8", - keybuf, - { - name: "RSA-OAEP", - modulusLength: 4096, - publicExponent: new Uint8Array([1, 0, 1]), - hash: "SHA-256", + init: async ()=>{ + const keys = Data.get("keys"); + if (keys) { + Crypto.keys.sign = { + privateKey: await Crypto.import(keys.sign_priv, Crypto.sign_alg, ["sign"]), + publicKey: await Crypto.import(keys.sign_pub, Crypto.sign_alg, ["verify"]), }, - true, - ["encrypt", "decrypt", "sign", "verify"] - ); + Crypto.keys.encr = { + privateKey: await Crypto.import(keys.encr_priv, Crypto.encr_alg, ["decrypt"]), + publicKey: await Crypto.import(keys.encr_pub, Crypto.encr_alg, ["encrypt"]), + } + } else { + await Crypto.generate(); + } + await Crypto.importPublicKeys("myself", await Crypto.publicKeys()); + }, + + generate: async ()=>{ + Crypto.keys.sign = await Crypto.api.generateKey(Crypto.sign_alg, true, ["sign", "verify"]), + Crypto.keys.encr = await Crypto.api.generateKey(Crypto.encr_alg, true, ["encrypt", "decrypt"]), + Data.put("keys", { + sign_priv: await Crypto.export(Crypto.keys.sign.privateKey), + sign_pub: await Crypto.export(Crypto.keys.sign.publicKey), + encr_priv: await Crypto.export(Crypto.keys.encr.privateKey), + encr_pub: await Crypto.export(Crypto.keys.encr.publicKey), + }) }, - export: (key)=>{ - const expkey = await Crypto.api.exportKey("pkcs8", key); - const strkey = Crypto.atos(expkey); - return window.btoa(strkey); + publicKeys: async ()=> + Crypto.encodeObject({ + encrypt: await Crypto.export(Crypto.keys.encr.publicKey), + verify: await Crypto.export(Crypto.keys.sign.publicKey), + }), + + importPublicKeys: async (name, key)=>{ + const keys = await Crypto.decodeObject(key); + keys.encrypt = await Crypto.import(keys.encrypt, Crypto.encr_alg, ["encrypt"]); + keys.verify = await Crypto.import(keys.verify, Crypto.sign_alg, ["verify"]); + Crypto.keys.public[name] ||= []; + Crypto.keys.public[name].push(keys); + Data.put(name + "/keys", key); }, + + encodePacket: async (msg, key)=>{ + const packet = { data: new TextEncoder().encode(JSON.stringify(msg)) }; + packet.sign = await Crypto.api.sign(Crypto.sign_alg, Crypto.keys.sign.privateKey, packet.data); + packet.data = await Crypto.api.encrypt(Crypto.encr_alg, key, packet.data); + packet.data = Crypto.encodeBuffer(new Uint8Array(packet.data)); + packet.sign = Crypto.encodeBuffer(new Uint8Array(packet.sign)); + return Crypto.encodeObject(packet); + }, + + decodePacket: async (packet)=>{ + packet = Crypto.decodeObject(packet); + packet.data = Crypto.decodeBuffer(packet.data); + packet.sign = Crypto.decodeBuffer(packet.sign); + packet.data = await Crypto.api.decrypt(Crypto.encr_alg, Crypto.keys.encr.privateKey, packet.data); + packet.msg = JSON.parse(new TextDecoder().decode(packet.data)); + return packet; + }, + + verifyPacket: (packet, sign_key)=> + Crypto.api.verify(Crypto.sign_alg, sign_key, packet.sign, packet.data), + + export: async (key)=> + Crypto.encodeObject(await Crypto.api.exportKey("jwk", key)), + + import: (key, alg, uses)=> + Crypto.api.importKey("jwk", Crypto.decodeObject(key), alg, true, uses), + + encodeBuffer: (buf)=> + btoa(String.fromCharCode(...buf)), + + decodeBuffer: (str)=> + Uint8Array.from(atob(str), c => c.charCodeAt(0)), + + encodeObject: (data)=> + btoa(JSON.stringify(data)), + + decodeObject: (data)=> + JSON.parse(atob(data)), } /* Chat Client Interface */ const Client = ((state = {keys: {}, logs: {}})=>({ - init: ()=>{ - /* scan storage for all keys and logs */ - for (var i = 0; i < Data.len(); i++) { - const name = Data.key(i); - const key = name.match(/(.*)\/key$/) - const log = name.match(/(.*)\/log\/(\d)$/) - if (key) { - state.keys[key[1]] = Client.loadKey(key[1], Data.get(name)); - } else if (log) { - state.logs[log[1]] ||= []; - state.logs[log[1]][Number(log[2])] = Data.get(name); - } - } - - /* connect using keys from storage */ - if (Data.get('keys/priv') && Data.get('keys/pub')) - { - Client.privKey(Data.get('keys/priv')); - Client.pubKey(Data.get('keys/pub')); - Client.connect(); - } - - // TODO: Populate HTML with active contact's log + init: async ()=>{ + await Crypto.init(); + Client.log("Public Key: " + await Crypto.publicKeys()); + Client.connect(); }, log: (msg)=>{ - const element = document.createElement('DIV'); - element.textContent += msg + '\n'; - messages.appendChild(element); + messages.appendChild(L("DIV", {}, msg)); messages.scrollTop = messages.scrollHeight; }, send: ()=>{ - if (message.value.length > 0) { - const key = state.keys[contact.value]; - if (key) { - Client.log("me: " + message.value); - const msg = key.encrypt(message.value); - state.conn.send(msg); - message.value = ""; - } + const keys = Crypto.keys.public[contact.value]; + if (message.value.length > 0 && keys) { + const msg = { date: new Date(), text: message.value } + keys.forEach(async (key) => { + Client.log("me: " + message.value); + state.conn.send(await Crypto.encodePacket(msg, key.encrypt)); + }); + message.value = ""; } }, - loadKey: (name, key)=>{ - Data.put(name, key); - const crypt = new JSEncrypt(); - crypt.setKey(key); - return crypt; - }, - - privKey: (key)=>{ - state.keys.private = Client.loadKey('keys/priv', key); - }, - - pubKey: (key)=>{ - state.keys.public = Client.loadKey('keys/pub', key); - }, - - keysValid: ()=> state.keys.private.decrypt(state.keys.public.encrypt("test")), - getFile: (file) => fetch(file).then((resp)=>{ if (!resp.ok) { throw "failed to fetch key" }; return resp.text(); @@ -161,28 +176,37 @@ const Client = ((state = {keys: {}, logs: {}})=>({ addContact: (name)=>{ Client.getFile('./keys/' + name).then((key)=>{ - state.keys[name] = Client.loadKey(name, key); + key.split("\n").forEach(async (key)=>{ + await Crypto.importPublicKeys(name, key.split(" ").slice(-1)[0]); + }) contact.appendChild(L('OPTION', { value: name }, name)); }); }, getMessage: (id)=>{ - Client.getFile('./msgs/' + id).then((msg)=>{ - const text = state.keys.private.decrypt(msg); - if (text) { Client.log("them: " + text); } + Client.getFile('./msgs/' + id).then(async (msg)=>{ + const packet = await Crypto.decodePacket(msg); + for (let [name, keys] of Object.entries(Crypto.keys.public)) { + keys.forEach(async (key) => { + if (await Crypto.verifyPacket(packet, key.verify)) { + Client.log(name + ": " + packet.msg.text); + } + }); + } }); }, connect: ()=>{ - state.conn = new WebSocket('ws://localhost:8080/'); + state.conn = new WebSocket("wss://" + window.location.host); state.conn.onopen = ()=>{ Client.log('CONNECT'); + dialog.classList.remove("visible"); }; state.conn.onclose = ()=>{ Client.log('DISCONNECT'); -// dialog.classList.add("visible"); + dialog.classList.add("visible"); }; state.conn.onmessage = (event)=>{ @@ -206,22 +230,8 @@ message.onkeyup = (ev)=>{ } }; -connect.onclick = async ()=>{ - let pubKey = await public_key.files[0].text(); - let privKey = await private_key.files[0].text(); - Client.pubKey(pubKey); - Client.privKey(privKey); - if (Client.keysValid()) - { - dialog.classList.remove("visible"); - Client.connect(); - } -} - addContact.onclick = ()=>{ - const name = prompt("Enter name:", ""); - Client.addContact(name); + Client.addContact(contactName.value); }; -// Try connecting based on local storage -(()=>{ Client.init(); })(); +(async ()=>{ Client.init(); })(); diff --git a/static/index.html b/static/index.html index 4d6a809..6879463 100644 --- a/static/index.html +++ b/static/index.html @@ -11,9 +11,10 @@
- +
@@ -28,19 +29,7 @@