aboutsummaryrefslogtreecommitdiff
path: root/xcode/OLMKit
diff options
context:
space:
mode:
Diffstat (limited to 'xcode/OLMKit')
-rw-r--r--xcode/OLMKit/Info.plist26
-rw-r--r--xcode/OLMKit/OLMAccount.h41
-rw-r--r--xcode/OLMKit/OLMAccount.m253
-rw-r--r--xcode/OLMKit/OLMAccount_Private.h15
-rw-r--r--xcode/OLMKit/OLMInboundGroupSession.h29
-rw-r--r--xcode/OLMKit/OLMInboundGroupSession.m198
-rw-r--r--xcode/OLMKit/OLMKit.h21
-rw-r--r--xcode/OLMKit/OLMKit.m28
-rw-r--r--xcode/OLMKit/OLMMessage.h28
-rw-r--r--xcode/OLMKit/OLMMessage.m24
-rw-r--r--xcode/OLMKit/OLMOutboundGroupSession.h31
-rw-r--r--xcode/OLMKit/OLMOutboundGroupSession.m205
-rw-r--r--xcode/OLMKit/OLMSerializable.h19
-rw-r--r--xcode/OLMKit/OLMSession.h34
-rw-r--r--xcode/OLMKit/OLMSession.m261
-rw-r--r--xcode/OLMKit/OLMSession_Private.h16
-rw-r--r--xcode/OLMKit/OLMUtility.h37
-rw-r--r--xcode/OLMKit/OLMUtility.m109
18 files changed, 1375 insertions, 0 deletions
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..382be31
--- /dev/null
+++ b/xcode/OLMKit/OLMAccount.h
@@ -0,0 +1,41 @@
+//
+// OLMAccount.h
+// olm
+//
+// Created by Chris Ballinger on 4/8/16.
+//
+//
+
+#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..085b487
--- /dev/null
+++ b/xcode/OLMKit/OLMAccount.m
@@ -0,0 +1,253 @@
+//
+// OLMAccount.m
+// olm
+//
+// Created by Chris Ballinger on 4/8/16.
+//
+//
+
+#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);
+ if (accountResult == olm_error()) {
+ const char *error = olm_account_last_error(_account);
+ NSLog(@"error creating account: %s", error);
+ return nil;
+ }
+ return self;
+}
+
+- (size_t) 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);
+ 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 nil;
+ }
+ size_t result = olm_remove_one_time_keys(self.account, session.session);
+ if (result == olm_error()) {
+ const char *error = olm_account_last_error(_account);
+ NSAssert(NO, @"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:@"org.matrix.olm" 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:@"org.matrix.olm" 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:@"org.matrix.olm" 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..b8cf506
--- /dev/null
+++ b/xcode/OLMKit/OLMAccount_Private.h
@@ -0,0 +1,15 @@
+//
+// OLMAccount_Private.h
+// olm
+//
+// Created by Chris Ballinger on 4/8/16.
+//
+//
+
+#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..a507490
--- /dev/null
+++ b/xcode/OLMKit/OLMInboundGroupSession.h
@@ -0,0 +1,29 @@
+/*
+ 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.
+ */
+
+#import <Foundation/Foundation.h>
+#import "OLMSerializable.h"
+
+@interface OLMInboundGroupSession : NSObject <OLMSerializable, NSSecureCoding>
+
+- (instancetype) initInboundGroupSessionWithSessionKey:(NSString*)sessionKey;
+
+- (NSString*)sessionIdentifier;
+
+/** base64 ciphertext -> UTF-8 plaintext */
+- (NSString*)decryptMessage:(NSString*)message;
+
+@end
diff --git a/xcode/OLMKit/OLMInboundGroupSession.m b/xcode/OLMKit/OLMInboundGroupSession.m
new file mode 100644
index 0000000..d95d1ab
--- /dev/null
+++ b/xcode/OLMKit/OLMInboundGroupSession.m
@@ -0,0 +1,198 @@
+/*
+ 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.
+ */
+
+#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 {
+ 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 *error = olm_inbound_group_session_last_error(session);
+ NSAssert(NO, @"olm_init_inbound_group_session error: %s", error);
+ 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);
+ NSAssert(NO, @"olm_inbound_group_session_id error: %s", error);
+ return nil;
+ }
+ NSString *idString = [[NSString alloc] initWithData:idData encoding:NSUTF8StringEncoding];
+ return idString;
+}
+
+- (NSString *)decryptMessage:(NSString *)message
+{
+ 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 *error = olm_inbound_group_session_last_error(session);
+ NSAssert(NO, @"olm_group_decrypt_max_plaintext_length error: %s", error);
+ return nil;
+ }
+ // message buffer is destroyed by olm_group_decrypt_max_plaintext_length
+ mutMessage = messageData.mutableCopy;
+ NSMutableData *plaintextData = [NSMutableData dataWithLength:maxPlaintextLength];
+ size_t plaintextLength = olm_group_decrypt(session, mutMessage.mutableBytes, mutMessage.length, plaintextData.mutableBytes, plaintextData.length);
+ if (plaintextLength == olm_error()) {
+ const char *error = olm_inbound_group_session_last_error(session);
+ NSAssert(NO, @"olm_group_decrypt error: %s", error);
+ return nil;
+ }
+ plaintextData.length = plaintextLength;
+ NSString *plaintext = [[NSString alloc] initWithData:plaintextData encoding:NSUTF8StringEncoding];
+ 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:@"org.matrix.olm" 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:@"org.matrix.olm" 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:@"org.matrix.olm" 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..3865e74
--- /dev/null
+++ b/xcode/OLMKit/OLMKit.h
@@ -0,0 +1,21 @@
+//
+// OLMKit.h
+// OLMKit
+//
+// Created by Chris Ballinger on 4/8/16.
+//
+//
+
+#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..0cd4ed5
--- /dev/null
+++ b/xcode/OLMKit/OLMKit.m
@@ -0,0 +1,28 @@
+/*
+ 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.
+ */
+
+#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..97c748f
--- /dev/null
+++ b/xcode/OLMKit/OLMMessage.h
@@ -0,0 +1,28 @@
+//
+// OLMMessage.h
+// olm
+//
+// Created by Chris Ballinger on 4/8/16.
+//
+//
+
+#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..d0cfb41
--- /dev/null
+++ b/xcode/OLMKit/OLMMessage.m
@@ -0,0 +1,24 @@
+//
+// OLMMessage.m
+// olm
+//
+// Created by Chris Ballinger on 4/8/16.
+//
+//
+
+#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..e7a8a91
--- /dev/null
+++ b/xcode/OLMKit/OLMOutboundGroupSession.h
@@ -0,0 +1,31 @@
+/*
+ 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.
+ */
+
+#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;
+
+@end
diff --git a/xcode/OLMKit/OLMOutboundGroupSession.m b/xcode/OLMKit/OLMOutboundGroupSession.m
new file mode 100644
index 0000000..c86fa9f
--- /dev/null
+++ b/xcode/OLMKit/OLMOutboundGroupSession.m
@@ -0,0 +1,205 @@
+/*
+ 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.
+ */
+
+#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);
+ if (result == olm_error()) {
+ const char *error = olm_outbound_group_session_last_error(session);
+ NSAssert(NO, @"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);
+ NSAssert(NO, @"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);
+ NSAssert(NO, @"olm_outbound_group_session_key error: %s", error);
+ return nil;
+ }
+ NSString *sessionKey = [[NSString alloc] initWithData:sessionKeyData encoding:NSUTF8StringEncoding];
+ return sessionKey;
+}
+
+- (NSString *)encryptMessage:(NSString *)message {
+ 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 *error = olm_outbound_group_session_last_error(session);
+ NSAssert(NO, @"olm_group_encrypt error: %s", error);
+ 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:@"org.matrix.olm" 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:@"org.matrix.olm" 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:@"org.matrix.olm" 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..b4b115a
--- /dev/null
+++ b/xcode/OLMKit/OLMSerializable.h
@@ -0,0 +1,19 @@
+//
+// OLMSerializable.h
+// olm
+//
+// Created by Chris Ballinger on 4/8/16.
+//
+//
+
+#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..c209564
--- /dev/null
+++ b/xcode/OLMKit/OLMSession.h
@@ -0,0 +1,34 @@
+//
+// OLMSession.h
+// olm
+//
+// Created by Chris Ballinger on 4/8/16.
+//
+//
+
+#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;
+
+- (instancetype) initInboundSessionWithAccount:(OLMAccount*)account oneTimeKeyMessage:(NSString*)oneTimeKeyMessage;
+
+- (instancetype) initInboundSessionWithAccount:(OLMAccount*)account theirIdentityKey:(NSString*)theirIdentityKey oneTimeKeyMessage:(NSString*)oneTimeKeyMessage;
+
+- (NSString*) sessionIdentifier;
+
+- (BOOL) matchesInboundSession:(NSString*)oneTimeKeyMessage;
+
+- (BOOL) matchesInboundSessionFrom:(NSString*)theirIdentityKey oneTimeKeyMessage:(NSString *)oneTimeKeyMessage;
+
+/** UTF-8 plaintext -> base64 ciphertext */
+- (OLMMessage*) encryptMessage:(NSString*)message;
+
+/** base64 ciphertext -> UTF-8 plaintext */
+- (NSString*) decryptMessage:(OLMMessage*)message;
+
+@end
diff --git a/xcode/OLMKit/OLMSession.m b/xcode/OLMKit/OLMSession.m
new file mode 100644
index 0000000..41aef7e
--- /dev/null
+++ b/xcode/OLMKit/OLMSession.m
@@ -0,0 +1,261 @@
+//
+// OLMSession.m
+// olm
+//
+// Created by Chris Ballinger on 4/8/16.
+//
+//
+
+#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 {
+ 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);
+ if (result == olm_error()) {
+ const char *error = olm_session_last_error(_session);
+ NSAssert(NO, @"olm_create_outbound_session error: %s", error);
+ return nil;
+ }
+ return self;
+}
+
+- (instancetype) initInboundSessionWithAccount:(OLMAccount*)account oneTimeKeyMessage:(NSString*)oneTimeKeyMessage {
+ 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 *error = olm_session_last_error(_session);
+ NSAssert(NO, @"olm_create_inbound_session error: %s", error);
+ return nil;
+ }
+ return self;
+}
+
+- (instancetype) initInboundSessionWithAccount:(OLMAccount*)account theirIdentityKey:(NSString*)theirIdentityKey oneTimeKeyMessage:(NSString*)oneTimeKeyMessage {
+ 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 *error = olm_session_last_error(_session);
+ NSAssert(NO, @"olm_create_inbound_session_from error: %s", error);
+ 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);
+ NSAssert(NO, @"olm_session_id error: %s", error);
+ return nil;
+ }
+ NSString *idString = [[NSString alloc] initWithData:idData encoding:NSUTF8StringEncoding];
+ return idString;
+}
+
+- (OLMMessage*) encryptMessage:(NSString*)message {
+ 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);
+ if (result == olm_error()) {
+ const char *error = olm_session_last_error(_session);
+ NSAssert(NO, @"olm_encrypt error: %s", error);
+ return nil;
+ }
+ NSString *ciphertextString = [[NSString alloc] initWithData:ciphertext encoding:NSUTF8StringEncoding];
+ OLMMessage *encryptedMessage = [[OLMMessage alloc] initWithCiphertext:ciphertextString type:messageType];
+ return encryptedMessage;
+}
+
+- (NSString*) decryptMessage:(OLMMessage*)message {
+ 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 *error = olm_session_last_error(_session);
+ NSAssert(NO, @"olm_decrypt_max_plaintext_length error: %s", error);
+ 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 *error = olm_session_last_error(_session);
+ NSAssert(NO, @"olm_decrypt error: %s", error);
+ return nil;
+ }
+ plaintextData.length = plaintextLength;
+ NSString *plaintext = [[NSString alloc] initWithData:plaintextData encoding:NSUTF8StringEncoding];
+ 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:@"org.matrix.olm" 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:@"org.matrix.olm" 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:@"org.matrix.olm" 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..bd7d25d
--- /dev/null
+++ b/xcode/OLMKit/OLMSession_Private.h
@@ -0,0 +1,16 @@
+//
+// OLMSession_Private.h
+// olm
+//
+// Created by Chris Ballinger on 4/13/16.
+//
+//
+
+#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..1952b8e
--- /dev/null
+++ b/xcode/OLMKit/OLMUtility.h
@@ -0,0 +1,37 @@
+//
+// OLMUtility.h
+// olm
+//
+// Created by Chris Ballinger on 4/8/16.
+//
+//
+
+#import <Foundation/Foundation.h>
+
+@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..292fc21
--- /dev/null
+++ b/xcode/OLMKit/OLMUtility.m
@@ -0,0 +1,109 @@
+//
+// OLMUtility.m
+// olm
+//
+// Created by Chris Ballinger on 4/8/16.
+//
+//
+
+#import "OLMUtility.h"
+
+#include "olm/olm.h"
+
+@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);
+ NSAssert(NO, @"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,
+ 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