Commit daab2a58 authored by Chris Ballinger's avatar Chris Ballinger
Browse files

OLMAccount and OLMSession serialization

parent f505113f
...@@ -9,7 +9,9 @@ ...@@ -9,7 +9,9 @@
#import <Foundation/Foundation.h> #import <Foundation/Foundation.h>
#import "OLMSerializable.h" #import "OLMSerializable.h"
@interface OLMAccount : NSObject <OLMSerializable> @class OLMSession;
@interface OLMAccount : NSObject <OLMSerializable, NSSecureCoding>
/** Creates new account */ /** Creates new account */
- (instancetype) initNewAccount; - (instancetype) initNewAccount;
...@@ -18,11 +20,13 @@ ...@@ -18,11 +20,13 @@
- (NSDictionary*) identityKeys; - (NSDictionary*) identityKeys;
/** signs message with ed25519 key for account */ /** signs message with ed25519 key for account */
- (NSData*) signMessage:(NSData*)messageData; - (NSString*) signMessage:(NSData*)messageData;
/** Public parts of the unpublished one time keys for the account */ /** Public parts of the unpublished one time keys for the account */
- (NSDictionary*) oneTimeKeys; - (NSDictionary*) oneTimeKeys;
- (BOOL) removeOneTimeKeysForSession:(OLMSession*)session;
/** Marks the current set of one time keys as being published. */ /** Marks the current set of one time keys as being published. */
- (void) markKeysAsPublished; - (void) markKeysAsPublished;
......
...@@ -8,6 +8,8 @@ ...@@ -8,6 +8,8 @@
#import "OLMAccount.h" #import "OLMAccount.h"
#import "OLMAccount_Private.h" #import "OLMAccount_Private.h"
#import "OLMSession.h"
#import "OLMSession_Private.h"
#import "OLMUtility.h" #import "OLMUtility.h"
@import Security; @import Security;
...@@ -34,7 +36,7 @@ ...@@ -34,7 +36,7 @@
return YES; return YES;
} }
- (instancetype) initNewAccount { - (instancetype) init {
self = [super init]; self = [super init];
if (!self) { if (!self) {
return nil; return nil;
...@@ -43,6 +45,14 @@ ...@@ -43,6 +45,14 @@
if (!success) { if (!success) {
return nil; return nil;
} }
return self;
}
- (instancetype) initNewAccount {
self = [self init];
if (!self) {
return nil;
}
size_t randomLength = olm_create_account_random_length(_account); size_t randomLength = olm_create_account_random_length(_account);
NSMutableData *random = [OLMUtility randomBytesOfLength:randomLength]; NSMutableData *random = [OLMUtility randomBytesOfLength:randomLength];
size_t accountResult = olm_create_account(_account, random.mutableBytes, random.length); size_t accountResult = olm_create_account(_account, random.mutableBytes, random.length);
...@@ -114,5 +124,106 @@ ...@@ -114,5 +124,106 @@
} }
} }
- (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_session_last_error(session.session);
NSAssert(NO, @"olm_remove_one_time_keys error: %s", error);
return NO;
}
return YES;
}
#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 @end
...@@ -11,9 +11,9 @@ ...@@ -11,9 +11,9 @@
@protocol OLMSerializable <NSObject> @protocol OLMSerializable <NSObject>
/** Initializes from encrypted serialized data. Will throw error if invalid key or invalid base64. */ /** Initializes from encrypted serialized data. Will throw error if invalid key or invalid base64. */
- (instancetype) initWithSerializedData:(NSData*)serializedData key:(NSData*)key error:(NSError**)error; - (instancetype) initWithSerializedData:(NSString*)serializedData key:(NSData*)key error:(NSError**)error;
/** Serializes and encrypts object data */ /** Serializes and encrypts object data, outputs base64 blob */
- (NSData*) serializeDataWithKey:(NSData*)key; - (NSString*) serializeDataWithKey:(NSData*)key error:(NSError**)error;
@end @end
...@@ -11,9 +11,7 @@ ...@@ -11,9 +11,7 @@
#import "OLMAccount.h" #import "OLMAccount.h"
#import "OLMMessage.h" #import "OLMMessage.h"
@interface OLMSession : NSObject <OLMSerializable> @interface OLMSession : NSObject <OLMSerializable, NSSecureCoding>
@property (nonatomic, strong) OLMAccount *account;
- (instancetype) initOutboundSessionWithAccount:(OLMAccount*)account theirIdentityKey:(NSString*)theirIdentityKey theirOneTimeKey:(NSString*)theirOneTimeKey; - (instancetype) initOutboundSessionWithAccount:(OLMAccount*)account theirIdentityKey:(NSString*)theirIdentityKey theirOneTimeKey:(NSString*)theirOneTimeKey;
...@@ -27,8 +25,6 @@ ...@@ -27,8 +25,6 @@
- (BOOL) matchesInboundSessionFrom:(NSString*)theirIdentityKey oneTimeKeyMessage:(NSString *)oneTimeKeyMessage; - (BOOL) matchesInboundSessionFrom:(NSString*)theirIdentityKey oneTimeKeyMessage:(NSString *)oneTimeKeyMessage;
- (BOOL) removeOneTimeKeys;
/** UTF-8 plaintext -> base64 ciphertext */ /** UTF-8 plaintext -> base64 ciphertext */
- (OLMMessage*) encryptMessage:(NSString*)message; - (OLMMessage*) encryptMessage:(NSString*)message;
......
...@@ -9,12 +9,9 @@ ...@@ -9,12 +9,9 @@
#import "OLMSession.h" #import "OLMSession.h"
#import "OLMUtility.h" #import "OLMUtility.h"
#import "OLMAccount_Private.h" #import "OLMAccount_Private.h"
#import "OLMSession_Private.h"
@import olm; @import olm;
@interface OLMSession()
@property (nonatomic) OlmSession *session;
@end
@implementation OLMSession @implementation OLMSession
- (void) dealloc { - (void) dealloc {
...@@ -37,7 +34,7 @@ ...@@ -37,7 +34,7 @@
return YES; return YES;
} }
- (instancetype) initWithAccount:(OLMAccount*)account { - (instancetype) init {
self = [super init]; self = [super init];
if (!self) { if (!self) {
return nil; return nil;
...@@ -46,6 +43,18 @@ ...@@ -46,6 +43,18 @@
if (!success) { if (!success) {
return nil; 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; _account = account;
return self; return self;
} }
...@@ -72,10 +81,6 @@ ...@@ -72,10 +81,6 @@
if (!self) { if (!self) {
return nil; return nil;
} }
BOOL success = [self initializeSessionMemory];
if (!success) {
return nil;
}
NSMutableData *otk = [NSMutableData dataWithData:[oneTimeKeyMessage dataUsingEncoding:NSUTF8StringEncoding]]; NSMutableData *otk = [NSMutableData dataWithData:[oneTimeKeyMessage dataUsingEncoding:NSUTF8StringEncoding]];
size_t result = olm_create_inbound_session(_session, account.account, otk.mutableBytes, oneTimeKeyMessage.length); size_t result = olm_create_inbound_session(_session, account.account, otk.mutableBytes, oneTimeKeyMessage.length);
if (result == olm_error()) { if (result == olm_error()) {
...@@ -91,10 +96,6 @@ ...@@ -91,10 +96,6 @@
if (!self) { if (!self) {
return nil; return nil;
} }
BOOL success = [self initializeSessionMemory];
if (!success) {
return nil;
}
NSData *idKey = [theirIdentityKey dataUsingEncoding:NSUTF8StringEncoding]; NSData *idKey = [theirIdentityKey dataUsingEncoding:NSUTF8StringEncoding];
NSMutableData *otk = [NSMutableData dataWithData:[oneTimeKeyMessage 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); size_t result = olm_create_inbound_session_from(_session, account.account, idKey.bytes, idKey.length, otk.mutableBytes, otk.length);
...@@ -143,16 +144,6 @@ ...@@ -143,16 +144,6 @@
return encryptedMessage; return encryptedMessage;
} }
- (BOOL) removeOneTimeKeys {
size_t result = olm_remove_one_time_keys(_account.account, _session);
if (result == olm_error()) {
const char *error = olm_session_last_error(_session);
NSAssert(NO, @"olm_remove_one_time_keys error: %s", error);
return NO;
}
return YES;
}
- (NSString*) decryptMessage:(OLMMessage*)message { - (NSString*) decryptMessage:(OLMMessage*)message {
NSParameterAssert(message != nil); NSParameterAssert(message != nil);
NSData *messageData = [message.ciphertext dataUsingEncoding:NSUTF8StringEncoding]; NSData *messageData = [message.ciphertext dataUsingEncoding:NSUTF8StringEncoding];
...@@ -180,4 +171,91 @@ ...@@ -180,4 +171,91 @@
return plaintext; 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 @end
//
// OLMSession_Private.h
// olm
//
// Created by Chris Ballinger on 4/13/16.
//
//
@import olm;
@interface OLMSession()
@property (nonatomic) OlmSession *session;
@property (nonatomic, strong) OLMAccount *account;
@end
\ No newline at end of file
...@@ -25,9 +25,7 @@ ...@@ -25,9 +25,7 @@
[super tearDown]; [super tearDown];
} }
- (void)testExample { - (void)testAliceAndBob {
// This is an example of a functional test case.
// Use XCTAssert and related functions to verify your tests produce the correct results.
OLMAccount *alice = [[OLMAccount alloc] initNewAccount]; OLMAccount *alice = [[OLMAccount alloc] initNewAccount];
OLMAccount *bob = [[OLMAccount alloc] initNewAccount]; OLMAccount *bob = [[OLMAccount alloc] initNewAccount];
[bob generateOneTimeKeys:5]; [bob generateOneTimeKeys:5];
...@@ -50,9 +48,114 @@ ...@@ -50,9 +48,114 @@
OLMSession *bobSession = [[OLMSession alloc] initInboundSessionWithAccount:bob oneTimeKeyMessage:aliceToBobMsg.ciphertext]; OLMSession *bobSession = [[OLMSession alloc] initInboundSessionWithAccount:bob oneTimeKeyMessage:aliceToBobMsg.ciphertext];
NSString *plaintext = [bobSession decryptMessage:aliceToBobMsg]; NSString *plaintext = [bobSession decryptMessage:aliceToBobMsg];
XCTAssertEqualObjects(message, plaintext); XCTAssertEqualObjects(message, plaintext);
BOOL success = [bobSession removeOneTimeKeys]; BOOL success = [bob removeOneTimeKeysForSession:bobSession];
XCTAssertTrue(success); 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];
NSString *message = @"Hello I'm Alice!";
OLMMessage *aliceToBobMsg = [aliceSession encryptMessage:message];
OLMSession *bobSession = [[OLMSession alloc] initInboundSessionWithAccount:bob oneTimeKeyMessage:aliceToBobMsg.ciphertext];
NSString *plaintext = [bobSession decryptMessage:aliceToBobMsg];
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];
OLMMessage *eMsg2 = [bobSession encryptMessage:msg2];
OLMMessage *eMsg3 = [bobSession encryptMessage:msg3];
NSString *dMsg1 = [aliceSession decryptMessage:eMsg1];
NSString *dMsg2 = [aliceSession decryptMessage:eMsg2];
NSString *dMsg3 = [aliceSession decryptMessage:eMsg3];
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 = bob.oneTimeKeys;
XCTAssertEqualObjects(bobIdKeys, bobIdKeys2);
XCTAssertEqualObjects(bobOneTimeKeys, bobOneTimeKeys2);
}
- (void) testSessionSerialization {
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];
NSString *message = @"Hello I'm Alice!";
OLMMessage *aliceToBobMsg = [aliceSession encryptMessage:message];
OLMSession *bobSession = [[OLMSession alloc] initInboundSessionWithAccount:bob oneTimeKeyMessage:aliceToBobMsg.ciphertext];
NSString *plaintext = [bobSession decryptMessage:aliceToBobMsg];
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];
OLMMessage *eMsg2 = [bobSession encryptMessage:msg2];
OLMMessage *eMsg3 = [bobSession encryptMessage:msg3];
NSData *aliceData = [NSKeyedArchiver archivedDataWithRootObject:aliceSession];
OLMSession *alice2 = [NSKeyedUnarchiver unarchiveObjectWithData:aliceData];
NSString *dMsg1 = [alice2 decryptMessage:eMsg1];
NSString *dMsg2 = [alice2 decryptMessage:eMsg2];
NSString *dMsg3 = [alice2 decryptMessage:eMsg3];
XCTAssertEqualObjects(msg1, dMsg1);
XCTAssertEqualObjects(msg2, dMsg2);
XCTAssertEqualObjects(msg3, dMsg3);
}
@end @end
...@@ -7,6 +7,7 @@ ...@@ -7,6 +7,7 @@
objects = { objects = {