diff options
Diffstat (limited to 'javascript')
-rw-r--r-- | javascript/.gitignore | 4 | ||||
-rw-r--r-- | javascript/README.md | 19 | ||||
-rwxr-xr-x | javascript/build.py | 72 | ||||
-rw-r--r-- | javascript/demo/demo.css | 8 | ||||
-rw-r--r-- | javascript/demo/group_demo.html | 61 | ||||
-rw-r--r-- | javascript/demo/group_demo.js | 492 | ||||
-rw-r--r-- | javascript/demo/one_to_one_demo.html (renamed from javascript/demo.html) | 3 | ||||
-rw-r--r-- | javascript/olm_inbound_group_session.js | 103 | ||||
-rw-r--r-- | javascript/olm_outbound_group_session.js | 110 | ||||
-rw-r--r-- | javascript/olm_post.js | 72 | ||||
-rw-r--r-- | javascript/olm_pre.js | 2 | ||||
-rw-r--r-- | javascript/package.json | 9 |
12 files changed, 859 insertions, 96 deletions
diff --git a/javascript/.gitignore b/javascript/.gitignore new file mode 100644 index 0000000..603fe7c --- /dev/null +++ b/javascript/.gitignore @@ -0,0 +1,4 @@ +/exported_functions.json +/node_modules +/npm-debug.log +/olm.js diff --git a/javascript/README.md b/javascript/README.md index 6902e36..6ed9bbb 100644 --- a/javascript/README.md +++ b/javascript/README.md @@ -22,4 +22,21 @@ Example: bob_session.create_inbound(bob, bob_message); var plaintext = bob_session.decrypt(message_1.type, bob_message); - bob.remove_one_time_keys(bob_session);
\ No newline at end of file + bob.remove_one_time_keys(bob_session); + + +Group chat: + + var outbound_session = new Olm.OutboundGroupSession(); + outbound_session.create(); + + // exchange these over a secure channel + var session_id = group_session.session_id(); + var session_key = group_session.session_key(); + var message_index = group_session.message_index(); + + var inbound_session = new Olm.InboundGroupSession(); + inbound_session.create(message_index, session_key); + + var ciphertext = outbound_session.encrypt("Hello"); + var plaintext = inbound_session.decrypt(ciphertext); diff --git a/javascript/build.py b/javascript/build.py deleted file mode 100755 index 9766906..0000000 --- a/javascript/build.py +++ /dev/null @@ -1,72 +0,0 @@ -#! /usr/bin/python -# Copyright 2015 OpenMarket Ltd -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -import subprocess -import glob -import os -import sys -import re -import json - -source_files = glob.glob("src/*.cpp") -pre_js, = glob.glob("javascript/*pre.js") -post_js, = glob.glob("javascript/*post.js") - -if not os.path.exists("build"): - os.mkdir("build") - -functions = set() -RE_FUNCTION=re.compile("(olm_[^( ]*)\\(") -with open("include/olm/olm.hh") as header: - for line in header: - match = RE_FUNCTION.search(line) - if match: - functions.add(match.groups()[0]) - - -exported_functions = os.path.abspath("build/exported_functions.json") -with open(exported_functions, "w") as json_file: - json.dump(["_" + function for function in functions], json_file) - - -emcc = os.environ.get("EMCC", "emcc") - -compile_args = [emcc] -compile_args += """ - -O3 - -Iinclude - -Ilib - -std=c++11 - --closure 1 - --memory-init-file 0 - -s NO_FILESYSTEM=1 - -s NO_BROWSER=1 - -s INVOKE_RUN=0 -""".split() -compile_args += source_files -compile_args += ("--pre-js", pre_js) -compile_args += ("--post-js", post_js) -compile_args += ("-s", "EXPORTED_FUNCTIONS=@" + exported_functions) -compile_args += sys.argv[1:] - -library = "build/olm.js" - -def run(args): - print args - print " ".join(args) - subprocess.check_call(args) - -run(compile_args + ["-o", library]) - diff --git a/javascript/demo/demo.css b/javascript/demo/demo.css new file mode 100644 index 0000000..bf07df1 --- /dev/null +++ b/javascript/demo/demo.css @@ -0,0 +1,8 @@ +div.user { + width: 500px; + float: left; + overflow: scroll; + margin: 0px 20px 0px 0px; + border: 1px solid black; + padding: 5px; +}
\ No newline at end of file diff --git a/javascript/demo/group_demo.html b/javascript/demo/group_demo.html new file mode 100644 index 0000000..31aacb0 --- /dev/null +++ b/javascript/demo/group_demo.html @@ -0,0 +1,61 @@ +<html> + <head> + <link rel="stylesheet" type="text/css" href="demo.css"/> + <script src="../olm.js"></script> + <script src="group_demo.js"></script> + </head> +<body> +<div id="user1" class="user"> + <h1>User1</h1> + + <textarea class="user_plain_input"></textarea> + <button class="user_encrypt">Encrypt</button> + + <h2>Outgoing</h2> + + <h3>One-to-one output</h3> + <div class="user_cipher_output"></div> + + <h3>Group output</h3> + <div class="group_output"></div> + + <h2>Incoming</h2> + + <h3>One-to-one Received</h3> + <div class="user_cipher_input"></div> + + <h3>Group received</h3> + <div class="group_input"></div> + + <h2>Tasks</h2> + <div class="user_progress"></div> +</div> + +<div id="user2" class="user"> + <h1>User 2</h1> + + <textarea class="user_plain_input"></textarea> + <button class="user_encrypt">Encrypt</button> + + <h2>Outgoing</h2> + + <h3>One-to-one output</h3> + <div class="user_cipher_output"></div> + + <h3>Group output</h3> + <div class="group_output"></div> + + <h2>Incoming</h2> + + <h3>One-to-one Received</h3> + <div class="user_cipher_input"></div> + + <h3>Group received</h3> + <div class="group_input"></div> + + <h2>Tasks</h2> + <div class="user_progress"></div> +</div> + +</body> +</html> diff --git a/javascript/demo/group_demo.js b/javascript/demo/group_demo.js new file mode 100644 index 0000000..1b8f7ab --- /dev/null +++ b/javascript/demo/group_demo.js @@ -0,0 +1,492 @@ +/* Javascript parts of the group demo. To use, load group_demo.html in your + * browser. + */ + +function buttonElement(buttonLabel, clickHandler) { + var button = document.createElement("button"); + button.appendChild(document.createTextNode(buttonLabel)); + button.addEventListener("click", clickHandler, false); + return button; +} + +function buttonsAndText(textContent, buttonLabelToHandlerMap) { + var el = document.createElement("div"); + for (var label in buttonLabelToHandlerMap) { + if (!buttonLabelToHandlerMap.hasOwnProperty(label)) { + continue; + } + var handler = buttonLabelToHandlerMap[label]; + var button = buttonElement(label, handler); + el.appendChild(button); + } + + var message_element = document.createElement("tt"); + el.appendChild(message_element); + + var content = document.createTextNode(textContent); + message_element.appendChild(content); + + return el; +} + +function buttonAndTextElement(buttonLabel, textContent, clickHandler) { + var buttonMap = {}; + buttonMap[buttonLabel] = clickHandler; + return buttonsAndText(textContent, buttonMap); +} + +function DemoUser(name) { + this.name = name; + this.olmAccount = new Olm.Account(); + this.olmAccount.create(); + + this.idKey = this.getIdKeys()["curve25519"]; + + /* the people in our chat, indexed by their Curve25519 identity key. + */ + this.peers = {}; + + /* the Ed25519 signing key for each peer, indexed by their Curve25519 id key + */ + this.peerSigningKeys = {}; + + /* for each peer, a one-to-one session - indexed by id key and created on + * demand */ + this.peerSessions = {}; + + /* for each peer, info on their sender session - indexed by id key and + * session id */ + this.peerGroupSessions = {}; + + /* our outbound group session */ + this.groupSession = undefined; + + /* a list of pending tasks */ + this.tasks = []; + this.taskWorker = undefined; + + /* the operations our peers are allowed to do on us */ + var publicOps = [ + "getIdKeys", "getOneTimeKey", + "receiveOneToOne", "receiveGroup", + ]; + + this.remoteOps = {}; + for (var i=0; i<publicOps.length; i++) { + var op = publicOps[i]; + this.remoteOps[op] = this[op].bind(this); + } +} + +DemoUser.prototype._progress = function(message) { + var progress = this.progressElement; + + var message_element = document.createElement("pre"); + var start_content = document.createTextNode(message + "..."); + function start() { + message_element.appendChild(start_content); + progress.appendChild(message_element); + } + function done(res) { + var done_content = document.createTextNode(message + "..." + res); + message_element.replaceChild(done_content, start_content); + } + return {start:start, done:done}; +}; + +DemoUser.prototype._do_tasks = function() { + var self = this; + var task = self.tasks.shift(); + var desc = task[0]; + var func = task[1]; + var callback = task[2]; + + var p = self._progress(desc); + p.start(); + + function done() { + p.done("Done"); + + if (callback) { + try { + callback.apply(undefined, arguments) + } catch (e) { + console.error("Uncaught exception in callback", e.stack || e); + } + } + + start_tasks(); + } + + // sleep 50ms before actually doing the task + self.taskWorker = window.setTimeout(function() { + try { + task[1](done); + } catch (e) { + console.error("Uncaught exception in task", e.stack || e); + p.done("Failed: "+e); + start_tasks(); + } + }, 50); + + + function start_tasks() { + if (self.tasks.length == 0) { + self.taskWorker = undefined; + return; + } + + self.taskWorker = window.setTimeout(self._do_tasks.bind(self), 50); + } +} + +/** + * add a function "task" to this user's queue of things to do. + * + * task is called with a single argument 'done' which is a function to call + * once the task is complete. + * + * 'callback' is called once the task is complete, with any arguments that + * were passed to 'done'. + */ +DemoUser.prototype.addTask = function(description, task, callback) { + this.tasks.push([description, task, callback]); + if(!this.taskWorker) { + this._do_tasks(); + } +}; + +DemoUser.prototype.addPeer = function(peerOps) { + var keys = peerOps.getIdKeys(); + var id = keys["curve25519"]; + this.peers[id] = peerOps; + this.peerSigningKeys[id] = keys["ed25519"]; +}; + +DemoUser.prototype.getIdKeys = function() { + return JSON.parse(this.olmAccount.identity_keys()); +}; + +DemoUser.prototype.generateKeys = function(callback) { + var self = this; + this.addTask("generate one time key", function(done) { + self.olmAccount.generate_one_time_keys(1); + done(); + }, callback); +}; + +DemoUser.prototype.getOneTimeKey = function() { + var self = this; + var keys = JSON.parse(self.olmAccount.one_time_keys()) + .curve25519; + for (key_id in keys) { + if (keys.hasOwnProperty(key_id)) { + return keys[key_id]; + } + } + throw new Error("No one-time-keys generated"); +}; + +/* ************************************************************************ + * + * one-to-one messaging + */ + +/** + * retrieve, or initiate, a one-to-one session to a given peer + */ +DemoUser.prototype.getPeerSession = function(peerId, callback) { + var self = this; + + if (this.peerSessions[peerId]) { + callback(this.peerSessions[peerId]); + return; + } + + var peer = this.peers[peerId]; + this.addTask("get peer keys", function(done) { + key = peer.getOneTimeKey(); + done(key); + }, function(ot_key) { + self.addTask("create peer session", function(done) { + var session = new Olm.Session(); + session.create_outbound(self.olmAccount, peerId, ot_key); + self.peerSessions[peerId] = session; + done(session); + }, callback); + }); +}; + +/** + * encrypt a one-to-one message and prepare it for sending to a peer + */ +DemoUser.prototype.sendToPeer = function(peerId, message, callback) { + var self = this; + this.getPeerSession(peerId, function(session) { + self.addTask("encrypt one-to-one message", function(done) { + var encrypted = session.encrypt(message); + var packet = { + sender_key: self.idKey, + ciphertext: encrypted, + }; + var json = JSON.stringify(packet); + + var el = buttonAndTextElement("send", json, function(ev) { + self.peers[peerId].receiveOneToOne(json); + }); + self.cipherOutputDiv.appendChild(el); + done(); + }, callback); + }); +}; + +/** + * handler for receiving a one-to-one message + */ +DemoUser.prototype.receiveOneToOne = function(jsonpacket) { + var self = this; + var el = buttonAndTextElement("decrypt", jsonpacket, function(ev) { + var sender = JSON.parse(jsonpacket).sender_key; + self.decryptOneToOne(jsonpacket, function(result) { + + var el2 = document.createElement("tt"); + el.appendChild(el2); + + var content = document.createTextNode(" -> "+result); + el2.appendChild(content); + + var body = JSON.parse(result); + + // create a new inbound session if we don't yet have one + if (!self.peerGroupSessions[sender] || + !self.peerGroupSessions[sender][body.session_id]) { + self.createInboundSession( + sender, body.session_id, body.session_key + ); + } + }); + }); + this.cipherInputDiv.appendChild(el); +}; + +/** + * add a task to decrypt a one-to-one message. Calls the callback with the + * decrypted plaintext + */ +DemoUser.prototype.decryptOneToOne = function(jsonpacket, callback) { + var self = this; + self.addTask("decrypt one-to-one message", function(done) { + var packet = JSON.parse(jsonpacket); + var peerId = packet.sender_key; + + var session = self.peerSessions[peerId]; + var plaintext; + if (session) { + plaintext = session.decrypt(packet.ciphertext.type, packet.ciphertext.body); + done(plaintext); + return; + } + + if (packet.ciphertext.type != 0) { + throw new Error("Unknown one-to-one session"); + } + + session = new Olm.Session(); + session.create_inbound(self.olmAccount, packet.ciphertext.body); + self.peerSessions[peerId] = session; + plaintext = session.decrypt(packet.ciphertext.type, packet.ciphertext.body); + done(plaintext); + }, callback) +}; + +/* ************************************************************************ + * + * group messaging + */ + + +/** + * retrieve, or initiate, an outbound group session + */ +DemoUser.prototype.getGroupSession = function() { + if (this.groupSession) { + return this.groupSession; + } + + this.groupSession = new Olm.OutboundGroupSession(); + this.groupSession.create(); + + var keymsg = { + "session_id": this.groupSession.session_id(), + "session_key": this.groupSession.session_key(), + "message_index": this.groupSession.message_index(), + }; + var jsonmsg = JSON.stringify(keymsg); + + for (var peer in this.peers) { + if (!this.peers.hasOwnProperty(peer)) { + continue; + } + this.sendToPeer(peer, jsonmsg); + } + + return this.groupSession; +}; + +/** + * add a task to create an inbound group session + */ +DemoUser.prototype.createInboundSession = function( + peer_id, session_id, session_key, callback +) { + var self = this; + this.addTask("init inbound session", function(done) { + session = new Olm.InboundGroupSession(); + session.create(session_key); + if (!self.peerGroupSessions[peer_id]) { + self.peerGroupSessions[peer_id] = {}; + } + if (session_id != session.session_id()) { + throw new Error("Mismatched session_ids"); + } + self.peerGroupSessions[peer_id][session_id] = session; + done(session); + }, callback); +}; + +/** + * handler for receiving a group message + */ +DemoUser.prototype.receiveGroup = function(jsonpacket) { + var self = this; + var el = buttonAndTextElement("decrypt", jsonpacket, function(ev) { + self.decryptGroup(jsonpacket, function(result) { + var el2 = document.createElement("tt"); + el.appendChild(el2); + + var content = document.createTextNode(" -> "+result); + el2.appendChild(content); + }); + }); + this.groupInputDiv.appendChild(el); +}; + +/** + * add a task to decrypt a received group message. Calls the callback with the + * decrypted plaintext + */ +DemoUser.prototype.decryptGroup = function(jsonpacket, callback) { + var self = this; + this.addTask("decrypt group message", function(done) { + var packet = JSON.parse(jsonpacket); + + var sender = packet.sender_key; + var session_id = packet.session_id; + + var sender_signing_key = self.peerSigningKeys[sender]; + if (!sender_signing_key) { + throw new Error("No known signing key for sender "+sender); + } + + var olmUtility = new Olm.Utility(); + olmUtility.ed25519_verify( + sender_signing_key, packet.body, packet.signature + ); + + var peer_sessions = self.peerGroupSessions[sender]; + if (!peer_sessions) { + throw new Error("No sessions for sender "+sender); + } + + var session = peer_sessions[session_id]; + if (!session) { + throw new Error("Unknown session id " + session_id); + } + + var plaintext = session.decrypt(packet.body); + done(plaintext); + }, callback); +}; + + + +/** + * add a task to encrypt, and prepare for sending, a group message. + * + * Will create a group session if necessary + */ +DemoUser.prototype.encrypt = function(message) { + var self = this; + var session = this.getGroupSession(); + + function sendJsonToPeers(json) { + for (var peer in self.peers) { + if (!self.peers.hasOwnProperty(peer)) { + continue; + } + self.peers[peer].receiveGroup(json); + } + } + + + self.addTask("encrypt group message", function(done) { + var encrypted = session.encrypt(message); + var signature = self.olmAccount.sign(encrypted); + + var packet = { + sender_key: self.idKey, + session_id: session.session_id(), + body: encrypted, + signature: signature, + }; + var json = JSON.stringify(packet); + + var el = buttonsAndText(json, { + send: function(ev) { + sendJsonToPeers(json); + }, + "send corrupted": function(ev) { + var p = JSON.parse(json); + p.body += " "; + sendJsonToPeers(JSON.stringify(p)); + }, + }); + self.groupOutputDiv.appendChild(el); + done(); + }); +}; + + +/* ************************************************************************** */ + +function initUserDiv(demoUser, div) { + demoUser.progressElement = div.getElementsByClassName("user_progress")[0]; + demoUser.cipherOutputDiv = div.getElementsByClassName("user_cipher_output")[0]; + demoUser.cipherInputDiv = div.getElementsByClassName("user_cipher_input")[0]; + demoUser.groupOutputDiv = div.getElementsByClassName("group_output")[0]; + demoUser.groupInputDiv = div.getElementsByClassName("group_input")[0]; + + var plain_input = div.getElementsByClassName("user_plain_input")[0]; + var encrypt = div.getElementsByClassName("user_encrypt")[0]; + + encrypt.addEventListener("click", function() { + demoUser.encrypt(plain_input.value); + }, false); + +} + +function startDemo() { + var user1 = new DemoUser(); + initUserDiv(user1, document.getElementById("user1")); + user1.generateKeys(); + + var user2 = new DemoUser(); + initUserDiv(user2, document.getElementById("user2")); + user2.generateKeys(); + + user1.addPeer(user2.remoteOps); + user2.addPeer(user1.remoteOps); +} + + +document.addEventListener("DOMContentLoaded", startDemo, false); diff --git a/javascript/demo.html b/javascript/demo/one_to_one_demo.html index 20e780b..fc7e654 100644 --- a/javascript/demo.html +++ b/javascript/demo/one_to_one_demo.html @@ -1,8 +1,7 @@ <html> <head> -<script src="../build/olm.js"></script> +<script src="../olm.js"></script> <script> - document.addEventListener("DOMContentLoaded", function (event) { function progress(who, message) { var message_element = document.createElement("pre"); diff --git a/javascript/olm_inbound_group_session.js b/javascript/olm_inbound_group_session.js new file mode 100644 index 0000000..6058233 --- /dev/null +++ b/javascript/olm_inbound_group_session.js @@ -0,0 +1,103 @@ +/* The 'length' argument to Pointer_stringify doesn't work if the input includes + * characters >= 128; we therefore need to add a NULL character to all of our + * strings. This acts as a symbolic constant to help show what we're doing. + */ +var NULL_BYTE_PADDING_LENGTH = 1; + +function InboundGroupSession() { + var size = Module['_olm_inbound_group_session_size'](); + this.buf = malloc(size); + this.ptr = Module['_olm_inbound_group_session'](this.buf); +} + +function inbound_group_session_method(wrapped) { + return function() { + var result = wrapped.apply(this, arguments); + if (result === OLM_ERROR) { + var message = Pointer_stringify( + Module['_olm_inbound_group_session_last_error'](arguments[0]) + ); + throw new Error("OLM." + message); + } + return result; + } +} + +InboundGroupSession.prototype['free'] = function() { + Module['_olm_clear_inbound_group_session'](this.ptr); + free(this.ptr); +} + +InboundGroupSession.prototype['pickle'] = restore_stack(function(key) { + var key_array = array_from_string(key); + var pickle_length = inbound_group_session_method( + Module['_olm_pickle_inbound_group_session_length'] + )(this.ptr); + var key_buffer = stack(key_array); + var pickle_buffer = stack(pickle_length + NULL_BYTE_PADDING_LENGTH); + inbound_group_session_method(Module['_olm_pickle_inbound_group_session'])( + this.ptr, key_buffer, key_array.length, pickle_buffer, pickle_length + ); + return Pointer_stringify(pickle_buffer); +}); + +InboundGroupSession.prototype['unpickle'] = restore_stack(function(key, pickle) { + var key_array = array_from_string(key); + var key_buffer = stack(key_array); + var pickle_array = array_from_string(pickle); + var pickle_buffer = stack(pickle_array); + inbound_group_session_method(Module['_olm_unpickle_inbound_group_session'])( + this.ptr, key_buffer, key_array.length, pickle_buffer, + pickle_array.length + ); +}); + +InboundGroupSession.prototype['create'] = restore_stack(function(session_key) { + var key_array = array_from_string(session_key); + var key_buffer = stack(key_array); + + inbound_group_session_method(Module['_olm_init_inbound_group_session'])( + this.ptr, key_buffer, key_array.length + ); +}); + +InboundGroupSession.prototype['decrypt'] = restore_stack(function( + message +) { + var message_array = array_from_string(message); + var message_buffer = stack(message_array); + var max_plaintext_length = inbound_group_session_method( + Module['_olm_group_decrypt_max_plaintext_length'] + )(this.ptr, message_buffer, message_array.length); + // caculating the length destroys the input buffer. + // So we copy the array to a new buffer + var message_buffer = stack(message_array); + var plaintext_buffer = stack(max_plaintext_length + NULL_BYTE_PADDING_LENGTH); + var plaintext_length = inbound_group_session_method(Module["_olm_group_decrypt"])( + this.ptr, + message_buffer, message_array.length, + plaintext_buffer, max_plaintext_length + ); + + // Pointer_stringify requires a null-terminated argument (the optional + // 'len' argument doesn't work for UTF-8 data). + Module['setValue']( + plaintext_buffer+plaintext_length, + 0, "i8" + ); + + return Pointer_stringify(plaintext_buffer); +}); + +InboundGroupSession.prototype['session_id'] = restore_stack(function() { + var length = inbound_group_session_method( + Module['_olm_inbound_group_session_id_length'] + )(this.ptr); + var session_id = stack(length + NULL_BYTE_PADDING_LENGTH); + inbound_group_session_method(Module['_olm_inbound_group_session_id'])( + this.ptr, session_id, length + ); + return Pointer_stringify(session_id); +}); + +olm_exports['InboundGroupSession'] = InboundGroupSession; diff --git a/javascript/olm_outbound_group_session.js b/javascript/olm_outbound_group_session.js new file mode 100644 index 0000000..88a441d --- /dev/null +++ b/javascript/olm_outbound_group_session.js @@ -0,0 +1,110 @@ +/* The 'length' argument to Pointer_stringify doesn't work if the input includes + * characters >= 128; we therefore need to add a NULL character to all of our + * strings. This acts as a symbolic constant to help show what we're doing. + */ +var NULL_BYTE_PADDING_LENGTH = 1; + + +function OutboundGroupSession() { + var size = Module['_olm_outbound_group_session_size'](); + this.buf = malloc(size); + this.ptr = Module['_olm_outbound_group_session'](this.buf); +} + +function outbound_group_session_method(wrapped) { + return function() { + var result = wrapped.apply(this, arguments); + if (result === OLM_ERROR) { + var message = Pointer_stringify( + Module['_olm_outbound_group_session_last_error'](arguments[0]) + ); + throw new Error("OLM." + message); + } + return result; + } +} + +OutboundGroupSession.prototype['free'] = function() { + Module['_olm_clear_outbound_group_session'](this.ptr); + free(this.ptr); +} + +OutboundGroupSession.prototype['pickle'] = restore_stack(function(key) { + var key_array = array_from_string(key); + var pickle_length = outbound_group_session_method( + Module['_olm_pickle_outbound_group_session_length'] + )(this.ptr); + var key_buffer = stack(key_array); + var pickle_buffer = stack(pickle_length + NULL_BYTE_PADDING_LENGTH); + outbound_group_session_method(Module['_olm_pickle_outbound_group_session'])( + this.ptr, key_buffer, key_array.length, pickle_buffer, pickle_length + ); + return Pointer_stringify(pickle_buffer); +}); + +OutboundGroupSession.prototype['unpickle'] = restore_stack(function(key, pickle) { + var key_array = array_from_string(key); + var key_buffer = stack(key_array); + var pickle_array = array_from_string(pickle); + var pickle_buffer = stack(pickle_array); + outbound_group_session_method(Module['_olm_unpickle_outbound_group_session'])( + this.ptr, key_buffer, key_array.length, pickle_buffer, + pickle_array.length + ); +}); + +OutboundGroupSession.prototype['create'] = restore_stack(function() { + var random_length = outbound_group_session_method( + Module['_olm_init_outbound_group_session_random_length'] + )(this.ptr); + var random = random_stack(random_length); + outbound_group_session_method(Module['_olm_init_outbound_group_session'])( + this.ptr, random, random_length + ); +}); + +OutboundGroupSession.prototype['encrypt'] = restore_stack(function(plaintext) { + var plaintext_array = array_from_string(plaintext); + var message_length = outbound_group_session_method( + Module['_olm_group_encrypt_message_length'] + )(this.ptr, plaintext_array.length); + var plaintext_buffer = stack(plaintext_array); + var message_buffer = stack(message_length + NULL_BYTE_PADDING_LENGTH); + outbound_group_session_method(Module['_olm_group_encrypt'])( + this.ptr, + plaintext_buffer, plaintext_array.length, + message_buffer, message_length + ); + return Pointer_stringify(message_buffer); +}); + +OutboundGroupSession.prototype['session_id'] = restore_stack(function() { + var length = outbound_group_session_method( + Module['_olm_outbound_group_session_id_length'] + )(this.ptr); + var session_id = stack(length + NULL_BYTE_PADDING_LENGTH); + outbound_group_session_method(Module['_olm_outbound_group_session_id'])( + this.ptr, session_id, length + ); + return Pointer_stringify(session_id); +}); + +OutboundGroupSession.prototype['session_key'] = restore_stack(function() { + var key_length = outbound_group_session_method( + Module['_olm_outbound_group_session_key_length'] + )(this.ptr); + var key = stack(key_length + NULL_BYTE_PADDING_LENGTH); + outbound_group_session_method(Module['_olm_outbound_group_session_key'])( + this.ptr, key, key_length + ); + return Pointer_stringify(key); +}); + +OutboundGroupSession.prototype['message_index'] = function() { + var idx = outbound_group_session_method( + Module['_olm_outbound_group_session_message_index'] + )(this.ptr); + return idx; +}; + +olm_exports['OutboundGroupSession'] = OutboundGroupSession; diff --git a/javascript/olm_post.js b/javascript/olm_post.js index dbb4862..8951c11 100644 --- a/javascript/olm_post.js +++ b/javascript/olm_post.js @@ -4,6 +4,16 @@ var free = Module['_free']; var Pointer_stringify = Module['Pointer_stringify']; var OLM_ERROR = Module['_olm_error'](); +/* The 'length' argument to Pointer_stringify doesn't work if the input includes + * characters >= 128; we therefore need to add a NULL character to all of our + * strings. This acts as a symbolic constant to help show what we're doing. + */ +var NULL_BYTE_PADDING_LENGTH = 1; + +/* allocate a number of bytes of storage on the stack. + * + * If size_or_array is a Number, allocates that number of zero-initialised bytes. + */ function stack(size_or_array) { return Module['allocate'](size_or_array, 'i8', Module['ALLOC_STACK']); } @@ -68,11 +78,11 @@ Account.prototype['identity_keys'] = restore_stack(function() { var keys_length = account_method( Module['_olm_account_identity_keys_length'] )(this.ptr); - var keys = stack(keys_length); + var keys = stack(keys_length + NULL_BYTE_PADDING_LENGTH); account_method(Module['_olm_account_identity_keys'])( this.ptr, keys, keys_length ); - return Pointer_stringify(keys, keys_length); + return Pointer_stringify(keys); }); Account.prototype['sign'] = restore_stack(function(message) { @@ -81,24 +91,24 @@ Account.prototype['sign'] = restore_stack(function(message) { )(this.ptr); var message_array = array_from_string(message); var message_buffer = stack(message_array); - var signature_buffer = stack(signature_length); + var signature_buffer = stack(signature_length + NULL_BYTE_PADDING_LENGTH); account_method(Module['_olm_account_sign'])( this.ptr, message_buffer, message_array.length, signature_buffer, signature_length ); - return Pointer_stringify(signature_buffer, signature_length); + return Pointer_stringify(signature_buffer); }); Account.prototype['one_time_keys'] = restore_stack(function() { var keys_length = account_method( Module['_olm_account_one_time_keys_length'] )(this.ptr); - var keys = stack(keys_length); + var keys = stack(keys_length + NULL_BYTE_PADDING_LENGTH); account_method(Module['_olm_account_one_time_keys'])( this.ptr, keys, keys_length ); - return Pointer_stringify(keys, keys_length); + return Pointer_stringify(keys); }); Account.prototype['mark_keys_as_published'] = restore_stack(function() { @@ -135,11 +145,11 @@ Account.prototype['pickle'] = restore_stack(function(key) { Module['_olm_pickle_account_length'] )(this.ptr); var key_buffer = stack(key_array); - var pickle_buffer = stack(pickle_length); + var pickle_buffer = stack(pickle_length + NULL_BYTE_PADDING_LENGTH); account_method(Module['_olm_pickle_account'])( this.ptr, key_buffer, key_array.length, pickle_buffer, pickle_length ); - return Pointer_stringify(pickle_buffer, pickle_length); + return Pointer_stringify(pickle_buffer); }); Account.prototype['unpickle'] = restore_stack(function(key, pickle) { @@ -183,11 +193,11 @@ Session.prototype['pickle'] = restore_stack(function(key) { Module['_olm_pickle_session_length'] )(this.ptr); var key_buffer = stack(key_array); - var pickle_buffer = stack(pickle_length); + var pickle_buffer = stack(pickle_length + NULL_BYTE_PADDING_LENGTH); session_method(Module['_olm_pickle_session'])( this.ptr, key_buffer, key_array.length, pickle_buffer, pickle_length ); - return Pointer_stringify(pickle_buffer, pickle_length); + return Pointer_stringify(pickle_buffer); }); Session.prototype['unpickle'] = restore_stack(function(key, pickle) { @@ -246,13 +256,20 @@ Session.prototype['create_inbound_from'] = restore_stack(function( Session.prototype['session_id'] = restore_stack(function() { var id_length = session_method(Module['_olm_session_id_length'])(this.ptr); - var id_buffer = stack(id_length); + var id_buffer = stack(id_length + NULL_BYTE_PADDING_LENGTH); session_method(Module['_olm_session_id'])( this.ptr, id_buffer, id_length ); - return Pointer_stringify(id_buffer, id_length); + return Pointer_stringify(id_buffer); }); +Session.prototype['has_received_message'] = function() { + return session_method(Module['_olm_session_has_received_message'])( + this.ptr + ) ? true : false; +}; + + Session.prototype['matches_inbound'] = restore_stack(function( one_time_key_message ) { @@ -292,7 +309,7 @@ Session.prototype['encrypt'] = restore_stack(function( )(this.ptr, plaintext_array.length); var random = random_stack(random_length); var plaintext_buffer = stack(plaintext_array); - var message_buffer = stack(message_length); + var message_buffer = stack(message_length + NULL_BYTE_PADDING_LENGTH); session_method(Module['_olm_encrypt'])( this.ptr, plaintext_buffer, plaintext_array.length, @@ -301,7 +318,7 @@ Session.prototype['encrypt'] = restore_stack(function( ); return { "type": message_type, - "body": Pointer_stringify(message_buffer, message_length) + "body": Pointer_stringify(message_buffer) }; }); @@ -316,13 +333,23 @@ Session.prototype['decrypt'] = restore_stack(function( // caculating the length destroys the input buffer. // So we copy the array to a new buffer var message_buffer = stack(message_array); - var plaintext_buffer = stack(max_plaintext_length); + var plaintext_buffer = stack( + max_plaintext_length + NULL_BYTE_PADDING_LENGTH + ); var plaintext_length = session_method(Module["_olm_decrypt"])( this.ptr, message_type, message_buffer, message.length, plaintext_buffer, max_plaintext_length ); - return Pointer_stringify(plaintext_buffer, plaintext_length); + + // Pointer_stringify requires a null-terminated argument (the optional + // 'len' argument doesn't work for UTF-8 data). + Module['setValue']( + plaintext_buffer+plaintext_length, + 0, "i8" + ); + + return Pointer_stringify(plaintext_buffer); }); function Utility() { @@ -353,13 +380,13 @@ Utility.prototype['sha256'] = restore_stack(function(input) { var output_length = utility_method(Module['_olm_sha256_length'])(this.ptr); var input_array = array_from_string(input); var input_buffer = stack(input_array); - var output_buffer = stack(output_length); + var output_buffer = stack(output_length + NULL_BYTE_PADDING_LENGTH); utility_method(Module['_olm_sha2516'])( this.ptr, input_buffer, input_array.length(), output_buffer, output_length ); - return Pointer_stringify(output_buffer, output_length); + return Pointer_stringify(output_buffer); }); Utility.prototype['ed25519_verify'] = restore_stack(function( @@ -383,4 +410,13 @@ olm_exports["Account"] = Account; olm_exports["Session"] = Session; olm_exports["Utility"] = Utility; +olm_exports["get_library_version"] = restore_stack(function() { + var buf = stack(3); + Module['_olm_get_library_version'](buf, buf+1, buf+2); + return [ + getValue(buf, 'i8'), + getValue(buf+1, 'i8'), + getValue(buf+2, 'i8'), + ]; +}); }(); diff --git a/javascript/olm_pre.js b/javascript/olm_pre.js index 7706687..50bf8c2 100644 --- a/javascript/olm_pre.js +++ b/javascript/olm_pre.js @@ -2,7 +2,7 @@ var olm_exports = {}; var get_random_values; var process; // Shadow the process object so that emscripten won't get // confused by browserify -if (global && global["window"]) { +if (typeof(global) !== 'undefined' && global["window"]) { // We're running with browserify module["exports"] = olm_exports; global["window"]["Olm"] = olm_exports; diff --git a/javascript/package.json b/javascript/package.json index efcdaa5..df43ce1 100644 --- a/javascript/package.json +++ b/javascript/package.json @@ -1,9 +1,14 @@ { "name": "olm", - "version": "0.1.0", - "description": "An implementation of a well known cryptographic ratchet", + "version": "1.3.0", + "description": "An implementation of the Double Ratchet cryptographic ratchet", "main": "olm.js", + "files": [ + "olm.js", + "README.md" + ], "scripts": { + "build": "make -C .. js", "test": "echo \"Error: no test specified\" && exit 1" }, "repository": { |