Skip to content
Snippets Groups Projects
Commit 5508945a authored by Greg Fodor's avatar Greg Fodor
Browse files

WIP crypto

parent ee912fa2
No related branches found
No related tags found
No related merge requests found
// NOTE: We do not use an IV since we generate a new keypair each time we use these routines.
async function deriveKey(privateKey, publicKey) {
return crypto.subtle.deriveKey(
{ name: "ECDH", public: publicKey },
privateKey,
{ name: "AES-CBC", length: 256 },
true,
["encrypt", "decrypt"]
);
}
async function publicKeyToString(key) {
return JSON.stringify(await crypto.subtle.exportKey("jwk", key));
}
async function stringToPublicKey(s) {
return await crypto.subtle.importKey("jwk", JSON.parse(s), { name: "ECDH", namedCurve: "P-256" }, true, [
"deriveKey"
]);
}
function stringToArrayBuffer(s) {
const buf = new Uint8Array(s.length);
for (let i = 0; i < s.length; i++) {
buf[i] = s.charCodeAt(i);
}
return buf;
}
function arrayBufferToString(buf) {
let s = "";
for (let i = 0; i < buf.byteLength; i++) {
s += String.fromCharCode(buf[i]);
}
return s;
}
// This allows a single object to be passed encrypted from a receiver in a req -> response flow
// Requestor generates a public key and private key, and should send the public key to receiver.
export async function generateKeys() {
const keyPair = await crypto.subtle.generateKey({ name: "ECDH", namedCurve: "P-256" }, true, ["deriveKey"]);
const publicKeyString = await publicKeyToString(keyPair.publicKey);
return { publicKeyString, privateKey: keyPair.privateKey };
}
// Receiver takes the public key from requestor and passes obj to get a response public key and the encrypted data to return.
export async function generatePublicKeyAndEncryptedObject(incomingPublicKeyString, obj) {
const iv = new Uint8Array(16);
const incomingPublicKey = await stringToPublicKey(incomingPublicKeyString);
const keyPair = await crypto.subtle.generateKey({ name: "ECDH", namedCurve: "P-256" }, true, ["deriveKey"]);
const publicKeyString = await publicKeyToString(keyPair.publicKey);
const secret = await deriveKey(keyPair.privateKey, incomingPublicKey);
const encryptedData = btoa(
await crypto.subtle.encrypt({ name: "AES-CBC", iv }, secret, stringToArrayBuffer(JSON.stringify(obj)))
);
return { publicKeyString, encryptedData };
}
// Requestor then takes the receiver's public key, the private key (returned from generateKeys()), and the data from the receiver.
export async function decryptObject(publicKeyString, privateKey, base64value) {
const iv = new Uint8Array(16);
const publicKey = await publicKeyToString(publicKeyString);
const secret = await deriveKey(privateKey, publicKey);
return JSON.parse(
arrayBufferToString(
new Uint8Array(
await crypto.subtle.decrypt({ name: "AES-CBC", iv }, secret, stringToArrayBuffer(atob(base64value)))
)
)
);
}
import { generatePublicKeyAndEncryptedObject, generateKeys, decryptObject } from "./crypto";
export default class XferChannel { export default class XferChannel {
constructor(store) { constructor(store) {
this.store = store; this.store = store;
...@@ -46,17 +48,27 @@ export default class XferChannel { ...@@ -46,17 +48,27 @@ export default class XferChannel {
channel.on("xfer_request", incoming => { channel.on("xfer_request", incoming => {
if (readyToSend) { if (readyToSend) {
const payload = { path: location.pathname, target_session_id: incoming.reply_to_session_id }; const data = { path: location.pathname };
// Copy profile data to xfer'ed device if it's been set. // Copy profile data to xfer'ed device if it's been set.
if (this.store.state.activity.hasChangedName) { if (this.store.state.activity.hasChangedName) {
payload.profile = { ...this.store.state.profile }; data.profile = { ...this.store.state.profile };
} }
channel.push("xfer_response", payload); this.generatePublicKeyAndEncryptedObject(incoming.public_key).then(
channel.leave(); ({ publicKeyString, encryptedData }) => {
finished("used"); const payload = {
readyToSend = false; target_session_id: incoming.reply_to_session_id,
public_key: publicKeyString,
data: encryptedData
};
channel.push("xfer_response", payload);
channel.leave();
finished("used");
readyToSend = false;
}
);
} }
}); });
...@@ -77,34 +89,40 @@ export default class XferChannel { ...@@ -77,34 +89,40 @@ export default class XferChannel {
const channel = this.socket.channel(`xfer:${code}`, { timeout: 10000 }); const channel = this.socket.channel(`xfer:${code}`, { timeout: 10000 });
let finished = false; let finished = false;
channel.on("presence_state", state => { generateKeys().then(({ publicKeyString, privateKey }) => {
const numOccupants = Object.keys(state).length; channel.on("presence_state", state => {
const numOccupants = Object.keys(state).length;
if (numOccupants === 1) {
// Great, only sender is in topic, request xfer
channel.push("xfer_request", { reply_to_session_id: this.socket.params.session_id });
setTimeout(() => {
if (finished) return;
channel.leave();
reject("no_response");
}, 10000);
} else if (numOccupants === 0) {
// Nobody in this channel, probably a bad code.
reject("failed");
} else {
console.warn("xfer code channel already has 2 or more occupants, something fishy is going on.");
reject("in_use");
}
});
channel.on("xfer_response", payload => { if (numOccupants === 1) {
finished = true; // Great, only sender is in topic, request xfer
channel.leave(); channel.push("xfer_request", {
resolve(payload); reply_to_session_id: this.socket.params.session_id,
}); public_key: publicKeyString
});
channel.join().receive("error", r => console.error(r)); setTimeout(() => {
if (finished) return;
channel.leave();
reject("no_response");
}, 10000);
} else if (numOccupants === 0) {
// Nobody in this channel, probably a bad code.
reject("failed");
} else {
console.warn("xfer code channel already has 2 or more occupants, something fishy is going on.");
reject("in_use");
}
});
channel.on("xfer_response", payload => {
finished = true;
channel.leave();
this.decryptObject(payload.public_key, privateKey, payload.data).then(resolve);
});
channel.join().receive("error", r => console.error(r));
});
}); });
}; };
} }
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment