diff options
41 files changed, 3101 insertions, 116 deletions
@@ -5,4 +5,25 @@ /docs/signing.html /olm-*.tgz /README.html -/tracing/README.html
\ No newline at end of file +/tracing/README.html + +# Xcode +build/ +DerivedData/ +*.pbxuser +!default.pbxuser +*.mode1v3 +!default.mode1v3 +*.mode2v3 +!default.mode2v3 +*.perspectivev3 +!default.perspectivev3 +xcuserdata/ +*.moved-aside +*.xcuserstate +*.hmap +*.ipa +*.dSYM.zip +*.dSYM +Pods/ +*.xcworkspace
\ No newline at end of file diff --git a/CHANGELOG.rst b/CHANGELOG.rst index a35eedf..4547eaa 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -1,3 +1,20 @@ +Changes in `2.1.0 <http://matrix.org/git/olm/commit/?h=2.1.0>`_ +=============================================================== + +This release includes the following changes since 2.0.0: + +* Add OLMKit, the Objective-C wrapper. Thanks to Chris Ballinger for the + initial work on this. + +Javascript wrapper: + +* Handle exceptions during loading better (don't leave a half-initialised + state). +* Allow applications to tune emscripten options (such as the amount of heap). +* Allocate memory for encrypted/decrypted messages on the empscripten heap, + rather than the stack, allowing more efficient memory use. + + Changes in `2.0.0 <http://matrix.org/git/olm/commit/?h=2.0.0>`_ =============================================================== @@ -16,6 +33,7 @@ This release includes the following changes since 1.3.0: * Add an ``install-headers`` target to the Makefile (and run it when installing the library). (Credit to Emmanuel Gil Peyrot). + Changes in `1.3.0 <http://matrix.org/git/olm/commit/?h=1.3.0>`_ =============================================================== @@ -1,7 +1,7 @@ #!/usr/bin/make -f MAJOR := 2 -MINOR := 0 +MINOR := 1 PATCH := 0 VERSION := $(MAJOR).$(MINOR).$(PATCH) PREFIX ?= /usr/local diff --git a/OLMKit.podspec b/OLMKit.podspec new file mode 100644 index 0000000..e21d10f --- /dev/null +++ b/OLMKit.podspec @@ -0,0 +1,61 @@ +Pod::Spec.new do |s| + + # The libolm version + MAJOR = 2 + MINOR = 1 + PATCH = 0 + + s.name = "OLMKit" + s.version = "#{MAJOR}.#{MINOR}.#{PATCH}" + s.summary = "An Objective-C wrapper of olm (http://matrix.org/git/olm)" + + s.description = <<-DESC + olm is an implementation of the Double Ratchet cryptographic ratchet in C++ + DESC + + s.homepage = "http://matrix.org/git/olm" + + s.license = { :type => "Apache License, Version 2.0", :file => "LICENSE" } + + s.authors = { "Chris Ballinger" => "chrisballinger@gmail.com", + "matrix.org" => "support@matrix.org" } + + s.platform = :ios, "5.0" + + # Expose the Objective-C wrapper API of libolm + s.public_header_files = "xcode/OLMKit/*.h" + + s.source = { + :git => "https://matrix.org/git/olm.git", + :tag => s.version.to_s + } + + s.source_files = "xcode/OLMKit/*.{h,m}", "include/**/*.{h,hh}", "src/*.{c,cpp}", "lib/crypto-algorithms/sha256.c", "lib/crypto-algorithms/aes.c", "lib/curve25519-donna/curve25519-donna.c" + + # Those files (including .c) are included by ed25519.c. We do not want to compile them twice + s.preserve_paths = "lib/ed25519/**/*.{h,c}" + + s.library = "c++" + + + # Use the same compiler options for C and C++ as olm/Makefile + + s.compiler_flags = "-g -O3 -DOLMLIB_VERSION_MAJOR=#{MAJOR} -DOLMLIB_VERSION_MINOR=#{MINOR} -DOLMLIB_VERSION_PATCH=#{PATCH}" + + # For headers search paths, manage first the normal installation. Then, use paths used + # when the pod is local + s.xcconfig = { + 'USER_HEADER_SEARCH_PATHS' =>"${PODS_ROOT}/OLMKit/include ${PODS_ROOT}/OLMKit/lib #{File.join(File.dirname(__FILE__), 'include')} #{File.join(File.dirname(__FILE__), 'lib')}" + } + + s.subspec 'olmc' do |olmc| + olmc.source_files = "src/*.{c}", "lib/curve25519-donna.h", "lib/crypto-algorithms/sha256.{h,c}", "lib/crypto-algorithms/aes.{h,c}", "lib/curve25519-donna/curve25519-donna.c" + olmc.compiler_flags = ' -std=c99 -fPIC' + end + + s.subspec 'olmcpp' do |olmcpp| + olmcpp.source_files = "src/*.{cpp}" + olmcpp.compiler_flags = ' -std=c++11 -fPIC' + end + +end @@ -39,25 +39,49 @@ To build the android project for Android bindings, run: cd android ./gradlew clean assembleRelease +To build the Xcode workspace for Objective-C bindings, run: + +.. code:: bash + + cd xcode + pod install + open OLMKit.xcworkspace + Release process --------------- -.. code:: bash +First: bump version numbers in ``Makefile``, ``javascript/package.json``, and +``OLMKit.podspec``. + +Also, ensure the changelog is up to date, and that everyting is committed to +git. - # Bump version numbers in ``Makefile`` and ``javascript/package.json`` - # Prepare changelog - git commit +It's probably sensible to do the above on a release branch (``release-vx.y.z`` +by convention), and merge back to master once the release is complete. + +.. code:: bash make clean + + # build and test C library make test + + # build and test JS wrapper make js + (cd javascript && npm run test) npm pack javascript + VERSION=x.y.z scp olm-$VERSION.tgz packages@ldc-prd-matrix-001:/sites/matrix/packages/npm/olm/ git tag $VERSION -s git push --tags -It's probably sensible to do the above on a release branch (``release-vx.y.z`` -by convention), and merge back to master once complete. + # OLMKit CocoaPod release + # Make sure the version OLMKit.podspec is the same as the git tag + # (this must be checked before git tagging) + pod spec lint OLMKit.podspec --use-libraries --allow-warnings + pod trunk push OLMKit.podspec --use-libraries --allow-warnings + # Check the pod has been successully published with: + pod search OLMKit Design diff --git a/javascript/.gitignore b/javascript/.gitignore index 603fe7c..ec22345 100644 --- a/javascript/.gitignore +++ b/javascript/.gitignore @@ -2,3 +2,4 @@ /node_modules /npm-debug.log /olm.js +/reports diff --git a/javascript/olm_inbound_group_session.js b/javascript/olm_inbound_group_session.js index 1b7fcfe..2e4727f 100644 --- a/javascript/olm_inbound_group_session.js +++ b/javascript/olm_inbound_group_session.js @@ -64,33 +64,51 @@ InboundGroupSession.prototype['create'] = restore_stack(function(session_key) { 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 message_index = stack(4); - var plaintext_length = inbound_group_session_method(Module["_olm_group_decrypt"])( - this.ptr, - message_buffer, message_array.length, - plaintext_buffer, max_plaintext_length, - message_index - ); + var message_buffer, 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" - ); + try { + message_buffer = malloc(message.length); + Module['writeAsciiToMemory'](message, message_buffer, true); + + var max_plaintext_length = inbound_group_session_method( + Module['_olm_group_decrypt_max_plaintext_length'] + )(this.ptr, message_buffer, message.length); + + // caculating the length destroys the input buffer, so we need to re-copy it. + Module['writeAsciiToMemory'](message, message_buffer, true); + + plaintext_buffer = malloc(max_plaintext_length + NULL_BYTE_PADDING_LENGTH); + var message_index = stack(4); - return { - "plaintext": Pointer_stringify(plaintext_buffer), - "message_index": Module['getValue'](message_index, "i32") + plaintext_length = inbound_group_session_method( + Module["_olm_group_decrypt"] + )( + this.ptr, + message_buffer, message.length, + plaintext_buffer, max_plaintext_length, + message_index + ); + + // UTF8ToString requires a null-terminated argument, so add the + // null terminator. + Module['setValue']( + plaintext_buffer+plaintext_length, + 0, "i8" + ); + + return { + "plaintext": UTF8ToString(plaintext_buffer), + "message_index": Module['getValue'](message_index, "i32") + } + } finally { + if (message_buffer !== undefined) { + free(message_buffer); + } + if (plaintext_buffer !== undefined) { + // don't leave a copy of the plaintext in the heap. + bzero(plaintext_buffer, plaintext_length + NULL_BYTE_PADDING_LENGTH); + free(plaintext_buffer); + } } }); diff --git a/javascript/olm_outbound_group_session.js b/javascript/olm_outbound_group_session.js index 88a441d..24ea644 100644 --- a/javascript/olm_outbound_group_session.js +++ b/javascript/olm_outbound_group_session.js @@ -63,20 +63,46 @@ OutboundGroupSession.prototype['create'] = restore_stack(function() { ); }); -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['encrypt'] = function(plaintext) { + var plaintext_buffer, message_buffer, plaintext_length; + try { + plaintext_length = Module['lengthBytesUTF8'](plaintext); + + var message_length = outbound_group_session_method( + Module['_olm_group_encrypt_message_length'] + )(this.ptr, plaintext_length); + + // need to allow space for the terminator (which stringToUTF8 always + // writes), hence + 1. + plaintext_buffer = malloc(plaintext_length + 1); + Module['stringToUTF8'](plaintext, plaintext_buffer, plaintext_length + 1); + + message_buffer = malloc(message_length + NULL_BYTE_PADDING_LENGTH); + outbound_group_session_method(Module['_olm_group_encrypt'])( + this.ptr, + plaintext_buffer, plaintext_length, + message_buffer, message_length + ); + + // UTF8ToString requires a null-terminated argument, so add the + // null terminator. + Module['setValue']( + message_buffer+message_length, + 0, "i8" + ); + + return Module['UTF8ToString'](message_buffer); + } finally { + if (plaintext_buffer !== undefined) { + // don't leave a copy of the plaintext in the heap. + bzero(plaintext_buffer, plaintext_length + 1); + free(plaintext_buffer); + } + if (message_buffer !== undefined) { + free(message_buffer); + } + } +}; OutboundGroupSession.prototype['session_id'] = restore_stack(function() { var length = outbound_group_session_method( diff --git a/javascript/olm_post.js b/javascript/olm_post.js index 8951c11..65eab02 100644 --- a/javascript/olm_post.js +++ b/javascript/olm_post.js @@ -4,9 +4,11 @@ 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. +/* The 'length' argument to Pointer_stringify doesn't work if the input + * includes characters >= 128, which makes Pointer_stringify unreliable. We + * could use it on strings which are known to be ascii, but that seems + * dangerous. Instead we add a NULL character to all of our strings and just + * use UTF8ToString. */ var NULL_BYTE_PADDING_LENGTH = 1; @@ -40,6 +42,13 @@ function restore_stack(wrapped) { } } +/* set a memory area to zero */ +function bzero(ptr, n) { + while(n-- > 0) { + Module['HEAP8'][ptr++] = 0; + } +} + function Account() { var size = Module['_olm_account_size'](); this.buf = malloc(size); @@ -297,59 +306,102 @@ Session.prototype['matches_inbound_from'] = restore_stack(function( Session.prototype['encrypt'] = restore_stack(function( plaintext ) { - var random_length = session_method( - Module['_olm_encrypt_random_length'] - )(this.ptr); - var message_type = session_method( - Module['_olm_encrypt_message_type'] - )(this.ptr); - var plaintext_array = array_from_string(plaintext); - var message_length = session_method( - Module['_olm_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 + NULL_BYTE_PADDING_LENGTH); - session_method(Module['_olm_encrypt'])( - this.ptr, - plaintext_buffer, plaintext_array.length, - random, random_length, - message_buffer, message_length - ); - return { - "type": message_type, - "body": Pointer_stringify(message_buffer) - }; + var plaintext_buffer, message_buffer, plaintext_length; + try { + var random_length = session_method( + Module['_olm_encrypt_random_length'] + )(this.ptr); + var message_type = session_method( + Module['_olm_encrypt_message_type'] + )(this.ptr); + + plaintext_length = Module['lengthBytesUTF8'](plaintext); + var message_length = session_method( + Module['_olm_encrypt_message_length'] + )(this.ptr, plaintext_length); + + var random = random_stack(random_length); + + // need to allow space for the terminator (which stringToUTF8 always + // writes), hence + 1. + plaintext_buffer = malloc(plaintext_length + 1); + Module['stringToUTF8'](plaintext, plaintext_buffer, plaintext_length + 1); + + message_buffer = malloc(message_length + NULL_BYTE_PADDING_LENGTH); + + session_method(Module['_olm_encrypt'])( + this.ptr, + plaintext_buffer, plaintext_length, + random, random_length, + message_buffer, message_length + ); + + // UTF8ToString requires a null-terminated argument, so add the + // null terminator. + Module['setValue']( + message_buffer+message_length, + 0, "i8" + ); + + return { + "type": message_type, + "body": Module['UTF8ToString'](message_buffer), + }; + } finally { + if (plaintext_buffer !== undefined) { + // don't leave a copy of the plaintext in the heap. + bzero(plaintext_buffer, plaintext_length + 1); + free(plaintext_buffer); + } + if (message_buffer !== undefined) { + free(message_buffer); + } + } }); 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['_olm_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 + 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 - ); - - // 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" - ); + var message_buffer, plaintext_buffer, max_plaintext_length; + + try { + message_buffer = malloc(message.length); + Module['writeAsciiToMemory'](message, message_buffer, true); + + max_plaintext_length = session_method( + Module['_olm_decrypt_max_plaintext_length'] + )(this.ptr, message_type, message_buffer, message.length); + + // caculating the length destroys the input buffer, so we need to re-copy it. + Module['writeAsciiToMemory'](message, message_buffer, true); + + plaintext_buffer = malloc(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 + ); + + // UTF8ToString requires a null-terminated argument, so add the + // null terminator. + Module['setValue']( + plaintext_buffer+plaintext_length, + 0, "i8" + ); + + return UTF8ToString(plaintext_buffer); + } finally { + if (message_buffer !== undefined) { + free(message_buffer); + } + if (plaintext_buffer !== undefined) { + // don't leave a copy of the plaintext in the heap. + bzero(plaintext_buffer, max_plaintext_length + NULL_BYTE_PADDING_LENGTH); + free(plaintext_buffer); + } + } - return Pointer_stringify(plaintext_buffer); }); function Utility() { @@ -419,4 +471,22 @@ olm_exports["get_library_version"] = restore_stack(function() { getValue(buf+2, 'i8'), ]; }); -}(); + +})(); + +// export the olm functions into the environment. +// +// make sure that we do this *after* populating olm_exports, so that we don't +// get a half-built window.Olm if there is an exception. + +if (typeof module !== 'undefined' && module.exports) { + // node / browserify + module.exports = olm_exports; +} + +if (typeof(window) !== 'undefined') { + // We've been imported directly into a browser. Define the global 'Olm' object. + // (we do this even if module.exports was defined, because it's useful to have + // Olm in the global scope for browserified and webpacked apps.) + window["Olm"] = olm_exports; +} diff --git a/javascript/olm_pre.js b/javascript/olm_pre.js index 50bf8c2..ae7aba5 100644 --- a/javascript/olm_pre.js +++ b/javascript/olm_pre.js @@ -2,32 +2,32 @@ var olm_exports = {}; var get_random_values; var process; // Shadow the process object so that emscripten won't get // confused by browserify -if (typeof(global) !== 'undefined' && global["window"]) { - // We're running with browserify - module["exports"] = olm_exports; - global["window"]["Olm"] = olm_exports; - get_random_values = function(buf) { - window.crypto.getRandomValues(buf); - }; -} else if (typeof(window) !== 'undefined') { - // We've been imported directly into a browser. - window["Olm"] = olm_exports; + +if (typeof(window) !== 'undefined') { + // We've in a browser (directly, via browserify, or via webpack). get_random_values = function(buf) { window.crypto.getRandomValues(buf); }; } else if (module["exports"]) { // We're running in node. - module["exports"] = olm_exports; var nodeCrypto = require("crypto"); get_random_values = function(buf) { var bytes = nodeCrypto.randomBytes(buf.length); buf.set(bytes); - } + }; process = global["process"]; } else { throw new Error("Cannot find global to attach library to"); } -var init = function() { - var module; // Shadow the Node 'module' object so that emscripten won't try - // to fiddle with it. +(function() { + /* applications should define OLM_OPTIONS in the environment to override + * emscripten module settings */ + var Module = {}; + if (typeof(OLM_OPTIONS) !== 'undefined') { + for (var key in OLM_OPTIONS) { + if (OLM_OPTIONS.hasOwnProperty(key)) { + Module[key] = OLM_OPTIONS[key]; + } + } + } diff --git a/javascript/package.json b/javascript/package.json index b65fb2e..5e8bd2c 100644 --- a/javascript/package.json +++ b/javascript/package.json @@ -1,6 +1,6 @@ { "name": "olm", - "version": "2.0.0", + "version": "2.1.0", "description": "An implementation of the Double Ratchet cryptographic ratchet", "main": "olm.js", "files": [ @@ -9,7 +9,7 @@ ], "scripts": { "build": "make -C .. js", - "test": "echo \"Error: no test specified\" && exit 1" + "test": "jasmine-node test --verbose --junitreport --captureExceptions" }, "repository": { "type": "git", @@ -23,5 +23,8 @@ "bugs": { "url": "https://github.com/matrix-org/olm/issues" }, - "homepage": "https://github.com/matrix-org/olm#readme" + "homepage": "https://github.com/matrix-org/olm#readme", + "devDependencies": { + "jasmine-node": "^1.14.5" + } } diff --git a/javascript/test/megolm.spec.js b/javascript/test/megolm.spec.js new file mode 100644 index 0000000..8f9d24a --- /dev/null +++ b/javascript/test/megolm.spec.js @@ -0,0 +1,68 @@ +/* +Copyright 2016 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. +*/ + +"use strict"; + +var Olm = require('../olm'); + +describe("megolm", function() { + var aliceSession, bobSession; + + beforeEach(function() { + aliceSession = new Olm.OutboundGroupSession(); + bobSession = new Olm.InboundGroupSession(); + }); + + afterEach(function() { + if (aliceSession !== undefined) { + aliceSession.free(); + aliceSession = undefined; + } + + if (bobSession !== undefined) { + bobSession.free(); + bobSession = undefined; + } + }); + + it("should encrypt and decrypt", function() { + aliceSession.create(); + expect(aliceSession.message_index()).toEqual(0); + bobSession.create(aliceSession.session_key()); + + var TEST_TEXT='têst1'; + var encrypted = aliceSession.encrypt(TEST_TEXT); + var decrypted = bobSession.decrypt(encrypted); + console.log(TEST_TEXT, "->", decrypted); + expect(decrypted.plaintext).toEqual(TEST_TEXT); + expect(decrypted.message_index).toEqual(0); + + TEST_TEXT='hot beverage: ☕'; + encrypted = aliceSession.encrypt(TEST_TEXT); + decrypted = bobSession.decrypt(encrypted); + console.log(TEST_TEXT, "->", decrypted); + expect(decrypted.plaintext).toEqual(TEST_TEXT); + expect(decrypted.message_index).toEqual(1); + + // shorter text, to spot buffer overruns + TEST_TEXT='☕'; + encrypted = aliceSession.encrypt(TEST_TEXT); + decrypted = bobSession.decrypt(encrypted); + console.log(TEST_TEXT, "->", decrypted); + expect(decrypted.plaintext).toEqual(TEST_TEXT); + expect(decrypted.message_index).toEqual(2); + }); +}); diff --git a/javascript/test/olm.spec.js b/javascript/test/olm.spec.js new file mode 100644 index 0000000..b7cc3ae --- /dev/null +++ b/javascript/test/olm.spec.js @@ -0,0 +1,94 @@ +/* +Copyright 2016 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. +*/ + +"use strict"; + +var Olm = require('../olm'); + +if (!Object.keys) { + Object.keys = function(o) { + var k=[], p; + for (p in o) if (Object.prototype.hasOwnProperty.call(o,p)) k.push(p); + return k; + } +} + +describe("olm", function() { + var aliceAccount, bobAccount; + var aliceSession, bobSession; + + beforeEach(function() { + aliceAccount = new Olm.Account(); + bobAccount = new Olm.Account(); + aliceSession = new Olm.Session(); + bobSession = new Olm.Session(); + }); + + afterEach(function() { + if (aliceAccount !== undefined) { + aliceAccount.free(); + aliceAccount = undefined; + } + + if (bobAccount !== undefined) { + bobAccount.free(); + bobAccount = undefined; + } + + if (aliceSession !== undefined) { + aliceSession.free(); + aliceSession = undefined; + } + + if (bobSession !== undefined) { + bobSession.free(); + bobSession = undefined; + } + }); + + it('should encrypt and decrypt', function() { + aliceAccount.create(); + bobAccount.create(); + + bobAccount.generate_one_time_keys(1); + var bobOneTimeKeys = JSON.parse(bobAccount.one_time_keys()).curve25519; + bobAccount.mark_keys_as_published(); + + var bobIdKey = JSON.parse(bobAccount.identity_keys()).curve25519; + + var otk_id = Object.keys(bobOneTimeKeys)[0]; + + aliceSession.create_outbound( + aliceAccount, bobIdKey, bobOneTimeKeys[otk_id] + ); + + var TEST_TEXT='têst1'; + var encrypted = aliceSession.encrypt(TEST_TEXT); + expect(encrypted.type).toEqual(0); + bobSession.create_inbound(bobAccount, encrypted.body); + bobAccount.remove_one_time_keys(bobSession); + var decrypted = bobSession.decrypt(encrypted.type, encrypted.body); + console.log(TEST_TEXT, "->", decrypted); + expect(decrypted).toEqual(TEST_TEXT); + + TEST_TEXT='hot beverage: ☕'; + encrypted = bobSession.encrypt(TEST_TEXT); + expect(encrypted.type).toEqual(1); + decrypted = aliceSession.decrypt(encrypted.type, encrypted.body); + console.log(TEST_TEXT, "->", decrypted); + expect(decrypted).toEqual(TEST_TEXT); + }); +}); @@ -11,4 +11,5 @@ make test . ~/.emsdk_set_env.sh make js +(cd javascript && npm install && npm run test) npm pack javascript diff --git a/lib/crypto-algorithms/aes.c b/lib/crypto-algorithms/aes.c index 948e36f..0c264da 100644 --- a/lib/crypto-algorithms/aes.c +++ b/lib/crypto-algorithms/aes.c @@ -21,6 +21,7 @@ #include "aes.h" #include <stdio.h> +#include <string.h> /****************************** MACROS ******************************/ // The least significant byte of the word is rotated to the end. diff --git a/lib/crypto-algorithms/sha256.c b/lib/crypto-algorithms/sha256.c index eb9c5c0..3acc274 100644 --- a/lib/crypto-algorithms/sha256.c +++ b/lib/crypto-algorithms/sha256.c @@ -15,6 +15,7 @@ /*************************** HEADER FILES ***************************/ #include <stdlib.h> #include <memory.h> +#include <string.h> #include "sha256.h" /****************************** MACROS ******************************/ diff --git a/xcode/OLMKit.xcodeproj/project.pbxproj b/xcode/OLMKit.xcodeproj/project.pbxproj new file mode 100644 index 0000000..a3b636f --- /dev/null +++ b/xcode/OLMKit.xcodeproj/project.pbxproj @@ -0,0 +1,528 @@ +// !$*UTF8*$! +{ + archiveVersion = 1; + classes = { + }; + objectVersion = 46; + objects = { + +/* Begin PBXBuildFile section */ + 3274F6021D9A633A005282E4 /* OLMKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 3274F5F81D9A633A005282E4 /* OLMKit.framework */; }; + 3274F6071D9A633A005282E4 /* OLMKitTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 3274F6061D9A633A005282E4 /* OLMKitTests.m */; }; + 3274F6131D9A698E005282E4 /* OLMKit.h in Headers */ = {isa = PBXBuildFile; fileRef = 3274F6121D9A698E005282E4 /* OLMKit.h */; }; + 32A151311DABDD4300400192 /* OLMKitGroupTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 32A151301DABDD4300400192 /* OLMKitGroupTests.m */; }; + 7DBAD311AEA85CF6DB80DCFA /* libPods-OLMKitTests.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 7123FABE917D0FB140E036B7 /* libPods-OLMKitTests.a */; }; + D667051A0BA47E17CCC4E5D7 /* libPods-OLMKit.a in Frameworks */ = {isa = PBXBuildFile; fileRef = F2F22FE8F173AF845B882805 /* libPods-OLMKit.a */; }; +/* End PBXBuildFile section */ + +/* Begin PBXContainerItemProxy section */ + 3274F6031D9A633A005282E4 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 3274F5EF1D9A633A005282E4 /* Project object */; + proxyType = 1; + remoteGlobalIDString = 3274F5F71D9A633A005282E4; + remoteInfo = OLMKit; + }; +/* End PBXContainerItemProxy section */ + +/* Begin PBXFileReference section */ + 1B226B371526F2782C9D6372 /* Pods-OLMKit.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-OLMKit.release.xcconfig"; path = "Pods/Target Support Files/Pods-OLMKit/Pods-OLMKit.release.xcconfig"; sourceTree = "<group>"; }; + 3274F5F81D9A633A005282E4 /* OLMKit.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = OLMKit.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + 3274F5FC1D9A633A005282E4 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; }; + 3274F6011D9A633A005282E4 /* OLMKitTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = OLMKitTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; + 3274F6061D9A633A005282E4 /* OLMKitTests.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = OLMKitTests.m; sourceTree = "<group>"; }; + 3274F6081D9A633A005282E4 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; }; + 3274F6121D9A698E005282E4 /* OLMKit.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = OLMKit.h; sourceTree = "<group>"; }; + 32A151301DABDD4300400192 /* OLMKitGroupTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = OLMKitGroupTests.m; sourceTree = "<group>"; }; + 7123FABE917D0FB140E036B7 /* libPods-OLMKitTests.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-OLMKitTests.a"; sourceTree = BUILT_PRODUCTS_DIR; }; + 875BA7A520258EA15A31DD82 /* Pods-OLMKitTests.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-OLMKitTests.debug.xcconfig"; path = "Pods/Target Support Files/Pods-OLMKitTests/Pods-OLMKitTests.debug.xcconfig"; sourceTree = "<group>"; }; + D48E486DAE1F59F4F7EA8C25 /* Pods-OLMKitTests.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-OLMKitTests.release.xcconfig"; path = "Pods/Target Support Files/Pods-OLMKitTests/Pods-OLMKitTests.release.xcconfig"; sourceTree = "<group>"; }; + E50E6B16E3433A5EB3297DEE /* Pods-OLMKit.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-OLMKit.debug.xcconfig"; path = "Pods/Target Support Files/Pods-OLMKit/Pods-OLMKit.debug.xcconfig"; sourceTree = "<group>"; }; + F2F22FE8F173AF845B882805 /* libPods-OLMKit.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-OLMKit.a"; sourceTree = BUILT_PRODUCTS_DIR; }; +/* End PBXFileReference section */ + +/* Begin PBXFrameworksBuildPhase section */ + 3274F5F41D9A633A005282E4 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + D667051A0BA47E17CCC4E5D7 /* libPods-OLMKit.a in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 3274F5FE1D9A633A005282E4 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + 3274F6021D9A633A005282E4 /* OLMKit.framework in Frameworks */, + 7DBAD311AEA85CF6DB80DCFA /* libPods-OLMKitTests.a in Frameworks */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXFrameworksBuildPhase section */ + +/* Begin PBXGroup section */ + 1FA3F53DFAAAA773F07F5E56 /* Pods */ = { + isa = PBXGroup; + children = ( + E50E6B16E3433A5EB3297DEE /* Pods-OLMKit.debug.xcconfig */, + 1B226B371526F2782C9D6372 /* Pods-OLMKit.release.xcconfig */, + 875BA7A520258EA15A31DD82 /* Pods-OLMKitTests.debug.xcconfig */, + D48E486DAE1F59F4F7EA8C25 /* Pods-OLMKitTests.release.xcconfig */, + ); + name = Pods; + sourceTree = "<group>"; + }; + 3274F5EE1D9A633A005282E4 = { + isa = PBXGroup; + children = ( + 3274F5FA1D9A633A005282E4 /* OLMKit */, + 3274F6051D9A633A005282E4 /* OLMKitTests */, + 3274F5F91D9A633A005282E4 /* Products */, + 1FA3F53DFAAAA773F07F5E56 /* Pods */, + A5D2E6F079A29F7CC2A8D9FE /* Frameworks */, + ); + sourceTree = "<group>"; + }; + 3274F5F91D9A633A005282E4 /* Products */ = { + isa = PBXGroup; + children = ( + 3274F5F81D9A633A005282E4 /* OLMKit.framework */, + 3274F6011D9A633A005282E4 /* OLMKitTests.xctest */, + ); + name = Products; + sourceTree = "<group>"; + }; + 3274F5FA1D9A633A005282E4 /* OLMKit */ = { + isa = PBXGroup; + children = ( + 3274F6121D9A698E005282E4 /* OLMKit.h */, + 3274F5FC1D9A633A005282E4 /* Info.plist */, + ); + path = OLMKit; + sourceTree = "<group>"; + }; + 3274F6051D9A633A005282E4 /* OLMKitTests */ = { + isa = PBXGroup; + children = ( + 3274F6061D9A633A005282E4 /* OLMKitTests.m */, + 32A151301DABDD4300400192 /* OLMKitGroupTests.m */, + 3274F6081D9A633A005282E4 /* Info.plist */, + ); + path = OLMKitTests; + sourceTree = "<group>"; + }; + A5D2E6F079A29F7CC2A8D9FE /* Frameworks */ = { + isa = PBXGroup; + children = ( + F2F22FE8F173AF845B882805 /* libPods-OLMKit.a */, + 7123FABE917D0FB140E036B7 /* libPods-OLMKitTests.a */, + ); + name = Frameworks; + sourceTree = "<group>"; + }; +/* End PBXGroup section */ + +/* Begin PBXHeadersBuildPhase section */ + 3274F5F51D9A633A005282E4 /* Headers */ = { + isa = PBXHeadersBuildPhase; + buildActionMask = 2147483647; + files = ( + 3274F6131D9A698E005282E4 /* OLMKit.h in Headers */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXHeadersBuildPhase section */ + +/* Begin PBXNativeTarget section */ + 3274F5F71D9A633A005282E4 /* OLMKit */ = { + isa = PBXNativeTarget; + buildConfigurationList = 3274F60C1D9A633B005282E4 /* Build configuration list for PBXNativeTarget "OLMKit" */; + buildPhases = ( + 7FBCB292198F4156D9CA3B8D /* [CP] Check Pods Manifest.lock */, + 3274F5F31D9A633A005282E4 /* Sources */, + 3274F5F41D9A633A005282E4 /* Frameworks */, + 3274F5F51D9A633A005282E4 /* Headers */, + 3274F5F61D9A633A005282E4 /* Resources */, + 30F93582035CD30D211A6C76 /* [CP] Copy Pods Resources */, + ); + buildRules = ( + ); + dependencies = ( + ); + name = OLMKit; + productName = OLMKit; + productReference = 3274F5F81D9A633A005282E4 /* OLMKit.framework */; + productType = "com.apple.product-type.framework"; + }; + 3274F6001D9A633A005282E4 /* OLMKitTests */ = { + isa = PBXNativeTarget; + buildConfigurationList = 3274F60F1D9A633B005282E4 /* Build configuration list for PBXNativeTarget "OLMKitTests" */; + buildPhases = ( + 47E69E5BE6A019858DC41D4F /* [CP] Check Pods Manifest.lock */, + 3274F5FD1D9A633A005282E4 /* Sources */, + 3274F5FE1D9A633A005282E4 /* Frameworks */, + 3274F5FF1D9A633A005282E4 /* Resources */, + 0A185F0CAE96B33A4CD91B6A /* [CP] Embed Pods Frameworks */, + 793D0533290528B7C0E17CAD /* [CP] Copy Pods Resources */, + ); + buildRules = ( + ); + dependencies = ( + 3274F6041D9A633A005282E4 /* PBXTargetDependency */, + ); + name = OLMKitTests; + productName = OLMKitTests; + productReference = 3274F6011D9A633A005282E4 /* OLMKitTests.xctest */; + productType = "com.apple.product-type.bundle.unit-test"; + }; +/* End PBXNativeTarget section */ + +/* Begin PBXProject section */ + 3274F5EF1D9A633A005282E4 /* Project object */ = { + isa = PBXProject; + attributes = { + LastUpgradeCheck = 0810; + ORGANIZATIONNAME = matrix.org; + TargetAttributes = { + 3274F5F71D9A633A005282E4 = { + CreatedOnToolsVersion = 8.0; + ProvisioningStyle = Automatic; + }; + 3274F6001D9A633A005282E4 = { + CreatedOnToolsVersion = 8.0; + ProvisioningStyle = Automatic; + }; + }; + }; + buildConfigurationList = 3274F5F21D9A633A005282E4 /* Build configuration list for PBXProject "OLMKit" */; + compatibilityVersion = "Xcode 3.2"; + developmentRegion = English; + hasScannedForEncodings = 0; + knownRegions = ( + en, + ); + mainGroup = 3274F5EE1D9A633A005282E4; + productRefGroup = 3274F5F91D9A633A005282E4 /* Products */; + projectDirPath = ""; + projectRoot = ""; + targets = ( + 3274F5F71D9A633A005282E4 /* OLMKit */, + 3274F6001D9A633A005282E4 /* OLMKitTests */, + ); + }; +/* End PBXProject section */ + +/* Begin PBXResourcesBuildPhase section */ + 3274F5F61D9A633A005282E4 /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 3274F5FF1D9A633A005282E4 /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXResourcesBuildPhase section */ + +/* Begin PBXShellScriptBuildPhase section */ + 0A185F0CAE96B33A4CD91B6A /* [CP] Embed Pods Frameworks */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputPaths = ( + ); + name = "[CP] Embed Pods Frameworks"; + outputPaths = ( + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "\"${SRCROOT}/Pods/Target Support Files/Pods-OLMKitTests/Pods-OLMKitTests-frameworks.sh\"\n"; + showEnvVarsInLog = 0; + }; + 30F93582035CD30D211A6C76 /* [CP] Copy Pods Resources */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputPaths = ( + ); + name = "[CP] Copy Pods Resources"; + outputPaths = ( + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "\"${SRCROOT}/Pods/Target Support Files/Pods-OLMKit/Pods-OLMKit-resources.sh\"\n"; + showEnvVarsInLog = 0; + }; + 47E69E5BE6A019858DC41D4F /* [CP] Check Pods Manifest.lock */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputPaths = ( + ); + name = "[CP] Check Pods Manifest.lock"; + outputPaths = ( + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "diff \"${PODS_ROOT}/../Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n"; + showEnvVarsInLog = 0; + }; + 793D0533290528B7C0E17CAD /* [CP] Copy Pods Resources */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputPaths = ( + ); + name = "[CP] Copy Pods Resources"; + outputPaths = ( + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "\"${SRCROOT}/Pods/Target Support Files/Pods-OLMKitTests/Pods-OLMKitTests-resources.sh\"\n"; + showEnvVarsInLog = 0; + }; + 7FBCB292198F4156D9CA3B8D /* [CP] Check Pods Manifest.lock */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputPaths = ( + ); + name = "[CP] Check Pods Manifest.lock"; + outputPaths = ( + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "diff \"${PODS_ROOT}/../Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n"; + showEnvVarsInLog = 0; + }; +/* End PBXShellScriptBuildPhase section */ + +/* Begin PBXSourcesBuildPhase section */ + 3274F5F31D9A633A005282E4 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; + 3274F5FD1D9A633A005282E4 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 3274F6071D9A633A005282E4 /* OLMKitTests.m in Sources */, + 32A151311DABDD4300400192 /* OLMKitGroupTests.m in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXSourcesBuildPhase section */ + +/* Begin PBXTargetDependency section */ + 3274F6041D9A633A005282E4 /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = 3274F5F71D9A633A005282E4 /* OLMKit */; + targetProxy = 3274F6031D9A633A005282E4 /* PBXContainerItemProxy */; + }; +/* End PBXTargetDependency section */ + +/* Begin XCBuildConfiguration section */ + 3274F60A1D9A633B005282E4 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_ANALYZER_NONNULL = YES; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_SUSPICIOUS_MOVES = YES; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; + COPY_PHASE_STRIP = NO; + CURRENT_PROJECT_VERSION = 1; + DEBUG_INFORMATION_FORMAT = dwarf; + ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_TESTABILITY = YES; + GCC_C_LANGUAGE_STANDARD = gnu99; + GCC_DYNAMIC_NO_PIC = NO; + GCC_NO_COMMON_BLOCKS = YES; + GCC_OPTIMIZATION_LEVEL = 0; + GCC_PREPROCESSOR_DEFINITIONS = ( + "DEBUG=1", + "$(inherited)", + ); + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 10.0; + MTL_ENABLE_DEBUG_INFO = YES; + ONLY_ACTIVE_ARCH = YES; + SDKROOT = iphoneos; + TARGETED_DEVICE_FAMILY = "1,2"; + VERSIONING_SYSTEM = "apple-generic"; + VERSION_INFO_PREFIX = ""; + }; + name = Debug; + }; + 3274F60B1D9A633B005282E4 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + ALWAYS_SEARCH_USER_PATHS = NO; + CLANG_ANALYZER_NONNULL = YES; + CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x"; + CLANG_CXX_LIBRARY = "libc++"; + CLANG_ENABLE_MODULES = YES; + CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; + CLANG_WARN_DOCUMENTATION_COMMENTS = YES; + CLANG_WARN_EMPTY_BODY = YES; + CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; + CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_SUSPICIOUS_MOVE = YES; + CLANG_WARN_SUSPICIOUS_MOVES = YES; + CLANG_WARN_UNREACHABLE_CODE = YES; + CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; + COPY_PHASE_STRIP = NO; + CURRENT_PROJECT_VERSION = 1; + DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; + ENABLE_NS_ASSERTIONS = NO; + ENABLE_STRICT_OBJC_MSGSEND = YES; + GCC_C_LANGUAGE_STANDARD = gnu99; + GCC_NO_COMMON_BLOCKS = YES; + GCC_WARN_64_TO_32_BIT_CONVERSION = YES; + GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; + GCC_WARN_UNDECLARED_SELECTOR = YES; + GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; + GCC_WARN_UNUSED_FUNCTION = YES; + GCC_WARN_UNUSED_VARIABLE = YES; + IPHONEOS_DEPLOYMENT_TARGET = 10.0; + MTL_ENABLE_DEBUG_INFO = NO; + SDKROOT = iphoneos; + TARGETED_DEVICE_FAMILY = "1,2"; + VALIDATE_PRODUCT = YES; + VERSIONING_SYSTEM = "apple-generic"; + VERSION_INFO_PREFIX = ""; + }; + name = Release; + }; + 3274F60D1D9A633B005282E4 /* Debug */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = E50E6B16E3433A5EB3297DEE /* Pods-OLMKit.debug.xcconfig */; + buildSettings = { + CODE_SIGN_IDENTITY = ""; + DEFINES_MODULE = YES; + DYLIB_COMPATIBILITY_VERSION = 1; + DYLIB_CURRENT_VERSION = 1; + DYLIB_INSTALL_NAME_BASE = "@rpath"; + INFOPLIST_FILE = OLMKit/Info.plist; + INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; + LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; + PRODUCT_BUNDLE_IDENTIFIER = org.matrix.OLMKit; + PRODUCT_NAME = "$(TARGET_NAME)"; + SKIP_INSTALL = YES; + }; + name = Debug; + }; + 3274F60E1D9A633B005282E4 /* Release */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 1B226B371526F2782C9D6372 /* Pods-OLMKit.release.xcconfig */; + buildSettings = { + CODE_SIGN_IDENTITY = ""; + DEFINES_MODULE = YES; + DYLIB_COMPATIBILITY_VERSION = 1; + DYLIB_CURRENT_VERSION = 1; + DYLIB_INSTALL_NAME_BASE = "@rpath"; + INFOPLIST_FILE = OLMKit/Info.plist; + INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; + LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; + PRODUCT_BUNDLE_IDENTIFIER = org.matrix.OLMKit; + PRODUCT_NAME = "$(TARGET_NAME)"; + SKIP_INSTALL = YES; + }; + name = Release; + }; + 3274F6101D9A633B005282E4 /* Debug */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = 875BA7A520258EA15A31DD82 /* Pods-OLMKitTests.debug.xcconfig */; + buildSettings = { + INFOPLIST_FILE = OLMKitTests/Info.plist; + LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; + PRODUCT_BUNDLE_IDENTIFIER = org.matrix.OLMKitTests; + PRODUCT_NAME = "$(TARGET_NAME)"; + }; + name = Debug; + }; + 3274F6111D9A633B005282E4 /* Release */ = { + isa = XCBuildConfiguration; + baseConfigurationReference = D48E486DAE1F59F4F7EA8C25 /* Pods-OLMKitTests.release.xcconfig */; + buildSettings = { + INFOPLIST_FILE = OLMKitTests/Info.plist; + LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; + PRODUCT_BUNDLE_IDENTIFIER = org.matrix.OLMKitTests; + PRODUCT_NAME = "$(TARGET_NAME)"; + }; + name = Release; + }; +/* End XCBuildConfiguration section */ + +/* Begin XCConfigurationList section */ + 3274F5F21D9A633A005282E4 /* Build configuration list for PBXProject "OLMKit" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 3274F60A1D9A633B005282E4 /* Debug */, + 3274F60B1D9A633B005282E4 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 3274F60C1D9A633B005282E4 /* Build configuration list for PBXNativeTarget "OLMKit" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 3274F60D1D9A633B005282E4 /* Debug */, + 3274F60E1D9A633B005282E4 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; + 3274F60F1D9A633B005282E4 /* Build configuration list for PBXNativeTarget "OLMKitTests" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 3274F6101D9A633B005282E4 /* Debug */, + 3274F6111D9A633B005282E4 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; +/* End XCConfigurationList section */ + }; + rootObject = 3274F5EF1D9A633A005282E4 /* Project object */; +} diff --git a/xcode/OLMKit/Info.plist b/xcode/OLMKit/Info.plist new file mode 100644 index 0000000..d3de8ee --- /dev/null +++ b/xcode/OLMKit/Info.plist @@ -0,0 +1,26 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd"> +<plist version="1.0"> +<dict> + <key>CFBundleDevelopmentRegion</key> + <string>en</string> + <key>CFBundleExecutable</key> + <string>$(EXECUTABLE_NAME)</string> + <key>CFBundleIdentifier</key> + <string>$(PRODUCT_BUNDLE_IDENTIFIER)</string> + <key>CFBundleInfoDictionaryVersion</key> + <string>6.0</string> + <key>CFBundleName</key> + <string>$(PRODUCT_NAME)</string> + <key>CFBundlePackageType</key> + <string>FMWK</string> + <key>CFBundleShortVersionString</key> + <string>1.0</string> + <key>CFBundleSignature</key> + <string>????</string> + <key>CFBundleVersion</key> + <string>$(CURRENT_PROJECT_VERSION)</string> + <key>NSPrincipalClass</key> + <string></string> +</dict> +</plist> diff --git a/xcode/OLMKit/OLMAccount.h b/xcode/OLMKit/OLMAccount.h new file mode 100644 index 0000000..c8d65cd --- /dev/null +++ b/xcode/OLMKit/OLMAccount.h @@ -0,0 +1,51 @@ +/* + Copyright 2016 Chris Ballinger + Copyright 2016 OpenMarket Ltd + Copyright 2016 Vector Creations 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 <Foundation/Foundation.h> +#import "OLMSerializable.h" + +@class OLMSession; + +@interface OLMAccount : NSObject <OLMSerializable, NSSecureCoding> + +/** Creates new account */ +- (instancetype) initNewAccount; + +/** public identity keys. base64 encoded in "curve25519" and "ed25519" keys */ +- (NSDictionary*) identityKeys; + +/** signs message with ed25519 key for account */ +- (NSString*) signMessage:(NSData*)messageData; + +/** Public parts of the unpublished one time keys for the account */ +- (NSDictionary*) oneTimeKeys; + +- (BOOL) removeOneTimeKeysForSession:(OLMSession*)session; + +/** Marks the current set of one time keys as being published. */ +- (void) markOneTimeKeysAsPublished; + +/** The largest number of one time keys this account can store. */ +- (NSUInteger) maxOneTimeKeys; + +/** Generates a number of new one time keys. If the total number of keys stored + * by this account exceeds -maxOneTimeKeys then the old keys are + * discarded. */ +- (void) generateOneTimeKeys:(NSUInteger)numberOfKeys; + +@end diff --git a/xcode/OLMKit/OLMAccount.m b/xcode/OLMKit/OLMAccount.m new file mode 100644 index 0000000..af1e308 --- /dev/null +++ b/xcode/OLMKit/OLMAccount.m @@ -0,0 +1,265 @@ +/* + Copyright 2016 Chris Ballinger + Copyright 2016 OpenMarket Ltd + Copyright 2016 Vector Creations 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 "OLMAccount.h" +#import "OLMAccount_Private.h" +#import "OLMSession.h" +#import "OLMSession_Private.h" +#import "OLMUtility.h" + +@import Security; + +@implementation OLMAccount + +- (void) dealloc { + olm_clear_account(_account); + free(_account); +} + +- (BOOL) initializeAccountMemory { + size_t accountSize = olm_account_size(); + _account = malloc(accountSize); + NSParameterAssert(_account != nil); + if (!_account) { + return NO; + } + _account = olm_account(_account); + NSParameterAssert(_account != nil); + if (!_account) { + return NO; + } + return YES; +} + +- (instancetype) init { + self = [super init]; + if (!self) { + return nil; + } + BOOL success = [self initializeAccountMemory]; + if (!success) { + return nil; + } + return self; +} + +- (instancetype) initNewAccount { + self = [self init]; + if (!self) { + return nil; + } + size_t randomLength = olm_create_account_random_length(_account); + NSMutableData *random = [OLMUtility randomBytesOfLength:randomLength]; + size_t accountResult = olm_create_account(_account, random.mutableBytes, random.length); + [random resetBytesInRange:NSMakeRange(0, random.length)]; + if (accountResult == olm_error()) { + const char *error = olm_account_last_error(_account); + NSLog(@"error creating account: %s", error); + return nil; + } + return self; +} + +- (NSUInteger) maxOneTimeKeys { + return olm_account_max_number_of_one_time_keys(_account); +} + + +/** public identity keys */ +- (NSDictionary*) identityKeys { + size_t identityKeysLength = olm_account_identity_keys_length(_account); + uint8_t *identityKeysBytes = malloc(identityKeysLength); + if (!identityKeysBytes) { + return nil; + } + size_t result = olm_account_identity_keys(_account, identityKeysBytes, identityKeysLength); + if (result == olm_error()) { + const char *error = olm_account_last_error(_account); + NSLog(@"error getting id keys: %s", error); + free(identityKeysBytes); + return nil; + } + NSData *idKeyData = [NSData dataWithBytesNoCopy:identityKeysBytes length:identityKeysLength freeWhenDone:YES]; + NSError *error = nil; + NSDictionary *keysDictionary = [NSJSONSerialization JSONObjectWithData:idKeyData options:0 error:&error]; + if (error) { + NSLog(@"Could not decode JSON: %@", error.localizedDescription); + } + return keysDictionary; +} + +- (NSString *)signMessage:(NSData *)messageData { + size_t signatureLength = olm_account_signature_length(_account); + uint8_t *signatureBytes = malloc(signatureLength); + if (!signatureBytes) { + return nil; + } + + size_t result = olm_account_sign(_account, messageData.bytes, messageData.length, signatureBytes, signatureLength); + if (result == olm_error()) { + const char *error = olm_account_last_error(_account); + NSLog(@"error signing message: %s", error); + free(signatureBytes); + return nil; + } + + NSData *signatureData = [NSData dataWithBytesNoCopy:signatureBytes length:signatureLength freeWhenDone:YES]; + return [[NSString alloc] initWithData:signatureData encoding:NSUTF8StringEncoding]; +} + +- (NSDictionary*) oneTimeKeys { + size_t otkLength = olm_account_one_time_keys_length(_account); + uint8_t *otkBytes = malloc(otkLength); + if (!otkBytes) { + return nil; + } + size_t result = olm_account_one_time_keys(_account, otkBytes, otkLength); + if (result == olm_error()) { + const char *error = olm_account_last_error(_account); + NSLog(@"error getting id keys: %s", error); + free(otkBytes); + } + NSData *otk = [NSData dataWithBytesNoCopy:otkBytes length:otkLength freeWhenDone:YES]; + NSError *error = nil; + NSDictionary *keysDictionary = [NSJSONSerialization JSONObjectWithData:otk options:0 error:&error]; + if (error) { + NSLog(@"Could not decode JSON: %@", error.localizedDescription); + } + return keysDictionary; +} + + +- (void) generateOneTimeKeys:(NSUInteger)numberOfKeys { + size_t randomLength = olm_account_generate_one_time_keys_random_length(_account, numberOfKeys); + NSMutableData *random = [OLMUtility randomBytesOfLength:randomLength]; + size_t result = olm_account_generate_one_time_keys(_account, numberOfKeys, random.mutableBytes, random.length); + [random resetBytesInRange:NSMakeRange(0, random.length)]; + if (result == olm_error()) { + const char *error = olm_account_last_error(_account); + NSLog(@"error generating keys: %s", error); + } +} + +- (BOOL) removeOneTimeKeysForSession:(OLMSession *)session { + NSParameterAssert(session != nil); + if (!session) { + return NO; + } + size_t result = olm_remove_one_time_keys(self.account, session.session); + if (result == olm_error()) { + const char *error = olm_account_last_error(_account); + NSLog(@"olm_remove_one_time_keys error: %s", error); + return NO; + } + return YES; +} + +- (void)markOneTimeKeysAsPublished +{ + olm_account_mark_keys_as_published(self.account); +} + +#pragma mark OLMSerializable + +/** Initializes from encrypted serialized data. Will throw error if invalid key or invalid base64. */ +- (instancetype) initWithSerializedData:(NSString*)serializedData key:(NSData*)key error:(NSError**)error { + self = [self init]; + if (!self) { + return nil; + } + NSParameterAssert(key.length > 0); + NSParameterAssert(serializedData.length > 0); + if (key.length == 0 || serializedData.length == 0) { + if (error) { + *error = [NSError errorWithDomain:OLMErrorDomain code:0 userInfo:@{NSLocalizedDescriptionKey: @"Bad length."}]; + } + return nil; + } + NSMutableData *pickle = [serializedData dataUsingEncoding:NSUTF8StringEncoding].mutableCopy; + size_t result = olm_unpickle_account(_account, key.bytes, key.length, pickle.mutableBytes, pickle.length); + if (result == olm_error()) { + const char *olm_error = olm_account_last_error(_account); + NSString *errorString = [NSString stringWithUTF8String:olm_error]; + if (error && errorString) { + *error = [NSError errorWithDomain:OLMErrorDomain code:0 userInfo:@{NSLocalizedDescriptionKey: errorString}]; + } + return nil; + } + return self; +} + +/** Serializes and encrypts object data, outputs base64 blob */ +- (NSString*) serializeDataWithKey:(NSData*)key error:(NSError**)error { + NSParameterAssert(key.length > 0); + size_t length = olm_pickle_account_length(_account); + NSMutableData *pickled = [NSMutableData dataWithLength:length]; + size_t result = olm_pickle_account(_account, key.bytes, key.length, pickled.mutableBytes, pickled.length); + if (result == olm_error()) { + const char *olm_error = olm_account_last_error(_account); + NSString *errorString = [NSString stringWithUTF8String:olm_error]; + if (error && errorString) { + *error = [NSError errorWithDomain:OLMErrorDomain code:0 userInfo:@{NSLocalizedDescriptionKey: errorString}]; + } + return nil; + } + NSString *pickleString = [[NSString alloc] initWithData:pickled encoding:NSUTF8StringEncoding]; + return pickleString; +} + +#pragma mark NSSecureCoding + ++ (BOOL) supportsSecureCoding { + return YES; +} + +#pragma mark NSCoding + +- (id)initWithCoder:(NSCoder *)decoder { + NSString *version = [decoder decodeObjectOfClass:[NSString class] forKey:@"version"]; + + NSError *error = nil; + + if ([version isEqualToString:@"1"]) { + NSString *pickle = [decoder decodeObjectOfClass:[NSString class] forKey:@"pickle"]; + NSData *key = [decoder decodeObjectOfClass:[NSData class] forKey:@"key"]; + + self = [self initWithSerializedData:pickle key:key error:&error]; + } + + NSParameterAssert(error == nil); + NSParameterAssert(self != nil); + if (!self) { + return nil; + } + + return self; +} + +- (void)encodeWithCoder:(NSCoder *)encoder { + NSData *key = [OLMUtility randomBytesOfLength:32]; + NSError *error = nil; + NSString *pickle = [self serializeDataWithKey:key error:&error]; + NSParameterAssert(pickle.length > 0 && error == nil); + + [encoder encodeObject:pickle forKey:@"pickle"]; + [encoder encodeObject:key forKey:@"key"]; + [encoder encodeObject:@"1" forKey:@"version"]; +} + + +@end diff --git a/xcode/OLMKit/OLMAccount_Private.h b/xcode/OLMKit/OLMAccount_Private.h new file mode 100644 index 0000000..313ab71 --- /dev/null +++ b/xcode/OLMKit/OLMAccount_Private.h @@ -0,0 +1,25 @@ +/* + Copyright 2016 Chris Ballinger + Copyright 2016 OpenMarket Ltd + Copyright 2016 Vector Creations 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. + */ + +#include "olm/olm.h" + +@interface OLMAccount() + +@property (nonatomic) OlmAccount *account; + +@end diff --git a/xcode/OLMKit/OLMInboundGroupSession.h b/xcode/OLMKit/OLMInboundGroupSession.h new file mode 100644 index 0000000..ede68e3 --- /dev/null +++ b/xcode/OLMKit/OLMInboundGroupSession.h @@ -0,0 +1,30 @@ +/* + Copyright 2016 OpenMarket Ltd + Copyright 2016 Vector Creations 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 <Foundation/Foundation.h> +#import "OLMSerializable.h" + +@interface OLMInboundGroupSession : NSObject <OLMSerializable, NSSecureCoding> + +- (instancetype) initInboundGroupSessionWithSessionKey:(NSString*)sessionKey error:(NSError**)error; + +- (NSString*)sessionIdentifier; + +/** base64 ciphertext -> UTF-8 plaintext */ +- (NSString*)decryptMessage:(NSString*)message messageIndex:(NSUInteger*)messageIndex error:(NSError**)error; + +@end diff --git a/xcode/OLMKit/OLMInboundGroupSession.m b/xcode/OLMKit/OLMInboundGroupSession.m new file mode 100644 index 0000000..6ef51c3 --- /dev/null +++ b/xcode/OLMKit/OLMInboundGroupSession.m @@ -0,0 +1,244 @@ +/* + Copyright 2016 OpenMarket Ltd + Copyright 2016 Vector Creations 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 "OLMInboundGroupSession.h" + +#import "OLMUtility.h" +#include "olm/olm.h" + +@interface OLMInboundGroupSession () +{ + OlmInboundGroupSession *session; +} +@end + + +@implementation OLMInboundGroupSession + +- (void)dealloc { + olm_clear_inbound_group_session(session); + free(session); +} + +- (instancetype)init { + self = [super init]; + if (self) + { + session = malloc(olm_inbound_group_session_size()); + if (session) { + session = olm_inbound_group_session(session); + } + + if (!session) { + return nil; + } + } + return self; +} + +- (instancetype)initInboundGroupSessionWithSessionKey:(NSString *)sessionKey error:(NSError**)error { + self = [self init]; + if (self) { + NSData *sessionKeyData = [sessionKey dataUsingEncoding:NSUTF8StringEncoding]; + size_t result = olm_init_inbound_group_session(session, sessionKeyData.bytes, sessionKeyData.length); + if (result == olm_error()) { + const char *olm_error = olm_inbound_group_session_last_error(session); + + NSString *errorString = [NSString stringWithUTF8String:olm_error]; + NSLog(@"olm_init_inbound_group_session error: %@", errorString); + + if (error && olm_error && errorString) { + *error = [NSError errorWithDomain:OLMErrorDomain + code:0 + userInfo:@{ + NSLocalizedDescriptionKey: errorString, + NSLocalizedFailureReasonErrorKey: [NSString stringWithFormat:@"olm_init_inbound_group_session error: %@", errorString] + }]; + } + + return nil; + } + } + return self; +} + +- (NSString *)sessionIdentifier { + size_t length = olm_inbound_group_session_id_length(session); + NSMutableData *idData = [NSMutableData dataWithLength:length]; + if (!idData) { + return nil; + } + size_t result = olm_inbound_group_session_id(session, idData.mutableBytes, idData.length); + if (result == olm_error()) { + const char *error = olm_inbound_group_session_last_error(session); + NSLog(@"olm_inbound_group_session_id error: %s", error); + return nil; + } + NSString *idString = [[NSString alloc] initWithData:idData encoding:NSUTF8StringEncoding]; + return idString; +} + +- (NSString *)decryptMessage:(NSString *)message messageIndex:(NSUInteger*)messageIndex error:(NSError**)error +{ + NSParameterAssert(message != nil); + NSData *messageData = [message dataUsingEncoding:NSUTF8StringEncoding]; + if (!messageData) { + return nil; + } + NSMutableData *mutMessage = messageData.mutableCopy; + size_t maxPlaintextLength = olm_group_decrypt_max_plaintext_length(session, mutMessage.mutableBytes, mutMessage.length); + if (maxPlaintextLength == olm_error()) { + const char *olm_error = olm_inbound_group_session_last_error(session); + + NSString *errorString = [NSString stringWithUTF8String:olm_error]; + NSLog(@"olm_group_decrypt_max_plaintext_length error: %@", errorString); + + if (error && olm_error && errorString) { + *error = [NSError errorWithDomain:OLMErrorDomain + code:0 + userInfo:@{ + NSLocalizedDescriptionKey: errorString, + NSLocalizedFailureReasonErrorKey: [NSString stringWithFormat:@"olm_group_decrypt_max_plaintext_length error: %@", errorString] + }]; + } + + return nil; + } + // message buffer is destroyed by olm_group_decrypt_max_plaintext_length + mutMessage = messageData.mutableCopy; + NSMutableData *plaintextData = [NSMutableData dataWithLength:maxPlaintextLength]; + + uint32_t message_index; + size_t plaintextLength = olm_group_decrypt(session, mutMessage.mutableBytes, mutMessage.length, plaintextData.mutableBytes, plaintextData.length, &message_index); + if (plaintextLength == olm_error()) { + const char *olm_error = olm_inbound_group_session_last_error(session); + + NSString *errorString = [NSString stringWithUTF8String:olm_error]; + NSLog(@"olm_group_decrypt error: %@", errorString); + + if (error && olm_error && errorString) { + *error = [NSError errorWithDomain:OLMErrorDomain + code:0 + userInfo:@{ + NSLocalizedDescriptionKey: errorString, + NSLocalizedFailureReasonErrorKey: [NSString stringWithFormat:@"olm_group_decrypt error: %@", errorString] + }]; + } + + return nil; + } + plaintextData.length = plaintextLength; + NSString *plaintext = [[NSString alloc] initWithData:plaintextData encoding:NSUTF8StringEncoding]; + [plaintextData resetBytesInRange:NSMakeRange(0, plaintextData.length)]; + + if (messageIndex) + { + *messageIndex = message_index; + } + + return plaintext; +} + + +#pragma mark OLMSerializable + +/** Initializes from encrypted serialized data. Will throw error if invalid key or invalid base64. */ +- (instancetype) initWithSerializedData:(NSString *)serializedData key:(NSData *)key error:(NSError *__autoreleasing *)error { + self = [self init]; + if (!self) { + return nil; + } + NSParameterAssert(key.length > 0); + NSParameterAssert(serializedData.length > 0); + if (key.length == 0 || serializedData.length == 0) { + if (error) { + *error = [NSError errorWithDomain:OLMErrorDomain code:0 userInfo:@{NSLocalizedDescriptionKey: @"Bad length."}]; + } + return nil; + } + NSMutableData *pickle = [serializedData dataUsingEncoding:NSUTF8StringEncoding].mutableCopy; + size_t result = olm_unpickle_inbound_group_session(session, key.bytes, key.length, pickle.mutableBytes, pickle.length); + if (result == olm_error()) { + const char *olm_error = olm_inbound_group_session_last_error(session); + NSString *errorString = [NSString stringWithUTF8String:olm_error]; + if (error && errorString) { + *error = [NSError errorWithDomain:OLMErrorDomain code:0 userInfo:@{NSLocalizedDescriptionKey: errorString}]; + } + return nil; + } + return self; +} + +/** Serializes and encrypts object data, outputs base64 blob */ +- (NSString*) serializeDataWithKey:(NSData*)key error:(NSError**)error { + NSParameterAssert(key.length > 0); + size_t length = olm_pickle_inbound_group_session_length(session); + NSMutableData *pickled = [NSMutableData dataWithLength:length]; + size_t result = olm_pickle_inbound_group_session(session, key.bytes, key.length, pickled.mutableBytes, pickled.length); + if (result == olm_error()) { + const char *olm_error = olm_inbound_group_session_last_error(session); + NSString *errorString = [NSString stringWithUTF8String:olm_error]; + if (error && errorString) { + *error = [NSError errorWithDomain:OLMErrorDomain code:0 userInfo:@{NSLocalizedDescriptionKey: errorString}]; + } + return nil; + } + NSString *pickleString = [[NSString alloc] initWithData:pickled encoding:NSUTF8StringEncoding]; + return pickleString; +} + +#pragma mark NSSecureCoding + ++ (BOOL) supportsSecureCoding { + return YES; +} + +#pragma mark NSCoding + +- (id)initWithCoder:(NSCoder *)decoder { + NSString *version = [decoder decodeObjectOfClass:[NSString class] forKey:@"version"]; + + NSError *error = nil; + + if ([version isEqualToString:@"1"]) { + NSString *pickle = [decoder decodeObjectOfClass:[NSString class] forKey:@"pickle"]; + NSData *key = [decoder decodeObjectOfClass:[NSData class] forKey:@"key"]; + + self = [self initWithSerializedData:pickle key:key error:&error]; + } + + NSParameterAssert(error == nil); + NSParameterAssert(self != nil); + if (!self) { + return nil; + } + + return self; +} + +- (void)encodeWithCoder:(NSCoder *)encoder { + NSData *key = [OLMUtility randomBytesOfLength:32]; + NSError *error = nil; + NSString *pickle = [self serializeDataWithKey:key error:&error]; + NSParameterAssert(pickle.length > 0 && error == nil); + + [encoder encodeObject:pickle forKey:@"pickle"]; + [encoder encodeObject:key forKey:@"key"]; + [encoder encodeObject:@"1" forKey:@"version"]; +} + +@end diff --git a/xcode/OLMKit/OLMKit.h b/xcode/OLMKit/OLMKit.h new file mode 100644 index 0000000..34db111 --- /dev/null +++ b/xcode/OLMKit/OLMKit.h @@ -0,0 +1,31 @@ +/* + Copyright 2016 Chris Ballinger + Copyright 2016 OpenMarket Ltd + Copyright 2016 Vector Creations 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 <UIKit/UIKit.h> + +//! Project version string for OLMKit, the same as libolm. +NSString *OLMKitVersionString(); + +// In this header, you should import all the public headers of your framework using statements like #import <OLMKit/PublicHeader.h> + +#import <OLMKit/OLMAccount.h> +#import <OLMKit/OLMSession.h> +#import <OLMKit/OLMMessage.h> +#import <OLMKit/OLMUtility.h> +#import <OLMKit/OLMInboundGroupSession.h> +#import <OLMKit/OLMOutboundGroupSession.h> diff --git a/xcode/OLMKit/OLMKit.m b/xcode/OLMKit/OLMKit.m new file mode 100644 index 0000000..e7bfd25 --- /dev/null +++ b/xcode/OLMKit/OLMKit.m @@ -0,0 +1,29 @@ +/* + Copyright 2016 OpenMarket Ltd + Copyright 2016 Vector Creations 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 "OLMKit.h" + +#include "olm/olm.h" + +NSString *OLMKitVersionString() +{ + uint8_t major, minor, patch; + + olm_get_library_version(&major, &minor, &patch); + + return [NSString stringWithFormat:@"%tu.%tu.%tu", major, minor, patch]; +} diff --git a/xcode/OLMKit/OLMMessage.h b/xcode/OLMKit/OLMMessage.h new file mode 100644 index 0000000..b6e8c8f --- /dev/null +++ b/xcode/OLMKit/OLMMessage.h @@ -0,0 +1,38 @@ +/* + Copyright 2016 Chris Ballinger + Copyright 2016 OpenMarket Ltd + Copyright 2016 Vector Creations 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 <Foundation/Foundation.h> + +/* + from olm.hh + static const size_t OLM_MESSAGE_TYPE_PRE_KEY = 0; + static const size_t OLM_MESSAGE_TYPE_MESSAGE = 1; + */ +typedef NS_ENUM(NSInteger, OLMMessageType) { + OLMMessageTypePreKey = 0, + OLMMessageTypeMessage = 1 +}; + +@interface OLMMessage : NSObject + +@property (nonatomic, copy, readonly, nonnull) NSString *ciphertext; +@property (readonly) OLMMessageType type; + +- (nullable instancetype) initWithCiphertext:(nonnull NSString*)ciphertext type:(OLMMessageType)type; + +@end diff --git a/xcode/OLMKit/OLMMessage.m b/xcode/OLMKit/OLMMessage.m new file mode 100644 index 0000000..949f834 --- /dev/null +++ b/xcode/OLMKit/OLMMessage.m @@ -0,0 +1,34 @@ +/* + Copyright 2016 Chris Ballinger + Copyright 2016 OpenMarket Ltd + Copyright 2016 Vector Creations 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 "OLMMessage.h" + +@implementation OLMMessage + +- (nullable instancetype) initWithCiphertext:(nonnull NSString*)ciphertext type:(OLMMessageType)type { + NSParameterAssert(ciphertext != nil); + self = [super init]; + if (!self) { + return nil; + } + _ciphertext = [ciphertext copy]; + _type = type; + return self; +} + +@end diff --git a/xcode/OLMKit/OLMOutboundGroupSession.h b/xcode/OLMKit/OLMOutboundGroupSession.h new file mode 100644 index 0000000..c979b61 --- /dev/null +++ b/xcode/OLMKit/OLMOutboundGroupSession.h @@ -0,0 +1,32 @@ +/* + Copyright 2016 OpenMarket Ltd + Copyright 2016 Vector Creations 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 <Foundation/Foundation.h> +#import "OLMSerializable.h" + +@interface OLMOutboundGroupSession : NSObject <OLMSerializable, NSSecureCoding> + +- (instancetype) initOutboundGroupSession; + +- (NSString*)sessionIdentifier; +- (NSUInteger)messageIndex; +- (NSString*)sessionKey; + +/** UTF-8 plaintext -> base64 ciphertext */ +- (NSString*)encryptMessage:(NSString*)message error:(NSError**)error; + +@end diff --git a/xcode/OLMKit/OLMOutboundGroupSession.m b/xcode/OLMKit/OLMOutboundGroupSession.m new file mode 100644 index 0000000..a3421fd --- /dev/null +++ b/xcode/OLMKit/OLMOutboundGroupSession.m @@ -0,0 +1,220 @@ +/* + Copyright 2016 OpenMarket Ltd + Copyright 2016 Vector Creations 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 "OLMOutboundGroupSession.h" + +#import "OLMUtility.h" +#include "olm/olm.h" + +@interface OLMOutboundGroupSession () +{ + OlmOutboundGroupSession *session; +} +@end + +@implementation OLMOutboundGroupSession + +- (void)dealloc { + olm_clear_outbound_group_session(session); + free(session); +} + +- (instancetype)init { + self = [super init]; + if (self) + { + session = malloc(olm_outbound_group_session_size()); + if (session) { + session = olm_outbound_group_session(session); + } + + if (!session) { + return nil; + } + } + return self; +} + +- (instancetype)initOutboundGroupSession { + self = [self init]; + if (self) { + NSMutableData *random = [OLMUtility randomBytesOfLength:olm_init_outbound_group_session_random_length(session)]; + + size_t result = olm_init_outbound_group_session(session, random.mutableBytes, random.length); + [random resetBytesInRange:NSMakeRange(0, random.length)]; + if (result == olm_error()) { + const char *error = olm_outbound_group_session_last_error(session); + NSLog(@"olm_init_outbound_group_session error: %s", error); + return nil; + } + } + return self; +} + +- (NSString *)sessionIdentifier { + size_t length = olm_outbound_group_session_id_length(session); + NSMutableData *idData = [NSMutableData dataWithLength:length]; + if (!idData) { + return nil; + } + size_t result = olm_outbound_group_session_id(session, idData.mutableBytes, idData.length); + if (result == olm_error()) { + const char *error = olm_outbound_group_session_last_error(session); + NSLog(@"olm_outbound_group_session_id error: %s", error); + return nil; + } + NSString *idString = [[NSString alloc] initWithData:idData encoding:NSUTF8StringEncoding]; + return idString; +} + +- (NSUInteger)messageIndex { + return olm_outbound_group_session_message_index(session); +} + +- (NSString *)sessionKey { + size_t length = olm_outbound_group_session_key_length(session); + NSMutableData *sessionKeyData = [NSMutableData dataWithLength:length]; + if (!sessionKeyData) { + return nil; + } + size_t result = olm_outbound_group_session_key(session, sessionKeyData.mutableBytes, sessionKeyData.length); + if (result == olm_error()) { + const char *error = olm_outbound_group_session_last_error(session); + NSLog(@"olm_outbound_group_session_key error: %s", error); + return nil; + } + NSString *sessionKey = [[NSString alloc] initWithData:sessionKeyData encoding:NSUTF8StringEncoding]; + [sessionKeyData resetBytesInRange:NSMakeRange(0, sessionKeyData.length)]; + return sessionKey; +} + +- (NSString *)encryptMessage:(NSString *)message error:(NSError**)error { + NSData *plaintextData = [message dataUsingEncoding:NSUTF8StringEncoding]; + size_t ciphertextLength = olm_group_encrypt_message_length(session, plaintextData.length); + NSMutableData *ciphertext = [NSMutableData dataWithLength:ciphertextLength]; + if (!ciphertext) { + return nil; + } + size_t result = olm_group_encrypt(session, plaintextData.bytes, plaintextData.length, ciphertext.mutableBytes, ciphertext.length); + if (result == olm_error()) { + const char *olm_error = olm_outbound_group_session_last_error(session); + + NSString *errorString = [NSString stringWithUTF8String:olm_error]; + NSLog(@"olm_group_encrypt error: %@", errorString); + + if (error && olm_error && errorString) { + *error = [NSError errorWithDomain:OLMErrorDomain + code:0 + userInfo:@{ + NSLocalizedDescriptionKey: errorString, + NSLocalizedFailureReasonErrorKey: [NSString stringWithFormat:@"olm_group_encrypt error: %@", errorString] + }]; + } + + return nil; + } + return [[NSString alloc] initWithData:ciphertext encoding:NSUTF8StringEncoding]; +} + +#pragma mark OLMSerializable + +/** Initializes from encrypted serialized data. Will throw error if invalid key or invalid base64. */ +- (instancetype) initWithSerializedData:(NSString *)serializedData key:(NSData *)key error:(NSError *__autoreleasing *)error { + self = [self init]; + if (!self) { + return nil; + } + NSParameterAssert(key.length > 0); + NSParameterAssert(serializedData.length > 0); + if (key.length == 0 || serializedData.length == 0) { + if (error) { + *error = [NSError errorWithDomain:OLMErrorDomain code:0 userInfo:@{NSLocalizedDescriptionKey: @"Bad length."}]; + } + return nil; + } + NSMutableData *pickle = [serializedData dataUsingEncoding:NSUTF8StringEncoding].mutableCopy; + size_t result = olm_unpickle_outbound_group_session(session, key.bytes, key.length, pickle.mutableBytes, pickle.length); + if (result == olm_error()) { + const char *olm_error = olm_outbound_group_session_last_error(session); + NSString *errorString = [NSString stringWithUTF8String:olm_error]; + if (error && errorString) { + *error = [NSError errorWithDomain:OLMErrorDomain code:0 userInfo:@{NSLocalizedDescriptionKey: errorString}]; + } + return nil; + } + return self; +} + +/** Serializes and encrypts object data, outputs base64 blob */ +- (NSString*) serializeDataWithKey:(NSData*)key error:(NSError**)error { + NSParameterAssert(key.length > 0); + size_t length = olm_pickle_outbound_group_session_length(session); + NSMutableData *pickled = [NSMutableData dataWithLength:length]; + size_t result = olm_pickle_outbound_group_session(session, key.bytes, key.length, pickled.mutableBytes, pickled.length); + if (result == olm_error()) { + const char *olm_error = olm_outbound_group_session_last_error(session); + NSString *errorString = [NSString stringWithUTF8String:olm_error]; + if (error && errorString) { + *error = [NSError errorWithDomain:OLMErrorDomain code:0 userInfo:@{NSLocalizedDescriptionKey: errorString}]; + } + return nil; + } + NSString *pickleString = [[NSString alloc] initWithData:pickled encoding:NSUTF8StringEncoding]; + return pickleString; +} + +#pragma mark NSSecureCoding + ++ (BOOL) supportsSecureCoding { + return YES; +} + +#pragma mark NSCoding + +- (id)initWithCoder:(NSCoder *)decoder { + NSString *version = [decoder decodeObjectOfClass:[NSString class] forKey:@"version"]; + + NSError *error = nil; + + if ([version isEqualToString:@"1"]) { + NSString *pickle = [decoder decodeObjectOfClass:[NSString class] forKey:@"pickle"]; + NSData *key = [decoder decodeObjectOfClass:[NSData class] forKey:@"key"]; + + self = [self initWithSerializedData:pickle key:key error:&error]; + } + + NSParameterAssert(error == nil); + NSParameterAssert(self != nil); + if (!self) { + return nil; + } + + return self; +} + +- (void)encodeWithCoder:(NSCoder *)encoder { + NSData *key = [OLMUtility randomBytesOfLength:32]; + NSError *error = nil; + NSString *pickle = [self serializeDataWithKey:key error:&error]; + NSParameterAssert(pickle.length > 0 && error == nil); + + [encoder encodeObject:pickle forKey:@"pickle"]; + [encoder encodeObject:key forKey:@"key"]; + [encoder encodeObject:@"1" forKey:@"version"]; +} + +@end diff --git a/xcode/OLMKit/OLMSerializable.h b/xcode/OLMKit/OLMSerializable.h new file mode 100644 index 0000000..e929903 --- /dev/null +++ b/xcode/OLMKit/OLMSerializable.h @@ -0,0 +1,29 @@ +/* + Copyright 2016 Chris Ballinger + Copyright 2016 OpenMarket Ltd + Copyright 2016 Vector Creations 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 <Foundation/Foundation.h> + +@protocol OLMSerializable <NSObject> + +/** Initializes from encrypted serialized data. Will throw error if invalid key or invalid base64. */ +- (instancetype) initWithSerializedData:(NSString*)serializedData key:(NSData*)key error:(NSError**)error; + +/** Serializes and encrypts object data, outputs base64 blob */ +- (NSString*) serializeDataWithKey:(NSData*)key error:(NSError**)error; + +@end diff --git a/xcode/OLMKit/OLMSession.h b/xcode/OLMKit/OLMSession.h new file mode 100644 index 0000000..0446f98 --- /dev/null +++ b/xcode/OLMKit/OLMSession.h @@ -0,0 +1,44 @@ +/* + Copyright 2016 Chris Ballinger + Copyright 2016 OpenMarket Ltd + Copyright 2016 Vector Creations 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 <Foundation/Foundation.h> +#import "OLMSerializable.h" +#import "OLMAccount.h" +#import "OLMMessage.h" + +@interface OLMSession : NSObject <OLMSerializable, NSSecureCoding> + +- (instancetype) initOutboundSessionWithAccount:(OLMAccount*)account theirIdentityKey:(NSString*)theirIdentityKey theirOneTimeKey:(NSString*)theirOneTimeKey error:(NSError**)error; + +- (instancetype) initInboundSessionWithAccount:(OLMAccount*)account oneTimeKeyMessage:(NSString*)oneTimeKeyMessage error:(NSError**)error; + +- (instancetype) initInboundSessionWithAccount:(OLMAccount*)account theirIdentityKey:(NSString*)theirIdentityKey oneTimeKeyMessage:(NSString*)oneTimeKeyMessage error:(NSError**)error; + +- (NSString*) sessionIdentifier; + +- (BOOL) matchesInboundSession:(NSString*)oneTimeKeyMessage; + +- (BOOL) matchesInboundSessionFrom:(NSString*)theirIdentityKey oneTimeKeyMessage:(NSString *)oneTimeKeyMessage; + +/** UTF-8 plaintext -> base64 ciphertext */ +- (OLMMessage*) encryptMessage:(NSString*)message error:(NSError**)error; + +/** base64 ciphertext -> UTF-8 plaintext */ +- (NSString*) decryptMessage:(OLMMessage*)message error:(NSError**)error; + +@end diff --git a/xcode/OLMKit/OLMSession.m b/xcode/OLMKit/OLMSession.m new file mode 100644 index 0000000..8c29113 --- /dev/null +++ b/xcode/OLMKit/OLMSession.m @@ -0,0 +1,381 @@ +/* + Copyright 2016 Chris Ballinger + Copyright 2016 OpenMarket Ltd + Copyright 2016 Vector Creations 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 "OLMSession.h" +#import "OLMUtility.h" +#import "OLMAccount_Private.h" +#import "OLMSession_Private.h" +#include "olm/olm.h" + +@implementation OLMSession + +- (void) dealloc { + olm_clear_session(_session); + free(_session); +} + +- (BOOL) initializeSessionMemory { + size_t size = olm_session_size(); + _session = malloc(size); + NSParameterAssert(_session != nil); + if (!_session) { + return NO; + } + _session = olm_session(_session); + NSParameterAssert(_session != nil); + if (!_session) { + return NO; + } + return YES; +} + +- (instancetype) init { + self = [super init]; + if (!self) { + return nil; + } + BOOL success = [self initializeSessionMemory]; + if (!success) { + return nil; + } + return self; +} + +- (instancetype) initWithAccount:(OLMAccount*)account { + self = [self init]; + if (!self) { + return nil; + } + NSParameterAssert(account != nil && account.account != NULL); + if (account == nil || account.account == NULL) { + return nil; + } + _account = account; + return self; +} + +- (instancetype) initOutboundSessionWithAccount:(OLMAccount*)account theirIdentityKey:(NSString*)theirIdentityKey theirOneTimeKey:(NSString*)theirOneTimeKey error:(NSError**)error { + self = [self initWithAccount:account]; + if (!self) { + return nil; + } + NSMutableData *random = [OLMUtility randomBytesOfLength:olm_create_outbound_session_random_length(_session)]; + NSData *idKey = [theirIdentityKey dataUsingEncoding:NSUTF8StringEncoding]; + NSData *otKey = [theirOneTimeKey dataUsingEncoding:NSUTF8StringEncoding]; + size_t result = olm_create_outbound_session(_session, account.account, idKey.bytes, idKey.length, otKey.bytes, otKey.length, random.mutableBytes, random.length); + [random resetBytesInRange:NSMakeRange(0, random.length)]; + if (result == olm_error()) { + const char *olm_error = olm_session_last_error(_session); + + NSString *errorString = [NSString stringWithUTF8String:olm_error]; + NSLog(@"olm_create_outbound_session error: %@", errorString); + + if (error && olm_error && errorString) { + *error = [NSError errorWithDomain:OLMErrorDomain + code:0 + userInfo:@{ + NSLocalizedDescriptionKey: errorString, + NSLocalizedFailureReasonErrorKey: [NSString stringWithFormat:@"olm_create_outbound_session error: %@", errorString] + }]; + } + + return nil; + } + return self; +} + +- (instancetype) initInboundSessionWithAccount:(OLMAccount*)account oneTimeKeyMessage:(NSString*)oneTimeKeyMessage error:(NSError**)error { + self = [self initWithAccount:account]; + if (!self) { + return nil; + } + NSMutableData *otk = [NSMutableData dataWithData:[oneTimeKeyMessage dataUsingEncoding:NSUTF8StringEncoding]]; + size_t result = olm_create_inbound_session(_session, account.account, otk.mutableBytes, oneTimeKeyMessage.length); + if (result == olm_error()) { + const char *olm_error = olm_session_last_error(_session); + + NSString *errorString = [NSString stringWithUTF8String:olm_error]; + NSLog(@"olm_create_inbound_session error: %@", errorString); + + if (error && olm_error && errorString) { + *error = [NSError errorWithDomain:OLMErrorDomain + code:0 + userInfo:@{ + NSLocalizedDescriptionKey: errorString, + NSLocalizedFailureReasonErrorKey: [NSString stringWithFormat:@"olm_create_inbound_session error: %@", errorString] + }]; + } + + return nil; + } + return self; +} + +- (instancetype) initInboundSessionWithAccount:(OLMAccount*)account theirIdentityKey:(NSString*)theirIdentityKey oneTimeKeyMessage:(NSString*)oneTimeKeyMessage error:(NSError**)error { + self = [self initWithAccount:account]; + if (!self) { + return nil; + } + NSData *idKey = [theirIdentityKey dataUsingEncoding:NSUTF8StringEncoding]; + NSMutableData *otk = [NSMutableData dataWithData:[oneTimeKeyMessage dataUsingEncoding:NSUTF8StringEncoding]]; + size_t result = olm_create_inbound_session_from(_session, account.account, idKey.bytes, idKey.length, otk.mutableBytes, otk.length); + if (result == olm_error()) { + const char *olm_error = olm_session_last_error(_session); + + NSString *errorString = [NSString stringWithUTF8String:olm_error]; + NSLog(@"olm_create_inbound_session_from error: %@", errorString); + + if (error && olm_error && errorString) { + *error = [NSError errorWithDomain:OLMErrorDomain + code:0 + userInfo:@{ + NSLocalizedDescriptionKey: errorString, + NSLocalizedFailureReasonErrorKey: [NSString stringWithFormat:@"olm_create_inbound_session_from error: %@", errorString] + }]; + } + + return nil; + } + return self; +} + +- (NSString*) sessionIdentifier { + size_t length = olm_session_id_length(_session); + NSMutableData *idData = [NSMutableData dataWithLength:length]; + if (!idData) { + return nil; + } + size_t result = olm_session_id(_session, idData.mutableBytes, idData.length); + if (result == olm_error()) { + const char *error = olm_session_last_error(_session); + NSLog(@"olm_session_id error: %s", error); + return nil; + } + NSString *idString = [[NSString alloc] initWithData:idData encoding:NSUTF8StringEncoding]; + return idString; +} + +- (BOOL)matchesInboundSession:(NSString *)oneTimeKeyMessage { + NSMutableData *otk = [NSMutableData dataWithData:[oneTimeKeyMessage dataUsingEncoding:NSUTF8StringEncoding]]; + + size_t result = olm_matches_inbound_session(_session, otk.mutableBytes, otk.length); + if (result == 1) { + return YES; + } + else { + if (result == olm_error()) { + const char *error = olm_session_last_error(_session); + NSLog(@"olm_matches_inbound_session error: %s", error); + } + return NO; + } +} + +- (BOOL)matchesInboundSessionFrom:(NSString *)theirIdentityKey oneTimeKeyMessage:(NSString *)oneTimeKeyMessage { + NSData *idKey = [theirIdentityKey dataUsingEncoding:NSUTF8StringEncoding]; + NSMutableData *otk = [NSMutableData dataWithData:[oneTimeKeyMessage dataUsingEncoding:NSUTF8StringEncoding]]; + + size_t result = olm_matches_inbound_session_from(_session, + idKey.bytes, idKey.length, + otk.mutableBytes, otk.length); + if (result == 1) { + return YES; + } + else { + if (result == olm_error()) { + const char *error = olm_session_last_error(_session); + NSLog(@"olm_matches_inbound_session error: %s", error); + } + return NO; + } +} + +- (OLMMessage*) encryptMessage:(NSString*)message error:(NSError**)error { + size_t messageType = olm_encrypt_message_type(_session); + size_t randomLength = olm_encrypt_random_length(_session); + NSMutableData *random = [OLMUtility randomBytesOfLength:randomLength]; + NSData *plaintextData = [message dataUsingEncoding:NSUTF8StringEncoding]; + size_t ciphertextLength = olm_encrypt_message_length(_session, plaintextData.length); + NSMutableData *ciphertext = [NSMutableData dataWithLength:ciphertextLength]; + if (!ciphertext) { + return nil; + } + size_t result = olm_encrypt(_session, plaintextData.bytes, plaintextData.length, random.mutableBytes, random.length, ciphertext.mutableBytes, ciphertext.length); + [random resetBytesInRange:NSMakeRange(0, random.length)]; + if (result == olm_error()) { + const char *olm_error = olm_session_last_error(_session); + + NSString *errorString = [NSString stringWithUTF8String:olm_error]; + NSLog(@"olm_encrypt error: %@", errorString); + + if (error && olm_error && errorString) { + *error = [NSError errorWithDomain:OLMErrorDomain + code:0 + userInfo:@{ + NSLocalizedDescriptionKey: errorString, + NSLocalizedFailureReasonErrorKey: [NSString stringWithFormat:@"olm_encrypt error: %@", errorString] + }]; + } + + return nil; + } + NSString *ciphertextString = [[NSString alloc] initWithData:ciphertext encoding:NSUTF8StringEncoding]; + OLMMessage *encryptedMessage = [[OLMMessage alloc] initWithCiphertext:ciphertextString type:messageType]; + return encryptedMessage; +} + +- (NSString*) decryptMessage:(OLMMessage*)message error:(NSError**)error { + NSParameterAssert(message != nil); + NSData *messageData = [message.ciphertext dataUsingEncoding:NSUTF8StringEncoding]; + if (!messageData) { + return nil; + } + NSMutableData *mutMessage = messageData.mutableCopy; + size_t maxPlaintextLength = olm_decrypt_max_plaintext_length(_session, message.type, mutMessage.mutableBytes, mutMessage.length); + if (maxPlaintextLength == olm_error()) { + const char *olm_error = olm_session_last_error(_session); + + NSString *errorString = [NSString stringWithUTF8String:olm_error]; + NSLog(@"olm_decrypt_max_plaintext_length error: %@", errorString); + + if (error && olm_error && errorString) { + *error = [NSError errorWithDomain:OLMErrorDomain + code:0 + userInfo:@{ + NSLocalizedDescriptionKey: errorString, + NSLocalizedFailureReasonErrorKey: [NSString stringWithFormat:@"olm_decrypt_max_plaintext_length error: %@", errorString] + }]; + } + + return nil; + } + // message buffer is destroyed by olm_decrypt_max_plaintext_length + mutMessage = messageData.mutableCopy; + NSMutableData *plaintextData = [NSMutableData dataWithLength:maxPlaintextLength]; + size_t plaintextLength = olm_decrypt(_session, message.type, mutMessage.mutableBytes, mutMessage.length, plaintextData.mutableBytes, plaintextData.length); + if (plaintextLength == olm_error()) { + const char *olm_error = olm_session_last_error(_session); + + NSString *errorString = [NSString stringWithUTF8String:olm_error]; + NSLog(@"olm_decrypt error: %@", errorString); + + if (error && olm_error && errorString) { + *error = [NSError errorWithDomain:OLMErrorDomain + code:0 + userInfo:@{ + NSLocalizedDescriptionKey: errorString, + NSLocalizedFailureReasonErrorKey: [NSString stringWithFormat:@"olm_decrypt error: %@", errorString] + }]; + } + + return nil; + } + plaintextData.length = plaintextLength; + NSString *plaintext = [[NSString alloc] initWithData:plaintextData encoding:NSUTF8StringEncoding]; + [plaintextData resetBytesInRange:NSMakeRange(0, plaintextData.length)]; + return plaintext; +} + +#pragma mark OLMSerializable + +/** Initializes from encrypted serialized data. Will throw error if invalid key or invalid base64. */ +- (instancetype) initWithSerializedData:(NSString*)serializedData key:(NSData*)key error:(NSError**)error { + self = [self init]; + if (!self) { + return nil; + } + NSParameterAssert(key.length > 0); + NSParameterAssert(serializedData.length > 0); + if (key.length == 0 || serializedData.length == 0) { + if (error) { + *error = [NSError errorWithDomain:OLMErrorDomain code:0 userInfo:@{NSLocalizedDescriptionKey: @"Bad length."}]; + } + return nil; + } + NSMutableData *pickle = [serializedData dataUsingEncoding:NSUTF8StringEncoding].mutableCopy; + size_t result = olm_unpickle_session(_session, key.bytes, key.length, pickle.mutableBytes, pickle.length); + if (result == olm_error()) { + const char *olm_error = olm_session_last_error(_session); + NSString *errorString = [NSString stringWithUTF8String:olm_error]; + if (error && errorString) { + *error = [NSError errorWithDomain:OLMErrorDomain code:0 userInfo:@{NSLocalizedDescriptionKey: errorString}]; + } + return nil; + } + return self; +} + +/** Serializes and encrypts object data, outputs base64 blob */ +- (NSString*) serializeDataWithKey:(NSData*)key error:(NSError**)error { + NSParameterAssert(key.length > 0); + size_t length = olm_pickle_session_length(_session); + NSMutableData *pickled = [NSMutableData dataWithLength:length]; + size_t result = olm_pickle_session(_session, key.bytes, key.length, pickled.mutableBytes, pickled.length); + if (result == olm_error()) { + const char *olm_error = olm_session_last_error(_session); + NSString *errorString = [NSString stringWithUTF8String:olm_error]; + if (error && errorString) { + *error = [NSError errorWithDomain:OLMErrorDomain code:0 userInfo:@{NSLocalizedDescriptionKey: errorString}]; + } + return nil; + } + NSString *pickleString = [[NSString alloc] initWithData:pickled encoding:NSUTF8StringEncoding]; + return pickleString; +} + +#pragma mark NSSecureCoding + ++ (BOOL) supportsSecureCoding { + return YES; +} + +#pragma mark NSCoding + +- (id)initWithCoder:(NSCoder *)decoder { + NSString *version = [decoder decodeObjectOfClass:[NSString class] forKey:@"version"]; + + NSError *error = nil; + + if ([version isEqualToString:@"1"]) { + NSString *pickle = [decoder decodeObjectOfClass:[NSString class] forKey:@"pickle"]; + NSData *key = [decoder decodeObjectOfClass:[NSData class] forKey:@"key"]; + + self = [self initWithSerializedData:pickle key:key error:&error]; + } + + NSParameterAssert(error == nil); + NSParameterAssert(self != nil); + if (!self) { + return nil; + } + + return self; +} + +- (void)encodeWithCoder:(NSCoder *)encoder { + NSData *key = [OLMUtility randomBytesOfLength:32]; + NSError *error = nil; + NSString *pickle = [self serializeDataWithKey:key error:&error]; + NSParameterAssert(pickle.length > 0 && error == nil); + + [encoder encodeObject:pickle forKey:@"pickle"]; + [encoder encodeObject:key forKey:@"key"]; + [encoder encodeObject:@"1" forKey:@"version"]; +} + +@end diff --git a/xcode/OLMKit/OLMSession_Private.h b/xcode/OLMKit/OLMSession_Private.h new file mode 100644 index 0000000..28ba5e1 --- /dev/null +++ b/xcode/OLMKit/OLMSession_Private.h @@ -0,0 +1,26 @@ +/* + Copyright 2016 Chris Ballinger + Copyright 2016 OpenMarket Ltd + Copyright 2016 Vector Creations 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. + */ + +#include "olm/olm.h" + +@interface OLMSession() + +@property (nonatomic) OlmSession *session; +@property (nonatomic, strong) OLMAccount *account; + +@end diff --git a/xcode/OLMKit/OLMUtility.h b/xcode/OLMKit/OLMUtility.h new file mode 100644 index 0000000..22e9724 --- /dev/null +++ b/xcode/OLMKit/OLMUtility.h @@ -0,0 +1,49 @@ +/* + Copyright 2016 Chris Ballinger + Copyright 2016 OpenMarket Ltd + Copyright 2016 Vector Creations 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 <Foundation/Foundation.h> + +FOUNDATION_EXPORT NSString *const OLMErrorDomain; + +@interface OLMUtility : NSObject + +/** + Calculate the SHA-256 hash of the input and encodes it as base64. + + @param message the message to hash. + @return the base64-encoded hash value. + */ +- (NSString*)sha256:(NSData*)message; + +/** + Verify an ed25519 signature. + + @param signature the base64-encoded signature to be checked. + @param key the ed25519 key. + @param message the message which was signed. + @param the result error if there is a problem with the verification. + If the key was too small then the message will be "OLM.INVALID_BASE64". + If the signature was invalid then the message will be "OLM.BAD_MESSAGE_MAC". + + @return YES if valid. + */ +- (BOOL)verifyEd25519Signature:(NSString*)signature key:(NSString*)key message:(NSData*)message error:(NSError**)error; + ++ (NSMutableData*) randomBytesOfLength:(NSUInteger)length; + +@end diff --git a/xcode/OLMKit/OLMUtility.m b/xcode/OLMKit/OLMUtility.m new file mode 100644 index 0000000..936785a --- /dev/null +++ b/xcode/OLMKit/OLMUtility.m @@ -0,0 +1,121 @@ +/* + Copyright 2016 Chris Ballinger + Copyright 2016 OpenMarket Ltd + Copyright 2016 Vector Creations 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 "OLMUtility.h" + +#include "olm/olm.h" + +NSString *const OLMErrorDomain = @"org.matrix.olm"; + +@interface OLMUtility() + +@property (nonatomic) OlmUtility *utility; + +@end + +@implementation OLMUtility + +- (void) dealloc { + olm_clear_utility(_utility); + free(_utility); +} + +- (BOOL) initializeUtilityMemory { + size_t utilitySize = olm_utility_size(); + _utility = malloc(utilitySize); + NSParameterAssert(_utility != nil); + if (!_utility) { + return NO; + } + _utility = olm_utility(_utility); + NSParameterAssert(_utility != nil); + if (!_utility) { + return NO; + } + return YES; +} + +- (instancetype) init { + self = [super init]; + if (!self) { + return nil; + } + BOOL success = [self initializeUtilityMemory]; + if (!success) { + return nil; + } + return self; +} + +- (NSString *)sha256:(NSData *)message { + size_t length = olm_sha256_length(_utility); + + NSMutableData *shaData = [NSMutableData dataWithLength:length]; + if (!shaData) { + return nil; + } + + size_t result = olm_sha256(_utility, message.bytes, message.length, shaData.mutableBytes, shaData.length); + if (result == olm_error()) { + const char *error = olm_utility_last_error(_utility); + NSLog(@"olm_sha256 error: %s", error); + return nil; + } + + NSString *sha = [[NSString alloc] initWithData:shaData encoding:NSUTF8StringEncoding]; + return sha; +} + +- (BOOL)verifyEd25519Signature:(NSString*)signature key:(NSString*)key message:(NSData*)message error:(NSError**)error { + + NSData *keyData = [key dataUsingEncoding:NSUTF8StringEncoding]; + NSData *signatureData = [signature dataUsingEncoding:NSUTF8StringEncoding]; + + size_t result = olm_ed25519_verify(_utility, + keyData.bytes, keyData.length, + message.bytes, message.length, + (void*)signatureData.bytes, signatureData.length + ); + + if (result == olm_error()) { + if (error) { + NSDictionary *userInfo = @{NSLocalizedFailureReasonErrorKey: [NSString stringWithUTF8String:olm_utility_last_error(_utility)]}; + + // @TODO + *error = [[NSError alloc] initWithDomain:@"OLMKitErrorDomain" code:0 userInfo:userInfo]; + } + return NO; + } + else { + return YES; + } +} + ++ (NSMutableData*) randomBytesOfLength:(NSUInteger)length { + NSMutableData *randomData = [NSMutableData dataWithLength:length]; + if (!randomData) { + return nil; + } + int result = SecRandomCopyBytes(kSecRandomDefault, randomData.length, randomData.mutableBytes); + if (result != 0) { + return nil; + } + return randomData; +} + +@end diff --git a/xcode/OLMKitTests/Info.plist b/xcode/OLMKitTests/Info.plist new file mode 100644 index 0000000..6c6c23c --- /dev/null +++ b/xcode/OLMKitTests/Info.plist @@ -0,0 +1,22 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd"> +<plist version="1.0"> +<dict> + <key>CFBundleDevelopmentRegion</key> + <string>en</string> + <key>CFBundleExecutable</key> + <string>$(EXECUTABLE_NAME)</string> + <key>CFBundleIdentifier</key> + <string>$(PRODUCT_BUNDLE_IDENTIFIER)</string> + <key>CFBundleInfoDictionaryVersion</key> + <string>6.0</string> + <key>CFBundleName</key> + <string>$(PRODUCT_NAME)</string> + <key>CFBundlePackageType</key> + <string>BNDL</string> + <key>CFBundleShortVersionString</key> + <string>1.0</string> + <key>CFBundleVersion</key> + <string>1</string> +</dict> +</plist> diff --git a/xcode/OLMKitTests/OLMKitGroupTests.m b/xcode/OLMKitTests/OLMKitGroupTests.m new file mode 100644 index 0000000..ea82295 --- /dev/null +++ b/xcode/OLMKitTests/OLMKitGroupTests.m @@ -0,0 +1,94 @@ +/* + Copyright 2016 OpenMarket Ltd + Copyright 2016 Vector Creations 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 <XCTest/XCTest.h> + +#import <OLMKit/OLMKit.h> + +#include "olm/olm.h" + +@interface OLMKitGroupTests : XCTestCase + +@end + +@implementation OLMKitGroupTests + +- (void)setUp { + [super setUp]; + // Put setup code here. This method is called before the invocation of each test method in the class. +} + +- (void)tearDown { + // Put teardown code here. This method is called after the invocation of each test method in the class. + [super tearDown]; +} + +- (void)testAliceAndBob { + NSError *error; + + OLMOutboundGroupSession *aliceSession = [[OLMOutboundGroupSession alloc] initOutboundGroupSession]; + XCTAssertGreaterThan(aliceSession.sessionIdentifier.length, 0); + XCTAssertGreaterThan(aliceSession.sessionKey.length, 0); + XCTAssertEqual(aliceSession.messageIndex, 0); + + // Store the session key before starting encrypting + NSString *sessionKey = aliceSession.sessionKey; + + NSString *message = @"Hello!"; + NSString *aliceToBobMsg = [aliceSession encryptMessage:message error:&error]; + + XCTAssertEqual(aliceSession.messageIndex, 1); + XCTAssertGreaterThanOrEqual(aliceToBobMsg.length, 0); + XCTAssertNil(error); + + OLMInboundGroupSession *bobSession = [[OLMInboundGroupSession alloc] initInboundGroupSessionWithSessionKey:sessionKey error:&error]; + XCTAssertEqualObjects(aliceSession.sessionIdentifier, bobSession.sessionIdentifier); + XCTAssertNil(error); + + NSUInteger messageIndex; + + NSString *plaintext = [bobSession decryptMessage:aliceToBobMsg messageIndex:&messageIndex error:&error]; + XCTAssertEqualObjects(message, plaintext); + + XCTAssertEqual(messageIndex, 0); + XCTAssertNil(error); +} + +- (void)testOutboundGroupSessionSerialization { + + OLMOutboundGroupSession *aliceSession = [[OLMOutboundGroupSession alloc] initOutboundGroupSession]; + + NSData *aliceData = [NSKeyedArchiver archivedDataWithRootObject:aliceSession]; + OLMOutboundGroupSession *aliceSession2 = [NSKeyedUnarchiver unarchiveObjectWithData:aliceData]; + + XCTAssertEqualObjects(aliceSession2.sessionKey, aliceSession.sessionKey); + XCTAssertEqualObjects(aliceSession2.sessionIdentifier, aliceSession.sessionIdentifier); +} + +- (void)testInboundGroupSessionSerialization { + + OLMOutboundGroupSession *aliceSession = [[OLMOutboundGroupSession alloc] initOutboundGroupSession]; + + OLMInboundGroupSession *bobSession = [[OLMInboundGroupSession alloc] initInboundGroupSessionWithSessionKey:aliceSession.sessionKey error:nil]; + + NSData *bobData = [NSKeyedArchiver archivedDataWithRootObject:bobSession]; + OLMInboundGroupSession *bobSession2 = [NSKeyedUnarchiver unarchiveObjectWithData:bobData]; + + XCTAssertEqualObjects(bobSession2.sessionIdentifier, aliceSession.sessionIdentifier); +} + +@end diff --git a/xcode/OLMKitTests/OLMKitTests.m b/xcode/OLMKitTests/OLMKitTests.m new file mode 100644 index 0000000..ee02420 --- /dev/null +++ b/xcode/OLMKitTests/OLMKitTests.m @@ -0,0 +1,206 @@ +/* +Copyright 2016 Chris Ballinger +Copyright 2016 OpenMarket Ltd +Copyright 2016 Vector Creations 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 <XCTest/XCTest.h> +#import <OLMKit/OLMKit.h> + +@interface OLMKitTests : XCTestCase + +@end + +@implementation OLMKitTests + +- (void)setUp { + [super setUp]; + // Put setup code here. This method is called before the invocation of each test method in the class. +} + +- (void)tearDown { + // Put teardown code here. This method is called after the invocation of each test method in the class. + [super tearDown]; +} + +- (void)testAliceAndBob { + NSError *error; + + OLMAccount *alice = [[OLMAccount alloc] initNewAccount]; + OLMAccount *bob = [[OLMAccount alloc] initNewAccount]; + [bob generateOneTimeKeys:5]; + NSDictionary *bobIdKeys = bob.identityKeys; + NSString *bobIdKey = bobIdKeys[@"curve25519"]; + NSDictionary *bobOneTimeKeys = bob.oneTimeKeys; + NSParameterAssert(bobIdKey != nil); + NSParameterAssert(bobOneTimeKeys != nil); + __block NSString *bobOneTimeKey = nil; + NSDictionary *bobOtkCurve25519 = bobOneTimeKeys[@"curve25519"]; + [bobOtkCurve25519 enumerateKeysAndObjectsUsingBlock:^(id _Nonnull key, id _Nonnull obj, BOOL * _Nonnull stop) { + bobOneTimeKey = obj; + }]; + XCTAssert([bobOneTimeKey isKindOfClass:[NSString class]]); + + OLMSession *aliceSession = [[OLMSession alloc] initOutboundSessionWithAccount:alice theirIdentityKey:bobIdKey theirOneTimeKey:bobOneTimeKey error:nil]; + NSString *message = @"Hello!"; + OLMMessage *aliceToBobMsg = [aliceSession encryptMessage:message error:&error]; + XCTAssertNil(error); + + OLMSession *bobSession = [[OLMSession alloc] initInboundSessionWithAccount:bob oneTimeKeyMessage:aliceToBobMsg.ciphertext error:nil]; + NSString *plaintext = [bobSession decryptMessage:aliceToBobMsg error:&error]; + XCTAssertEqualObjects(message, plaintext); + XCTAssertNil(error); + + XCTAssert([bobSession matchesInboundSession:aliceToBobMsg.ciphertext]); + XCTAssertFalse([aliceSession matchesInboundSession:@"ARandomOtkMessage"]); + + NSString *aliceIdKey = alice.identityKeys[@"curve25519"]; + XCTAssert([bobSession matchesInboundSessionFrom:aliceIdKey oneTimeKeyMessage:aliceToBobMsg.ciphertext]); + XCTAssertFalse([bobSession matchesInboundSessionFrom:@"ARandomIdKey" oneTimeKeyMessage:aliceToBobMsg.ciphertext]); + XCTAssertFalse([bobSession matchesInboundSessionFrom:aliceIdKey oneTimeKeyMessage:@"ARandomOtkMessage"]); + + BOOL success = [bob removeOneTimeKeysForSession:bobSession]; + XCTAssertTrue(success); +} + +- (void) testBackAndForth { + OLMAccount *alice = [[OLMAccount alloc] initNewAccount]; + OLMAccount *bob = [[OLMAccount alloc] initNewAccount]; + [bob generateOneTimeKeys:1]; + NSDictionary *bobIdKeys = bob.identityKeys; + NSString *bobIdKey = bobIdKeys[@"curve25519"]; + NSDictionary *bobOneTimeKeys = bob.oneTimeKeys; + NSParameterAssert(bobIdKey != nil); + NSParameterAssert(bobOneTimeKeys != nil); + __block NSString *bobOneTimeKey = nil; + NSDictionary *bobOtkCurve25519 = bobOneTimeKeys[@"curve25519"]; + [bobOtkCurve25519 enumerateKeysAndObjectsUsingBlock:^(id _Nonnull key, id _Nonnull obj, BOOL * _Nonnull stop) { + bobOneTimeKey = obj; + }]; + XCTAssert([bobOneTimeKey isKindOfClass:[NSString class]]); + + OLMSession *aliceSession = [[OLMSession alloc] initOutboundSessionWithAccount:alice theirIdentityKey:bobIdKey theirOneTimeKey:bobOneTimeKey error:nil]; + NSString *message = @"Hello I'm Alice!"; + OLMMessage *aliceToBobMsg = [aliceSession encryptMessage:message error:nil]; + + OLMSession *bobSession = [[OLMSession alloc] initInboundSessionWithAccount:bob oneTimeKeyMessage:aliceToBobMsg.ciphertext error:nil]; + NSString *plaintext = [bobSession decryptMessage:aliceToBobMsg error:nil]; + XCTAssertEqualObjects(message, plaintext); + BOOL success = [bob removeOneTimeKeysForSession:bobSession]; + XCTAssertTrue(success); + + NSString *msg1 = @"Hello I'm Bob!"; + NSString *msg2 = @"Isn't life grand?"; + NSString *msg3 = @"Let's go to the opera."; + + OLMMessage *eMsg1 = [bobSession encryptMessage:msg1 error:nil]; + OLMMessage *eMsg2 = [bobSession encryptMessage:msg2 error:nil]; + OLMMessage *eMsg3 = [bobSession encryptMessage:msg3 error:nil]; + + NSString *dMsg1 = [aliceSession decryptMessage:eMsg1 error:nil]; + NSString *dMsg2 = [aliceSession decryptMessage:eMsg2 error:nil]; + NSString *dMsg3 = [aliceSession decryptMessage:eMsg3 error:nil]; + XCTAssertEqualObjects(msg1, dMsg1); + XCTAssertEqualObjects(msg2, dMsg2); + XCTAssertEqualObjects(msg3, dMsg3); +} + +- (void) testAccountSerialization { + OLMAccount *bob = [[OLMAccount alloc] initNewAccount]; + [bob generateOneTimeKeys:5]; + NSDictionary *bobIdKeys = bob.identityKeys; + NSDictionary *bobOneTimeKeys = bob.oneTimeKeys; + + NSData *bobData = [NSKeyedArchiver archivedDataWithRootObject:bob]; + + OLMAccount *bob2 = [NSKeyedUnarchiver unarchiveObjectWithData:bobData]; + NSDictionary *bobIdKeys2 = bob2.identityKeys; + NSDictionary *bobOneTimeKeys2 = bob2.oneTimeKeys; + + XCTAssertEqualObjects(bobIdKeys, bobIdKeys2); + XCTAssertEqualObjects(bobOneTimeKeys, bobOneTimeKeys2); +} + +- (void) testSessionSerialization { + NSError *error; + + OLMAccount *alice = [[OLMAccount alloc] initNewAccount]; + OLMAccount *bob = [[OLMAccount alloc] initNewAccount]; + [bob generateOneTimeKeys:1]; + NSDictionary *bobIdKeys = bob.identityKeys; + NSString *bobIdKey = bobIdKeys[@"curve25519"]; + NSDictionary *bobOneTimeKeys = bob.oneTimeKeys; + NSParameterAssert(bobIdKey != nil); + NSParameterAssert(bobOneTimeKeys != nil); + __block NSString *bobOneTimeKey = nil; + NSDictionary *bobOtkCurve25519 = bobOneTimeKeys[@"curve25519"]; + [bobOtkCurve25519 enumerateKeysAndObjectsUsingBlock:^(id _Nonnull key, id _Nonnull obj, BOOL * _Nonnull stop) { + bobOneTimeKey = obj; + }]; + XCTAssert([bobOneTimeKey isKindOfClass:[NSString class]]); + + OLMSession *aliceSession = [[OLMSession alloc] initOutboundSessionWithAccount:alice theirIdentityKey:bobIdKey theirOneTimeKey:bobOneTimeKey error:nil]; + NSString *message = @"Hello I'm Alice!"; + OLMMessage *aliceToBobMsg = [aliceSession encryptMessage:message error:&error]; + XCTAssertNil(error); + + + OLMSession *bobSession = [[OLMSession alloc] initInboundSessionWithAccount:bob oneTimeKeyMessage:aliceToBobMsg.ciphertext error:nil]; + NSString *plaintext = [bobSession decryptMessage:aliceToBobMsg error:nil]; + XCTAssertEqualObjects(message, plaintext); + BOOL success = [bob removeOneTimeKeysForSession:bobSession]; + XCTAssertTrue(success); + + NSString *msg1 = @"Hello I'm Bob!"; + NSString *msg2 = @"Isn't life grand?"; + NSString *msg3 = @"Let's go to the opera."; + + OLMMessage *eMsg1 = [bobSession encryptMessage:msg1 error:nil]; + OLMMessage *eMsg2 = [bobSession encryptMessage:msg2 error:nil]; + OLMMessage *eMsg3 = [bobSession encryptMessage:msg3 error:nil]; + + NSData *aliceData = [NSKeyedArchiver archivedDataWithRootObject:aliceSession]; + OLMSession *alice2 = [NSKeyedUnarchiver unarchiveObjectWithData:aliceData]; + + NSString *dMsg1 = [alice2 decryptMessage:eMsg1 error:nil]; + NSString *dMsg2 = [alice2 decryptMessage:eMsg2 error:nil]; + NSString *dMsg3 = [alice2 decryptMessage:eMsg3 error:nil]; + XCTAssertEqualObjects(msg1, dMsg1); + XCTAssertEqualObjects(msg2, dMsg2); + XCTAssertEqualObjects(msg3, dMsg3); +} + +- (void)testEd25519Signing { + + OLMUtility *olmUtility = [[OLMUtility alloc] init]; + OLMAccount *alice = [[OLMAccount alloc] initNewAccount]; + + NSDictionary *aJSON = @{ + @"key1": @"value1", + @"key2": @"value2" + }; + NSData *message = [NSKeyedArchiver archivedDataWithRootObject:aJSON]; + NSString *signature = [alice signMessage:message]; + + + NSString *aliceEd25519Key = alice.identityKeys[@"ed25519"]; + + NSError *error; + BOOL result = [olmUtility verifyEd25519Signature:signature key:aliceEd25519Key message:message error:&error]; + XCTAssert(result); + XCTAssertNil(error); +} + +@end diff --git a/xcode/Podfile b/xcode/Podfile new file mode 100644 index 0000000..4c60dd3 --- /dev/null +++ b/xcode/Podfile @@ -0,0 +1,7 @@ +target "OLMKit" do +pod 'OLMKit', :path => '../OLMKit.podspec' +end + +target "OLMKitTests" do +pod 'OLMKit', :path => '../OLMKit.podspec' +end
\ No newline at end of file diff --git a/xcode/Podfile.lock b/xcode/Podfile.lock new file mode 100644 index 0000000..ecafd79 --- /dev/null +++ b/xcode/Podfile.lock @@ -0,0 +1,20 @@ +PODS: + - OLMKit (2.0.1): + - OLMKit/olmc (= 2.0.1) + - OLMKit/olmcpp (= 2.0.1) + - OLMKit/olmc (2.0.1) + - OLMKit/olmcpp (2.0.1) + +DEPENDENCIES: + - OLMKit (from `../OLMKit.podspec`) + +EXTERNAL SOURCES: + OLMKit: + :path: ../OLMKit.podspec + +SPEC CHECKSUMS: + OLMKit: 12a35a69f92c7facdd50b559128d1b4a17857ba7 + +PODFILE CHECKSUM: 4e261dae61d833ec5585ced2473023b98909fd35 + +COCOAPODS: 1.1.1 diff --git a/xcode/README.rst b/xcode/README.rst new file mode 100644 index 0000000..d56fa85 --- /dev/null +++ b/xcode/README.rst @@ -0,0 +1,26 @@ +OLMKit +====== + +OLMKit exposes an Objective-C wrapper to libolm. + +The original work by Chris Ballinger can be found at https://github.com/chrisballinger/OLMKit. + +Installation +------------ +You can embed OLMKit to your application project with CocoaPods. The pod for +the latest OLMKit release is:: + + pod 'OLMKit' + +Development +----------- +Run `pod install` and open `OLMKit.xcworkspace`. + +The project contains only tests files. The libolm and the Objective-C wrapper source files are loaded via the OLMKit CocoaPods pod. + +To add a new source file, add it to the file system and run `pod update` to make CocoaPods insert it into OLMKit.xcworkspace. + +Release +------- +See ../README.rst for the release of the CocoaPod. + |