aboutsummaryrefslogtreecommitdiff
path: root/javascript
diff options
context:
space:
mode:
Diffstat (limited to 'javascript')
-rw-r--r--javascript/.gitignore4
-rw-r--r--javascript/README.md19
-rwxr-xr-xjavascript/build.py72
-rw-r--r--javascript/demo/demo.css8
-rw-r--r--javascript/demo/group_demo.html61
-rw-r--r--javascript/demo/group_demo.js492
-rw-r--r--javascript/demo/one_to_one_demo.html (renamed from javascript/demo.html)3
-rw-r--r--javascript/olm_inbound_group_session.js103
-rw-r--r--javascript/olm_outbound_group_session.js110
-rw-r--r--javascript/olm_post.js72
-rw-r--r--javascript/olm_pre.js2
-rw-r--r--javascript/package.json9
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": {