From ee8172d882e853e737ac7e8b00fb760f21e80bfe Mon Sep 17 00:00:00 2001 From: Richard van der Hoff Date: Wed, 25 May 2016 17:08:44 +0100 Subject: [PATCH 1/8] Compile some of the crypto libs directly sha256.c and aes.c contain conflicting declarations, so we need to compile them as separate units. This requires a bit more Makefile-shuffling; the build directory now includes 'src' or 'lib' as appropriate, and we just mkdir -p before each compilation. --- Makefile | 75 ++++++++++++++++++++------------------- src/{libs.c => ed25519.c} | 3 -- 2 files changed, 38 insertions(+), 40 deletions(-) rename src/{libs.c => ed25519.c} (87%) diff --git a/Makefile b/Makefile index fca9576..49bec6a 100644 --- a/Makefile +++ b/Makefile @@ -17,16 +17,22 @@ JS_EXPORTED_FUNCTIONS := javascript/exported_functions.json PUBLIC_HEADERS := include/olm/olm.h -SOURCES := $(wildcard src/*.cpp) $(wildcard src/*.c) -RELEASE_OBJECTS := $(patsubst src/%,$(BUILD_DIR)/release/%,$(patsubst %.c,%.o,$(patsubst %.cpp,%.o,$(SOURCES)))) -DEBUG_OBJECTS := $(patsubst src/%,$(BUILD_DIR)/debug/%,$(patsubst %.c,%.o,$(patsubst %.cpp,%.o,$(SOURCES)))) -FUZZER_OBJECTS := $(patsubst src/%,$(BUILD_DIR)/fuzzers/objects/%,$(patsubst %.c,%.o,$(patsubst %.cpp,%.o,$(SOURCES)))) +SOURCES := $(wildcard src/*.cpp) $(wildcard src/*.c) \ + lib/crypto-algorithms/sha256.c \ + lib/crypto-algorithms/aes.c \ + lib/curve25519-donna/curve25519-donna.c + FUZZER_SOURCES := $(wildcard fuzzers/fuzz_*.cpp) $(wildcard fuzzers/fuzz_*.c) -FUZZER_BINARIES := $(patsubst fuzzers/%,$(BUILD_DIR)/fuzzers/%,$(patsubst %.c,%,$(patsubst %.cpp,%,$(FUZZER_SOURCES)))) -FUZZER_DEBUG_BINARIES := $(patsubst $(BUILD_DIR)/fuzzers/fuzz_%,$(BUILD_DIR)/fuzzers/debug_%,$(FUZZER_BINARIES)) TEST_SOURCES := $(wildcard tests/test_*.cpp) $(wildcard tests/test_*.c) -TEST_BINARIES := $(patsubst tests/%,$(BUILD_DIR)/tests/%,$(patsubst %.c,%,$(patsubst %.cpp,%,$(TEST_SOURCES)))) -JS_OBJECTS := $(patsubst src/%,$(BUILD_DIR)/javascript/%,$(patsubst %.c,%.js.bc,$(patsubst %.cpp,%.js.bc,$(SOURCES)))) + +OBJECTS := $(patsubst %.c,%.o,$(patsubst %.cpp,%.o,$(SOURCES))) +RELEASE_OBJECTS := $(addprefix $(BUILD_DIR)/release/,$(OBJECTS)) +DEBUG_OBJECTS := $(addprefix $(BUILD_DIR)/debug/,$(OBJECTS)) +FUZZER_OBJECTS := $(addprefix $(BUILD_DIR)/fuzzers/objects/,$(OBJECTS)) +FUZZER_BINARIES := $(addprefix $(BUILD_DIR)/,$(basename $(FUZZER_SOURCES))) +FUZZER_DEBUG_BINARIES := $(patsubst $(BUILD_DIR)/fuzzers/fuzz_%,$(BUILD_DIR)/fuzzers/debug_%,$(FUZZER_BINARIES)) +TEST_BINARIES := $(patsubst tests/%,$(BUILD_DIR)/tests/%,$(basename $(TEST_SOURCES))) +JS_OBJECTS := $(addprefix $(BUILD_DIR)/javascript/,$(OBJECTS)) JS_PRE := $(wildcard javascript/*pre.js) JS_POST := $(wildcard javascript/*post.js) @@ -82,14 +88,6 @@ $(JS_TARGET): LDFLAGS += $(JS_OPTIMIZE_FLAGS) lib: $(RELEASE_TARGET) .PHONY: lib -# Make sure that the build directory exists. -# We can't check the build directory into git because it is empty. -makedirs: - mkdir -p $(BUILD_DIR)/release $(BUILD_DIR)/debug $(BUILD_DIR)/javascript\ - $(BUILD_DIR)/tests $(BUILD_DIR)/fuzzers/objects -.PHONY: makedirs - - $(RELEASE_TARGET): $(RELEASE_OBJECTS) $(CXX) $(LDFLAGS) --shared -fPIC \ -Wl,--version-script,version_script.ver \ @@ -112,17 +110,6 @@ $(JS_TARGET): $(JS_OBJECTS) $(JS_PRE) $(JS_POST) $(JS_EXPORTED_FUNCTIONS) -s "EXPORTED_FUNCTIONS=@$(JS_EXPORTED_FUNCTIONS)" \ $(JS_OBJECTS) -o $@ -clean:; - rm -rf $(RELEASE_OBJECTS) $(RELEASE_OBJECTS:.o=.d) \ - $(DEBUG_OBJECTS) $(DEBUG_OBJECTS:.o=.d) \ - $(TEST_BINARIES) $(TEST_BINARIES:=.d) \ - $(JS_OBJECTS) $(JS_OBJECTS:.bc=.d) $(JS_TARGET) \ - $(JS_EXPORTED_FUNCTIONS)\ - $(RELEASE_TARGET) $(DEBUG_TARGET)\ - $(FUZZER_OBJECTS) $(FUZZER_OBJECTS:.o=.d)\ - $(FUZZER_BINARIES) $(FUZZER_BINARIES:=.d)\ - $(FUZZER_DEBUG_BINARIES) $(FUZZER_DEBUG_BINARIES:=.d)\ - build_tests: $(TEST_BINARIES) test: build_tests @@ -139,37 +126,51 @@ $(JS_EXPORTED_FUNCTIONS): $(PUBLIC_HEADERS) mv $@.tmp $@ all: test js lib debug -.PHONY: lib +.PHONY: all + +clean:; + rm -rf $(BUILD_DIR) +.PHONY: clean ### rules for building objects -$(BUILD_DIR)/release/%.o: src/%.c | makedirs +$(BUILD_DIR)/release/%.o: %.c + mkdir -p $(dir $@) $(COMPILE.c) $(OUTPUT_OPTION) $< -$(BUILD_DIR)/release/%.o: src/%.cpp | makedirs +$(BUILD_DIR)/release/%.o: %.cpp + mkdir -p $(dir $@) $(COMPILE.cc) $(OUTPUT_OPTION) $< -$(BUILD_DIR)/debug/%.o: src/%.c | makedirs +$(BUILD_DIR)/debug/%.o: %.c + mkdir -p $(dir $@) $(COMPILE.c) $(OUTPUT_OPTION) $< -$(BUILD_DIR)/debug/%.o: src/%.cpp | makedirs +$(BUILD_DIR)/debug/%.o: %.cpp + mkdir -p $(dir $@) $(COMPILE.cc) $(OUTPUT_OPTION) $< -$(BUILD_DIR)/javascript/%.js.bc: src/%.c | makedirs +$(BUILD_DIR)/javascript/%.o: %.c + mkdir -p $(dir $@) $(EMCC.c) $(OUTPUT_OPTION) $< -$(BUILD_DIR)/javascript/%.js.bc: src/%.cpp | makedirs +$(BUILD_DIR)/javascript/%.o: %.cpp + mkdir -p $(dir $@) $(EMCC.cc) $(OUTPUT_OPTION) $< $(BUILD_DIR)/tests/%: tests/%.c $(DEBUG_OBJECTS) + mkdir -p $(dir $@) $(LINK.c) $< $(DEBUG_OBJECTS) $(LOADLIBES) $(LDLIBS) -o $@ $(BUILD_DIR)/tests/%: tests/%.cpp $(DEBUG_OBJECTS) + mkdir -p $(dir $@) $(LINK.cc) $< $(DEBUG_OBJECTS) $(LOADLIBES) $(LDLIBS) -o $@ -$(BUILD_DIR)/fuzzers/objects/%.o: src/%.c | makedirs +$(BUILD_DIR)/fuzzers/objects/%.o: %.c + mkdir -p $(dir $@) $(AFL.c) $(OUTPUT_OPTION) $< -$(BUILD_DIR)/fuzzers/objects/%.o: src/%.cpp | makedirs +$(BUILD_DIR)/fuzzers/objects/%.o: %.cpp + mkdir -p $(dir $@) $(AFL.cc) $(OUTPUT_OPTION) $< $(BUILD_DIR)/fuzzers/fuzz_%: fuzzers/fuzz_%.c $(FUZZER_OBJECTS) @@ -188,7 +189,7 @@ $(BUILD_DIR)/fuzzers/debug_%: fuzzers/fuzz_%.cpp $(DEBUG_OBJECTS) -include $(RELEASE_OBJECTS:.o=.d) -include $(DEBUG_OBJECTS:.o=.d) --include $(JS_OBJECTS:.bc=.d) +-include $(JS_OBJECTS:.o=.d) -include $(TEST_BINARIES:=.d) -include $(FUZZER_OBJECTS:.o=.d) -include $(FUZZER_BINARIES:=.d) diff --git a/src/libs.c b/src/ed25519.c similarity index 87% rename from src/libs.c rename to src/ed25519.c index 4304354..f4f910d 100644 --- a/src/libs.c +++ b/src/ed25519.c @@ -12,9 +12,6 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -#include "crypto-algorithms/sha256.c" -#include "crypto-algorithms/aes.c" -#include "curve25519-donna/curve25519-donna.c" #define select ed25519_select #include "ed25519/src/fe.c" #include "ed25519/src/sc.c" From 708fddd747789a101123b09b67c064b119db8873 Mon Sep 17 00:00:00 2001 From: Richard van der Hoff Date: Wed, 25 May 2016 15:16:14 +0100 Subject: [PATCH 2/8] Remove session_id from group messages Putting the session_id inside the packed message body makes it hard to extract so that we can decide which session to use. We don't think there is any advantage to having thes sesion_id protected by the HMACs, so we're going to move it to the JSON framing. --- include/olm/message.h | 7 ------- src/inbound_group_session.c | 4 +--- src/message.cpp | 18 ++---------------- src/outbound_group_session.c | 3 +-- tests/test_message.cpp | 23 ++++++----------------- 5 files changed, 10 insertions(+), 45 deletions(-) diff --git a/include/olm/message.h b/include/olm/message.h index e80d54c..5eb504d 100644 --- a/include/olm/message.h +++ b/include/olm/message.h @@ -35,7 +35,6 @@ extern "C" { * The length of the buffer needed to hold a group message. */ size_t _olm_encode_group_message_length( - size_t group_session_id_length, uint32_t chain_index, size_t ciphertext_length, size_t mac_length @@ -45,8 +44,6 @@ size_t _olm_encode_group_message_length( * Writes the message headers into the output buffer. * * version: version number of the olm protocol - * session_id: group session identifier - * session_id_length: length of session_id * message_index: message index * ciphertext_length: length of the ciphertext * output: where to write the output. Should be at least @@ -58,8 +55,6 @@ size_t _olm_encode_group_message_length( */ size_t _olm_encode_group_message( uint8_t version, - const uint8_t *session_id, - size_t session_id_length, uint32_t message_index, size_t ciphertext_length, uint8_t *output, @@ -69,8 +64,6 @@ size_t _olm_encode_group_message( struct _OlmDecodeGroupMessageResults { uint8_t version; - const uint8_t *session_id; - size_t session_id_length; uint32_t message_index; int has_message_index; const uint8_t *ciphertext; diff --git a/src/inbound_group_session.c b/src/inbound_group_session.c index e171205..ce26033 100644 --- a/src/inbound_group_session.c +++ b/src/inbound_group_session.c @@ -231,9 +231,7 @@ static size_t _decrypt( return (size_t)-1; } - if (!decoded_results.has_message_index || !decoded_results.session_id - || !decoded_results.ciphertext - ) { + if (!decoded_results.has_message_index || !decoded_results.ciphertext ) { session->last_error = OLM_BAD_MESSAGE_FORMAT; return (size_t)-1; } diff --git a/src/message.cpp b/src/message.cpp index 2e841e5..ad26cb9 100644 --- a/src/message.cpp +++ b/src/message.cpp @@ -328,18 +328,15 @@ void olm::decode_one_time_key_message( -static const std::uint8_t GROUP_SESSION_ID_TAG = 012; -static const std::uint8_t GROUP_MESSAGE_INDEX_TAG = 020; -static const std::uint8_t GROUP_CIPHERTEXT_TAG = 032; +static const std::uint8_t GROUP_MESSAGE_INDEX_TAG = 010; +static const std::uint8_t GROUP_CIPHERTEXT_TAG = 022; size_t _olm_encode_group_message_length( - size_t group_session_id_length, uint32_t message_index, size_t ciphertext_length, size_t mac_length ) { size_t length = VERSION_LENGTH; - length += 1 + varstring_length(group_session_id_length); length += 1 + varint_length(message_index); length += 1 + varstring_length(ciphertext_length); length += mac_length; @@ -349,19 +346,14 @@ size_t _olm_encode_group_message_length( size_t _olm_encode_group_message( uint8_t version, - const uint8_t *session_id, - size_t session_id_length, uint32_t message_index, size_t ciphertext_length, uint8_t *output, uint8_t **ciphertext_ptr ) { std::uint8_t * pos = output; - std::uint8_t * session_id_pos; *(pos++) = version; - pos = encode(pos, GROUP_SESSION_ID_TAG, session_id_pos, session_id_length); - std::memcpy(session_id_pos, session_id, session_id_length); pos = encode(pos, GROUP_MESSAGE_INDEX_TAG, message_index); pos = encode(pos, GROUP_CIPHERTEXT_TAG, *ciphertext_ptr, ciphertext_length); return pos-output; @@ -376,8 +368,6 @@ void _olm_decode_group_message( std::uint8_t const * end = input + input_length - mac_length; std::uint8_t const * unknown = nullptr; - results->session_id = nullptr; - results->session_id_length = 0; bool has_message_index = false; results->message_index = 0; results->ciphertext = nullptr; @@ -388,10 +378,6 @@ void _olm_decode_group_message( results->version = *(pos++); while (pos != end) { - pos = decode( - pos, end, GROUP_SESSION_ID_TAG, - results->session_id, results->session_id_length - ); pos = decode( pos, end, GROUP_MESSAGE_INDEX_TAG, results->message_index, has_message_index diff --git a/src/outbound_group_session.c b/src/outbound_group_session.c index 9b2298a..2a6c220 100644 --- a/src/outbound_group_session.c +++ b/src/outbound_group_session.c @@ -187,7 +187,7 @@ static size_t raw_message_length( mac_length = megolm_cipher->ops->mac_length(megolm_cipher); return _olm_encode_group_message_length( - GROUP_SESSION_ID_LENGTH, session->ratchet.counter, + session->ratchet.counter, ciphertext_length, mac_length); } @@ -220,7 +220,6 @@ static size_t _encrypt( */ message_length = _olm_encode_group_message( OLM_PROTOCOL_VERSION, - session->session_id, GROUP_SESSION_ID_LENGTH, session->ratchet.counter, ciphertext_length, buffer, diff --git a/tests/test_message.cpp b/tests/test_message.cpp index 30c10a0..06b36dc 100644 --- a/tests/test_message.cpp +++ b/tests/test_message.cpp @@ -67,12 +67,8 @@ assert_equals(message2, output, 35); TestCase test_case("Group message encode test"); - const uint8_t session_id[] = "sessionid"; - size_t session_id_len = 9; - - size_t length = _olm_encode_group_message_length( - session_id_len, 200, 10, 8); - size_t expected_length = 1 + (2+session_id_len) + (1+2) + (2+10) + 8; + size_t length = _olm_encode_group_message_length(200, 10, 8); + size_t expected_length = 1 + (1+2) + (2+10) + 8; assert_equals(expected_length, length); uint8_t output[50]; @@ -80,7 +76,6 @@ assert_equals(message2, output, 35); _olm_encode_group_message( 3, - session_id, session_id_len, 200, // counter 10, // ciphertext length output, @@ -89,9 +84,8 @@ assert_equals(message2, output, 35); uint8_t expected[] = "\x03" - "\x0A\x09sessionid" - "\x10\xC8\x01" - "\x1A\x0A"; + "\x08\xC8\x01" + "\x12\x0A"; assert_equals(expected, output, sizeof(expected)-1); assert_equals(output+sizeof(expected)-1, ciphertext_ptr); @@ -103,17 +97,12 @@ assert_equals(message2, output, 35); struct _OlmDecodeGroupMessageResults results; std::uint8_t message[] = "\x03" - "\x0A\x09sessionid" - "\x10\xC8\x01" - "\x1A\x0A" "ciphertext" + "\x08\xC8\x01" + "\x12\x0A" "ciphertext" "hmacsha2"; - const uint8_t expected_session_id[] = "sessionid"; - _olm_decode_group_message(message, sizeof(message)-1, 8, &results); assert_equals(std::uint8_t(3), results.version); - assert_equals(std::size_t(9), results.session_id_length); - assert_equals(expected_session_id, results.session_id, 9); assert_equals(1, results.has_message_index); assert_equals(std::uint32_t(200), results.message_index); assert_equals(std::size_t(10), results.ciphertext_length); From 013f27f3dc8de747e82b74055c4dde55ca04b4c9 Mon Sep 17 00:00:00 2001 From: Richard van der Hoff Date: Wed, 25 May 2016 14:42:49 +0100 Subject: [PATCH 3/8] Javascript bindings for group sessions --- Makefile | 11 ++- javascript/README.md | 17 ++++ javascript/olm_inbound_group_session.js | 78 +++++++++++++++++ javascript/olm_outbound_group_session.js | 104 +++++++++++++++++++++++ 4 files changed, 206 insertions(+), 4 deletions(-) create mode 100644 javascript/olm_inbound_group_session.js create mode 100644 javascript/olm_outbound_group_session.js diff --git a/Makefile b/Makefile index 49bec6a..08238c9 100644 --- a/Makefile +++ b/Makefile @@ -15,7 +15,7 @@ JS_TARGET := javascript/olm.js JS_EXPORTED_FUNCTIONS := javascript/exported_functions.json -PUBLIC_HEADERS := include/olm/olm.h +PUBLIC_HEADERS := include/olm/olm.h include/olm/outbound_group_session.h include/olm/inbound_group_session.h SOURCES := $(wildcard src/*.cpp) $(wildcard src/*.c) \ lib/crypto-algorithms/sha256.c \ @@ -34,7 +34,9 @@ FUZZER_DEBUG_BINARIES := $(patsubst $(BUILD_DIR)/fuzzers/fuzz_%,$(BUILD_DIR)/fuz TEST_BINARIES := $(patsubst tests/%,$(BUILD_DIR)/tests/%,$(basename $(TEST_SOURCES))) JS_OBJECTS := $(addprefix $(BUILD_DIR)/javascript/,$(OBJECTS)) JS_PRE := $(wildcard javascript/*pre.js) -JS_POST := $(wildcard javascript/*post.js) +JS_POST := javascript/olm_outbound_group_session.js \ + javascript/olm_inbound_group_session.js \ + javascript/olm_post.js CPPFLAGS += -Iinclude -Ilib # we rely on , which was introduced in C99 @@ -106,7 +108,8 @@ js: $(JS_TARGET) $(JS_TARGET): $(JS_OBJECTS) $(JS_PRE) $(JS_POST) $(JS_EXPORTED_FUNCTIONS) $(EMCC_LINK) \ - --pre-js $(JS_PRE) --post-js $(JS_POST) \ + $(foreach f,$(JS_PRE),--pre-js $(f)) \ + $(foreach f,$(JS_POST),--post-js $(f)) \ -s "EXPORTED_FUNCTIONS=@$(JS_EXPORTED_FUNCTIONS)" \ $(JS_OBJECTS) -o $@ @@ -122,7 +125,7 @@ fuzzers: $(FUZZER_BINARIES) $(FUZZER_DEBUG_BINARIES) .PHONY: fuzzers $(JS_EXPORTED_FUNCTIONS): $(PUBLIC_HEADERS) - perl -MJSON -ne '/(olm_[^( ]*)\(/ && push @f, "_$$1"; END { print encode_json \@f }' $^ > $@.tmp + perl -MJSON -ne '$$f{"_$$1"}=1 if /(olm_[^( ]*)\(/; END { @f=sort keys %f; print encode_json \@f }' $^ > $@.tmp mv $@.tmp $@ all: test js lib debug diff --git a/javascript/README.md b/javascript/README.md index 5c2c96b..6ed9bbb 100644 --- a/javascript/README.md +++ b/javascript/README.md @@ -23,3 +23,20 @@ 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); + + +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/olm_inbound_group_session.js b/javascript/olm_inbound_group_session.js new file mode 100644 index 0000000..cfb557f --- /dev/null +++ b/javascript/olm_inbound_group_session.js @@ -0,0 +1,78 @@ +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); + 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, pickle_length); +}); + +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(message_index, 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, message_index, 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 = 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); + var plaintext_length = session_method(Module["_olm_group_decrypt"])( + this.ptr, + message_buffer, message.length, + plaintext_buffer, max_plaintext_length + ); + return Pointer_stringify(plaintext_buffer, plaintext_length); +}); + +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..277a882 --- /dev/null +++ b/javascript/olm_outbound_group_session.js @@ -0,0 +1,104 @@ + +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); + 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, pickle_length); +}); + +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(key) { + var random_length = 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); + outbound_group_session_method(Module['_olm_group_encrypt'])( + this.ptr, + plaintext_buffer, plaintext_array.length, + message_buffer, message_length + ); + return Pointer_stringify(message_buffer, message_length); +}); + +OutboundGroupSession.prototype['session_id'] = restore_stack(function(key) { + var length = outbound_group_session_method( + Module['_olm_outbound_group_session_id_length'] + )(this.ptr); + var session_id = stack(length); + outbound_group_session_method(Module['_olm_outbound_group_session_id'])( + this.ptr, session_id, length + ); + return Pointer_stringify(session_id, length); +}); + +OutboundGroupSession.prototype['session_key'] = restore_stack(function(key) { + var key_length = outbound_group_session_method( + Module['_olm_outbound_group_session_key_length'] + )(this.ptr); + var key = stack(key_length); + outbound_group_session_method(Module['_olm_outbound_group_session_key'])( + this.ptr, key, key_length + ); + return Pointer_stringify(key, key_length); +}); + +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; From 315fbfc9211d0b7369dc9cb43bbffc8100f30e16 Mon Sep 17 00:00:00 2001 From: Richard van der Hoff Date: Wed, 25 May 2016 14:53:28 +0100 Subject: [PATCH 4/8] Add a demo for group messaging via the JS bindings --- javascript/demo/demo.css | 8 + javascript/demo/group_demo.html | 61 +++ javascript/demo/group_demo.js | 421 ++++++++++++++++++ .../{demo.html => demo/one_to_one_demo.html} | 2 +- 4 files changed, 491 insertions(+), 1 deletion(-) create mode 100644 javascript/demo/demo.css create mode 100644 javascript/demo/group_demo.html create mode 100644 javascript/demo/group_demo.js rename javascript/{demo.html => demo/one_to_one_demo.html} (99%) 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 @@ + + + + + + + +
+

User1

+ + + + +

Outgoing

+ +

One-to-one output

+
+ +

Group output

+
+ +

Incoming

+ +

One-to-one Received

+
+ +

Group received

+
+ +

Tasks

+
+
+ +
+

User 2

+ + + + +

Outgoing

+ +

One-to-one output

+
+ +

Group output

+
+ +

Incoming

+ +

One-to-one Received

+
+ +

Group received

+
+ +

Tasks

+
+
+ + + diff --git a/javascript/demo/group_demo.js b/javascript/demo/group_demo.js new file mode 100644 index 0000000..c8939d3 --- /dev/null +++ b/javascript/demo/group_demo.js @@ -0,0 +1,421 @@ +/* Javascript parts of the group demo. To use, load group_demo.html in your + * browser. + */ + +function buttonAndTextElement(buttonLabel, textContent, clickHandler) { + var el = document.createElement("div"); + + var button = document.createElement("button"); + el.appendChild(button); + button.appendChild(document.createTextNode(buttonLabel)); + + var message_element = document.createElement("tt"); + el.appendChild(message_element); + + var content = document.createTextNode(textContent); + message_element.appendChild(content); + + el.addEventListener("click", clickHandler, false); + return el; +} + +function DemoUser(name) { + this.name = name; + this.olmAccount = new Olm.Account(); + this.olmAccount.create(); + + /* a list of the people in our chat */ + this.peers = []; + + /* 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; +} + +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(peer) { + this.peers.push(peer); +}; + +DemoUser.prototype.getIdKey = function() { + var keys = JSON.parse(this.olmAccount.identity_keys()); + return keys.curve25519; +}; + +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(peer, callback) { + var self = this; + var peerId = peer.getIdKey(); + if (this.peerSessions[peerId]) { + callback(this.peerSessions[peerId]); + return; + } + + 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(peer, message, callback) { + var self = this; + this.getPeerSession(peer, function(session) { + self.addTask("encrypt one-to-one message", function(done) { + var encrypted = session.encrypt(message); + var packet = { + sender_key: self.getIdKey(), + ciphertext: encrypted, + }; + var json = JSON.stringify(packet); + + var el = buttonAndTextElement("send", json, function(ev) { + peer.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.message_index, 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 i = 0; i < this.peers.length; i++) { + var peer = this.peers[i]; + this.sendToPeer(peer, jsonmsg); + } + + return this.groupSession; +}; + +/** + * add a task to create an inbound group session + */ +DemoUser.prototype.createInboundSession = function( + peer_id, session_id, message_index, session_key, callback +) { + var self = this; + this.addTask("init inbound session", function(done) { + session = new Olm.InboundGroupSession(); + session.create(message_index, session_key); + if (!self.peerGroupSessions[peer_id]) { + self.peerGroupSessions[peer_id] = {}; + } + 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 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(); + + self.addTask("encrypt group message", function(done) { + var encrypted = session.encrypt(message); + var packet = { + sender_key: self.getIdKey(), + session_id: session.session_id(), + body: encrypted, + }; + var json = JSON.stringify(packet); + + var el = buttonAndTextElement("send", json, function(ev) { + for (var i = 0; i < self.peers.length; i++) { + var peer = self.peers[i]; + peer.receiveGroup(json); + } + }); + 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); + user2.addPeer(user1); +} + + +document.addEventListener("DOMContentLoaded", startDemo, false); diff --git a/javascript/demo.html b/javascript/demo/one_to_one_demo.html similarity index 99% rename from javascript/demo.html rename to javascript/demo/one_to_one_demo.html index bebf918..fc7e654 100644 --- a/javascript/demo.html +++ b/javascript/demo/one_to_one_demo.html @@ -1,6 +1,6 @@ - +