aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.gitignore23
-rw-r--r--CHANGELOG.rst18
-rw-r--r--Makefile2
-rw-r--r--OLMKit.podspec61
-rw-r--r--README.rst36
-rw-r--r--javascript/.gitignore1
-rw-r--r--javascript/olm_inbound_group_session.js68
-rw-r--r--javascript/olm_outbound_group_session.js54
-rw-r--r--javascript/olm_post.js172
-rw-r--r--javascript/olm_pre.js30
-rw-r--r--javascript/package.json9
-rw-r--r--javascript/test/megolm.spec.js68
-rw-r--r--javascript/test/olm.spec.js94
-rwxr-xr-xjenkins.sh1
-rw-r--r--lib/crypto-algorithms/aes.c1
-rw-r--r--lib/crypto-algorithms/sha256.c1
-rw-r--r--xcode/OLMKit.xcodeproj/project.pbxproj528
-rw-r--r--xcode/OLMKit/Info.plist26
-rw-r--r--xcode/OLMKit/OLMAccount.h51
-rw-r--r--xcode/OLMKit/OLMAccount.m265
-rw-r--r--xcode/OLMKit/OLMAccount_Private.h25
-rw-r--r--xcode/OLMKit/OLMInboundGroupSession.h30
-rw-r--r--xcode/OLMKit/OLMInboundGroupSession.m244
-rw-r--r--xcode/OLMKit/OLMKit.h31
-rw-r--r--xcode/OLMKit/OLMKit.m29
-rw-r--r--xcode/OLMKit/OLMMessage.h38
-rw-r--r--xcode/OLMKit/OLMMessage.m34
-rw-r--r--xcode/OLMKit/OLMOutboundGroupSession.h32
-rw-r--r--xcode/OLMKit/OLMOutboundGroupSession.m220
-rw-r--r--xcode/OLMKit/OLMSerializable.h29
-rw-r--r--xcode/OLMKit/OLMSession.h44
-rw-r--r--xcode/OLMKit/OLMSession.m381
-rw-r--r--xcode/OLMKit/OLMSession_Private.h26
-rw-r--r--xcode/OLMKit/OLMUtility.h49
-rw-r--r--xcode/OLMKit/OLMUtility.m121
-rw-r--r--xcode/OLMKitTests/Info.plist22
-rw-r--r--xcode/OLMKitTests/OLMKitGroupTests.m94
-rw-r--r--xcode/OLMKitTests/OLMKitTests.m206
-rw-r--r--xcode/Podfile7
-rw-r--r--xcode/Podfile.lock20
-rw-r--r--xcode/README.rst26
41 files changed, 3101 insertions, 116 deletions
diff --git a/.gitignore b/.gitignore
index c19e757..fed0245 100644
--- a/.gitignore
+++ b/.gitignore
@@ -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>`_
===============================================================
diff --git a/Makefile b/Makefile
index 77aa485..438fa68 100644
--- a/Makefile
+++ b/Makefile
@@ -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
diff --git a/README.rst b/README.rst
index 4568fff..43873ee 100644
--- a/README.rst
+++ b/README.rst
@@ -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);
+ });
+});
diff --git a/jenkins.sh b/jenkins.sh
index 3b6fb1f..06f9c8d 100755
--- a/jenkins.sh
+++ b/jenkins.sh
@@ -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.
+