diff --git a/javascript/axolotl_post.js b/javascript/axolotl_post.js new file mode 100644 index 0000000..7902725 --- /dev/null +++ b/javascript/axolotl_post.js @@ -0,0 +1,247 @@ +var runtime = Module['Runtime']; +var malloc = Module['_malloc']; +var free = Module['_free']; +var Pointer_stringify = Module['Pointer_stringify']; +var AXOLOTL_ERROR = Module['_axolotl_error'](); + +function stack(size_or_array) { + return Module['allocate'](size_or_array, 'i8', Module['ALLOC_STACK']); +} + +function array_from_string(string) { + return Module['intArrayFromString'](string, true); +} + +function random_stack(size) { + var ptr = stack(size); + var array = new Uint8Array(Module['HEAPU8'].buffer, ptr, size); + window.crypto.getRandomValues(array); +} + +function restore_stack(wrapped) { + return function() { + var sp = runtime.stackSave(); + try { + return wrapped.apply(this, arguments); + } finally { + runtime.stackRestore(sp); + } + } +} + +function Account() { + var size = Module['_axolotl_account_size'](); + this.buf = malloc(size); + this.ptr = Module['_axolotl_account'](this.buf); +} + +function account_method(wrapped) { + return function() { + var result = wrapped.apply(this, arguments); + if (result === AXOLOTL_ERROR) { + var message = Pointer_stringify( + Module['_axolotl_account_last_error'](arguments[0]) + ); + throw "AXOLOTL." + message; + } + return result; + } +} + +Account.prototype['free'] = function() { + free(this.ptr); +} + +Account.prototype['create'] = restore_stack(function() { + var random_length = account_method( + Module['_axolotl_create_account_random_length'] + )(this.ptr); + var random = random_stack(random_length); + account_method(Module['_axolotl_create_account'])( + this.ptr, random, random_length + ); +}); + +Account.prototype['identity_keys'] = restore_stack(function() { + var keys_length = account_method( + Module['_axolotl_account_identity_keys_length'] + )(this.ptr); + var keys = stack(keys_length); + account_method(Module['_axolotl_account_identity_keys'])( + this.ptr, keys, keys_length + ); + return Pointer_stringify(keys, keys_length); +}); + +Account.prototype['one_time_keys'] = restore_stack(function() { + var keys_length = account_method( + Module['_axolotl_account_one_time_keys_length'] + )(this.ptr); + var keys = stack(keys_length); + account_method(Module['_axolotl_account_one_time_keys'])( + this.ptr, keys, keys_length + ); + return Pointer_stringify(keys, keys_length); +}); + +Account.prototype['pickle'] = restore_stack(function(key) { + var key_array = array_from_string(key); + var pickle_length = account_method( + Module['_axolotl_pickle_account_length'] + )(this.ptr); + var key_buffer = stack(key_array); + var pickle_buffer = stack(pickle_length); + account_method(Module['_axolotl_pickle_account'])( + this.ptr, key_buffer, key_array.length, pickle_buffer, pickle_length + ); + return Pointer_stringify(pickle_buffer, pickle_length); +}); + +Account.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_length); + account_method(Module['_axolotl_unpickle_account'])( + this.ptr, key_buffer, key_array.length, pickle_buffer, + pickle_array.length + ); +}); + +function Session() { + var size = Module['_axolotl_session_size'](); + this.buf = malloc(size); + this.ptr = Module['_axolotl_session'](this.buf); +} + +function session_method(wrapped) { + return function() { + var result = wrapped.apply(this, arguments); + if (result === AXOLOTL_ERROR) { + var message = Pointer_stringify( + Module['_axolotl_session_last_error'](arguments[0]) + ); + throw "AXOLOTL." + message; + } + return result; + } +} + +Session.prototype['free'] = function() { + free(this.ptr); +} + +Session.prototype['pickle'] = restore_stack(function(key) { + var key_array = array_from_string(key); + var pickle_length = session_method( + Module['_axolotl_pickle_session_length'] + )(this.ptr); + var key_buffer = stack(key_array); + var pickle_buffer = stack(pickle_length); + session_method(Module['_axolotl_pickle_session'])( + this.ptr, key_buffer, key_array.length, pickle_buffer, pickle_length + ); + return Pointer_stringify(pickle_buffer, pickle_length); +}); + +Session.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); + session_method(Module['_axolotl_unpickle_session'])( + this.ptr, key_buffer, key_array.length, pickle_buffer, + pickle_array.length + ); +}); + +Session.prototype['create_outbound'] = restore_stack(function( + account, their_identity_key, their_one_time_key_id, their_one_time_key +) { + var random_length = session_method( + Module['_axolotl_create_outbound_session_random_length'] + )(this.ptr); + var random = random_stack(random_length); + var identity_key_array = array_from_string(their_identity_key); + var one_time_key_array = array_from_string(their_one_time_key); + var identity_key_buffer = stack(identity_key_array); + var one_time_key_buffer = stack(one_time_key_array); + session_method(Module['_axolotl_create_outbound_session'])( + this.ptr, account.ptr, + identity_key_buffer, identity_key_array.length, + their_one_time_key_id, + one_time_key_buffer, one_time_key_array.length, + random, random_length + ); +}); + +Session.prototype['create_inbound'] = restore_stack(function( + account, one_time_key_message +) { + var message_array = array_from_string(one_time_key_message); + var message_buffer = stack(message_array); + session_method(Module['_axolotl_create_inbound_session'])( + this.ptr, account.ptr, message_buffer, message_array.length + ); +}); + +Session.prototype['matches_inbound'] = restore_stack(function( + account, one_time_key_message +) { + var message_array = array_from_string(one_time_key_message); + var message_buffer = stack(message_array); + return session_method(Module['_axolotl_matches_inbound_session'])( + this.ptr, account.ptr, message_buffer, message_array.length + ) ? true : false; +}); + +Session.prototype['encrypt'] = restore_stack(function( + plaintext +) { + var random_length = session_method( + Module['_axolotl_encrypt_random_length'] + )(this.ptr); + var message_type = session_method( + Module['_axolotl_encrypt_message_type'] + )(this.ptr); + var plaintext_array = array_from_string(plaintext); + var message_length = session_method( + Module['_axolotl_encrypt_message_length'] + )(this.ptr, plaintext_array.length); + var random = random_stack(random_length); + var plaintext_buffer = stack(plaintext_array); + var message_buffer = stack(message_length); + session_method(Module['_axolotl_encrypt'])( + this.ptr, + plaintext_buffer, plaintext_array.length, + random, random_length, + message_buffer, message_length + ); + return { + "type": message_type, + "body": Pointer_stringify(message_buffer, message_length) + }; +}); + +Session.prototype['decrypt'] = restore_stack(function( + message_type, message +) { + var message_array = array_from_string(message); + var message_buffer = stack(message_array); + var max_plaintext_length = session_method( + Module['_axolotl_decrypt_max_plaintext_length'] + )(this.ptr, message_type, 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); + var plaintext_length = session_method(Module["_axolotl_decrypt"])( + this.ptr, message_type, + message_buffer, message.length, + plaintext_buffer, max_plaintext_length + ); + return Pointer_stringify(plaintext_buffer, plaintext_length); +}); + +return {"Account": Account, "Session": Session}; +}(); diff --git a/javascript/axolotl_pre.js b/javascript/axolotl_pre.js new file mode 100644 index 0000000..69df923 --- /dev/null +++ b/javascript/axolotl_pre.js @@ -0,0 +1 @@ +Axolotl = function() { diff --git a/javascript/build.py b/javascript/build.py new file mode 100644 index 0000000..2b754e9 --- /dev/null +++ b/javascript/build.py @@ -0,0 +1,69 @@ +#! /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") + + +functions = set() +RE_FUNCTION=re.compile("(axolotl_[^( ]*)\\(") +with open("include/axolotl/axolotl.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) + +library = "build/axolotl.js" + +def run(args): + print args + print " ".join(args) + subprocess.check_call(args) + +run(compile_args + ["-o", library]) +