aboutsummaryrefslogtreecommitdiff
path: root/xcode
diff options
context:
space:
mode:
Diffstat (limited to 'xcode')
-rw-r--r--xcode/OLMKit.xcodeproj/project.pbxproj4
-rw-r--r--xcode/OLMKit/OLMKit.h2
-rw-r--r--xcode/OLMKit/OLMPKEncryption.h42
-rw-r--r--xcode/OLMKit/OLMPKEncryption.m111
-rw-r--r--xcode/OLMKit/OLMPkDecryption.h64
-rw-r--r--xcode/OLMKit/OLMPkDecryption.m295
-rw-r--r--xcode/OLMKit/OLMPkMessage.h31
-rw-r--r--xcode/OLMKit/OLMPkMessage.m32
-rw-r--r--xcode/OLMKitTests/OLMKitPkTests.m107
9 files changed, 688 insertions, 0 deletions
diff --git a/xcode/OLMKit.xcodeproj/project.pbxproj b/xcode/OLMKit.xcodeproj/project.pbxproj
index ded14f1..7ea3d5b 100644
--- a/xcode/OLMKit.xcodeproj/project.pbxproj
+++ b/xcode/OLMKit.xcodeproj/project.pbxproj
@@ -7,6 +7,7 @@
objects = {
/* Begin PBXBuildFile section */
+ 3244277D2175EF700023EDF1 /* OLMKitPkTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 3244277C2175EF700023EDF1 /* OLMKitPkTests.m */; };
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 */; };
@@ -27,6 +28,7 @@
/* 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>"; };
+ 3244277C2175EF700023EDF1 /* OLMKitPkTests.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = OLMKitPkTests.m; 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; };
@@ -105,6 +107,7 @@
3274F6051D9A633A005282E4 /* OLMKitTests */ = {
isa = PBXGroup;
children = (
+ 3244277C2175EF700023EDF1 /* OLMKitPkTests.m */,
3274F6061D9A633A005282E4 /* OLMKitTests.m */,
32A151301DABDD4300400192 /* OLMKitGroupTests.m */,
3274F6081D9A633A005282E4 /* Info.plist */,
@@ -279,6 +282,7 @@
buildActionMask = 2147483647;
files = (
3274F6071D9A633A005282E4 /* OLMKitTests.m in Sources */,
+ 3244277D2175EF700023EDF1 /* OLMKitPkTests.m in Sources */,
32A151311DABDD4300400192 /* OLMKitGroupTests.m in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
diff --git a/xcode/OLMKit/OLMKit.h b/xcode/OLMKit/OLMKit.h
index 455d11b..6f79399 100644
--- a/xcode/OLMKit/OLMKit.h
+++ b/xcode/OLMKit/OLMKit.h
@@ -26,6 +26,8 @@
#import <OLMKit/OLMUtility.h>
#import <OLMKit/OLMInboundGroupSession.h>
#import <OLMKit/OLMOutboundGroupSession.h>
+#import <OLMKit/OLMPkEncryption.h>
+#import <OLMKit/OLMPkDecryption.h>
@interface OLMKit : NSObject
diff --git a/xcode/OLMKit/OLMPKEncryption.h b/xcode/OLMKit/OLMPKEncryption.h
new file mode 100644
index 0000000..a55d5bc
--- /dev/null
+++ b/xcode/OLMKit/OLMPKEncryption.h
@@ -0,0 +1,42 @@
+/*
+ Copyright 2018 New Vector 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.0OLMPKEncryption
+ 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 "OLMPkMessage.h"
+
+NS_ASSUME_NONNULL_BEGIN
+
+@interface OLMPkEncryption : NSObject
+
+/**
+ Set the recipient's public key for encrypting to.
+
+ @param recipientKey the recipient's public key.
+ */
+- (void)setRecipientKey:(NSString*)recipientKey;
+
+/**
+ Encrypt a plaintext for the recipient.
+
+ @param message the message to encrypt.
+ @param error the error if any.
+ @return the encrypted message.
+ */
+- (OLMPkMessage *)encryptMessage:(NSString*)message error:(NSError* _Nullable *)error;
+
+@end
+
+NS_ASSUME_NONNULL_END
diff --git a/xcode/OLMKit/OLMPKEncryption.m b/xcode/OLMKit/OLMPKEncryption.m
new file mode 100644
index 0000000..c2e3d04
--- /dev/null
+++ b/xcode/OLMKit/OLMPKEncryption.m
@@ -0,0 +1,111 @@
+/*
+ Copyright 2018 New Vector 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 "OLMPkEncryption.h"
+
+#include "olm/olm.h"
+#include "olm/pk.h"
+#include "OLMUtility.h"
+
+@interface OLMPkEncryption ()
+{
+ OlmPkEncryption *session;
+}
+@end
+
+@implementation OLMPkEncryption
+
+- (void)dealloc {
+ olm_clear_pk_encryption(session);
+ free(session);
+}
+
+
+- (instancetype)init {
+ self = [super init];
+ if (self) {
+ session = (OlmPkEncryption *)malloc(olm_pk_encryption_size());
+ olm_pk_encryption(session);
+ }
+ return self;
+}
+
+- (void)setRecipientKey:(NSString*)recipientKey {
+ NSData *recipientKeyData = [recipientKey dataUsingEncoding:NSUTF8StringEncoding];
+ olm_pk_encryption_set_recipient_key(session, recipientKeyData.bytes, recipientKeyData.length);
+}
+
+- (OLMPkMessage *)encryptMessage:(NSString *)message error:(NSError *__autoreleasing _Nullable *)error {
+ NSData *plaintextData = [message dataUsingEncoding:NSUTF8StringEncoding];
+
+ size_t randomLength = olm_pk_encrypt_random_length(session);
+ NSMutableData *random = [OLMUtility randomBytesOfLength:randomLength];
+ if (!random) {
+ return nil;
+ }
+
+ size_t ciphertextLength = olm_pk_ciphertext_length(session, plaintextData.length);
+ NSMutableData *ciphertext = [NSMutableData dataWithLength:ciphertextLength];
+ if (!ciphertext) {
+ return nil;
+ }
+
+ size_t macLength = olm_pk_mac_length(session);
+ NSMutableData *macData = [NSMutableData dataWithLength:macLength];
+ if (!ciphertext) {
+ return nil;
+ }
+
+ size_t ephemeralKeyLength = olm_pk_key_length();
+ NSMutableData *ephemeralKeyData = [NSMutableData dataWithLength:ephemeralKeyLength];
+ if (!ciphertext) {
+ return nil;
+ }
+
+ size_t result = olm_pk_encrypt(session,
+ plaintextData.bytes, plaintextData.length,
+ ciphertext.mutableBytes, ciphertext.length,
+ macData.mutableBytes, macLength,
+ ephemeralKeyData.mutableBytes, ephemeralKeyLength,
+ random.mutableBytes, randomLength);
+ if (result == olm_error()) {
+ const char *olm_error = olm_pk_encryption_last_error(session);
+
+ NSString *errorString = [NSString stringWithUTF8String:olm_error];
+ NSLog(@"[OLMPkEncryption] encryptMessage: 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;
+ }
+
+ OLMPkMessage *encryptedMessage = [[OLMPkMessage alloc]
+ initWithCiphertext:[[NSString alloc] initWithData:ciphertext encoding:NSUTF8StringEncoding]
+ mac:[[NSString alloc] initWithData:macData encoding:NSUTF8StringEncoding]
+ ephemeralKey:[[NSString alloc] initWithData:ephemeralKeyData encoding:NSUTF8StringEncoding]];
+
+
+ return encryptedMessage;
+}
+
+@end
diff --git a/xcode/OLMKit/OLMPkDecryption.h b/xcode/OLMKit/OLMPkDecryption.h
new file mode 100644
index 0000000..8715a99
--- /dev/null
+++ b/xcode/OLMKit/OLMPkDecryption.h
@@ -0,0 +1,64 @@
+/*
+ Copyright 2018 New Vector 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 "OLMPkMessage.h"
+
+NS_ASSUME_NONNULL_BEGIN
+
+@interface OLMPkDecryption : NSObject <OLMSerializable, NSSecureCoding>
+
+/**
+ Initialise the key from the private part of a key as returned by `privateKey`.
+
+ Note that the pubkey is a base64 encoded string, but the private key is
+ an unencoded byte array.
+
+ @param privateKey the private key part.
+ @param error the error if any.
+ @return the associated public key.
+ */
+- (NSString *)setPrivateKey:(NSData*)privateKey error:(NSError* _Nullable *)error;
+
+/**
+ Generate a new key to use for decrypting messages.
+
+ @param error the error if any.
+ @return the public part of the generated key.
+ */
+- (NSString *)generateKey:(NSError* _Nullable *)error;
+
+/**
+ Get the private key.
+
+ @return the private key;
+ */
+- (NSData *)privateKey;
+
+/**
+ Decrypt a ciphertext.
+
+ @param message the cipher message to decrypt.
+ @param error the error if any.
+ @return the decrypted message.
+ */
+- (NSString *)decryptMessage:(OLMPkMessage*)message error:(NSError* _Nullable *)error;
+
+@end
+
+NS_ASSUME_NONNULL_END
diff --git a/xcode/OLMKit/OLMPkDecryption.m b/xcode/OLMKit/OLMPkDecryption.m
new file mode 100644
index 0000000..38a86a2
--- /dev/null
+++ b/xcode/OLMKit/OLMPkDecryption.m
@@ -0,0 +1,295 @@
+/*
+ Copyright 2018 New Vector 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 "OLMPkDecryption.h"
+
+#include "olm/olm.h"
+#include "olm/pk.h"
+#include "OLMUtility.h"
+
+@interface OLMPkDecryption ()
+{
+ OlmPkDecryption *session;
+}
+@end
+
+@implementation OLMPkDecryption
+
+- (void)dealloc {
+ olm_clear_pk_decryption(session);
+ free(session);
+}
+
+- (instancetype)init {
+ self = [super init];
+ if (self) {
+ session = (OlmPkDecryption *)malloc(olm_pk_decryption_size());
+ olm_pk_decryption(session);
+ }
+ return self;
+}
+
+- (NSString *)setPrivateKey:(NSData *)privateKey error:(NSError *__autoreleasing _Nullable *)error {
+ size_t publicKeyLength = olm_pk_key_length();
+ NSMutableData *publicKeyData = [NSMutableData dataWithLength:publicKeyLength];
+ if (!publicKeyData) {
+ return nil;
+ }
+
+ size_t result = olm_pk_key_from_private(session,
+ publicKeyData.mutableBytes, publicKeyLength,
+ (void*)privateKey.bytes, privateKey.length);
+ if (result == olm_error()) {
+ const char *olm_error = olm_pk_decryption_last_error(session);
+ NSLog(@"[OLMPkDecryption] setPrivateKey: olm_pk_key_from_private error: %s", olm_error);
+
+ NSString *errorString = [NSString stringWithUTF8String:olm_error];
+ if (error && olm_error && errorString) {
+ *error = [NSError errorWithDomain:OLMErrorDomain
+ code:0
+ userInfo:@{
+ NSLocalizedDescriptionKey: errorString,
+ NSLocalizedFailureReasonErrorKey: [NSString stringWithFormat:@"olm_pk_key_from_private error: %@", errorString]
+ }];
+ }
+ return nil;
+ }
+
+ NSString *publicKey = [[NSString alloc] initWithData:publicKeyData encoding:NSUTF8StringEncoding];
+ [publicKeyData resetBytesInRange:NSMakeRange(0, publicKeyData.length)];
+
+ return publicKey;
+}
+
+- (NSString *)generateKey:(NSError *__autoreleasing _Nullable *)error {
+ size_t randomLength = olm_pk_private_key_length();
+ NSMutableData *random = [OLMUtility randomBytesOfLength:randomLength];
+ if (!random) {
+ return nil;
+ }
+
+ size_t publicKeyLength = olm_pk_key_length();
+ NSMutableData *publicKeyData = [NSMutableData dataWithLength:publicKeyLength];
+ if (!publicKeyData) {
+ return nil;
+ }
+
+ size_t result = olm_pk_key_from_private(session,
+ publicKeyData.mutableBytes, publicKeyData.length,
+ random.mutableBytes, randomLength);
+ if (result == olm_error()) {
+ const char *olm_error = olm_pk_decryption_last_error(session);
+ NSLog(@"[OLMPkDecryption] generateKey: olm_pk_key_from_private error: %s", olm_error);
+
+ NSString *errorString = [NSString stringWithUTF8String:olm_error];
+ if (error && olm_error && errorString) {
+ *error = [NSError errorWithDomain:OLMErrorDomain
+ code:0
+ userInfo:@{
+ NSLocalizedDescriptionKey: errorString,
+ NSLocalizedFailureReasonErrorKey: [NSString stringWithFormat:@"olm_pk_key_from_private error: %@", errorString]
+ }];
+ }
+ return nil;
+ }
+
+ NSString *publicKey = [[NSString alloc] initWithData:publicKeyData encoding:NSUTF8StringEncoding];
+ [publicKeyData resetBytesInRange:NSMakeRange(0, publicKeyData.length)];
+
+ return publicKey;
+}
+
+- (NSData *)privateKey {
+ size_t privateKeyLength = olm_pk_private_key_length();
+ NSMutableData *privateKeyData = [NSMutableData dataWithLength:privateKeyLength];
+ if (!privateKeyData) {
+ return nil;
+ }
+
+ size_t result = olm_pk_get_private_key(session,
+ privateKeyData.mutableBytes, privateKeyLength);
+ if (result == olm_error()) {
+ const char *olm_error = olm_pk_decryption_last_error(session);
+ NSLog(@"[OLMPkDecryption] privateKey: olm_pk_get_private_key error: %s", olm_error);
+ return nil;
+ }
+
+ NSData *privateKey = [privateKeyData copy];
+ [privateKeyData resetBytesInRange:NSMakeRange(0, privateKeyData.length)];
+
+ return privateKey;
+}
+
+-(NSString *)decryptMessage:(OLMPkMessage *)message error:(NSError *__autoreleasing _Nullable *)error {
+ NSData *messageData = [message.ciphertext dataUsingEncoding:NSUTF8StringEncoding];
+ NSData *macData = [message.mac dataUsingEncoding:NSUTF8StringEncoding];
+ NSData *ephemeralKeyData = [message.ephemeralKey dataUsingEncoding:NSUTF8StringEncoding];
+ if (!messageData || !macData || !ephemeralKeyData) {
+ return nil;
+ }
+
+ NSMutableData *mutMessage = messageData.mutableCopy;
+ size_t maxPlaintextLength = olm_pk_max_plaintext_length(session, mutMessage.length);
+ if (maxPlaintextLength == olm_error()) {
+ const char *olm_error = olm_pk_decryption_last_error(session);
+
+ NSString *errorString = [NSString stringWithUTF8String:olm_error];
+ NSLog(@"[OLMPkDecryption] decryptMessage: olm_pk_max_plaintext_length error: %@", errorString);
+
+ if (error && olm_error && errorString) {
+ *error = [NSError errorWithDomain:OLMErrorDomain
+ code:0
+ userInfo:@{
+ NSLocalizedDescriptionKey: errorString,
+ NSLocalizedFailureReasonErrorKey: [NSString stringWithFormat:@"olm_pk_max_plaintext_length error: %@", errorString]
+ }];
+ }
+
+ return nil;
+ }
+
+ mutMessage = messageData.mutableCopy;
+ NSMutableData *plaintextData = [NSMutableData dataWithLength:maxPlaintextLength];
+ size_t plaintextLength = olm_pk_decrypt(session,
+ ephemeralKeyData.bytes, ephemeralKeyData.length,
+ macData.bytes, macData.length,
+ mutMessage.mutableBytes, mutMessage.length,
+ plaintextData.mutableBytes, plaintextData.length);
+ if (plaintextLength == olm_error()) {
+ const char *olm_error = olm_pk_decryption_last_error(session);
+
+ NSString *errorString = [NSString stringWithUTF8String:olm_error];
+ NSLog(@"[OLMPkDecryption] decryptMessage: olm_pk_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 *__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;
+ }
+
+ size_t ephemeralLength = olm_pk_key_length();
+ NSMutableData *ephemeralBuffer = [NSMutableData dataWithLength:ephemeralLength];
+
+ NSMutableData *pickle = [serializedData dataUsingEncoding:NSUTF8StringEncoding].mutableCopy;
+ size_t result = olm_unpickle_pk_decryption(session,
+ key.bytes, key.length,
+ pickle.mutableBytes, pickle.length,
+ ephemeralBuffer.mutableBytes, ephemeralLength);
+ if (result == olm_error()) {
+ const char *olm_error = olm_pk_decryption_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_pk_decryption_length(session);
+ NSMutableData *pickled = [NSMutableData dataWithLength:length];
+
+ size_t result = olm_pickle_pk_decryption(session,
+ key.bytes, key.length,
+ pickled.mutableBytes, pickled.length);
+ if (result == olm_error()) {
+ const char *olm_error = olm_pk_decryption_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/OLMPkMessage.h b/xcode/OLMKit/OLMPkMessage.h
new file mode 100644
index 0000000..1559fca
--- /dev/null
+++ b/xcode/OLMKit/OLMPkMessage.h
@@ -0,0 +1,31 @@
+/*
+ Copyright 2018 New Vector 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>
+
+NS_ASSUME_NONNULL_BEGIN
+
+@interface OLMPkMessage : NSObject
+
+@property (nonatomic, copy, readonly) NSString *ciphertext;
+@property (nonatomic, copy, readonly,) NSString *mac;
+@property (nonatomic, copy, readonly) NSString *ephemeralKey;
+
+- (instancetype) initWithCiphertext:(NSString*)ciphertext mac:(NSString*)mac ephemeralKey:(NSString*)ephemeralKey;
+
+@end
+
+NS_ASSUME_NONNULL_END
diff --git a/xcode/OLMKit/OLMPkMessage.m b/xcode/OLMKit/OLMPkMessage.m
new file mode 100644
index 0000000..0f24512
--- /dev/null
+++ b/xcode/OLMKit/OLMPkMessage.m
@@ -0,0 +1,32 @@
+/*
+ Copyright 2018 New Vector 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 "OLMPkMessage.h"
+
+@implementation OLMPkMessage
+
+- (instancetype)initWithCiphertext:(NSString *)ciphertext mac:(NSString *)mac ephemeralKey:(NSString *)ephemeralKey {
+ self = [super init];
+ if (!self) {
+ return nil;
+ }
+ _ciphertext = [ciphertext copy];
+ _mac = [mac copy];
+ _ephemeralKey = [ephemeralKey copy];
+ return self;
+}
+
+@end
diff --git a/xcode/OLMKitTests/OLMKitPkTests.m b/xcode/OLMKitTests/OLMKitPkTests.m
new file mode 100644
index 0000000..04d2e30
--- /dev/null
+++ b/xcode/OLMKitTests/OLMKitPkTests.m
@@ -0,0 +1,107 @@
+/*
+ Copyright 2018 New Vector 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>
+
+/**
+ Tests are inspired from js tests.
+ */
+@interface OLMKitPkTests : XCTestCase {
+ OLMPkEncryption *encryption;
+ OLMPkDecryption *decryption;
+}
+
+@end
+
+@implementation OLMKitPkTests
+
+- (void)setUp {
+ encryption = [OLMPkEncryption new];
+ decryption = [OLMPkDecryption new];
+}
+
+- (void)tearDown {
+ encryption = nil;
+ decryption = nil;
+}
+
+- (void)testImportExportKeys {
+ UInt8 alicePrivateBytes[] = {
+ 0x77, 0x07, 0x6D, 0x0A, 0x73, 0x18, 0xA5, 0x7D,
+ 0x3C, 0x16, 0xC1, 0x72, 0x51, 0xB2, 0x66, 0x45,
+ 0xDF, 0x4C, 0x2F, 0x87, 0xEB, 0xC0, 0x99, 0x2A,
+ 0xB1, 0x77, 0xFB, 0xA5, 0x1D, 0xB9, 0x2C, 0x2A
+ };
+
+ NSData *alicePrivate = [NSData dataWithBytes:alicePrivateBytes length:sizeof(alicePrivateBytes)];
+
+ NSError *error;
+ NSString *alicePublic = [decryption setPrivateKey:alicePrivate error:&error];
+ XCTAssertNil(error);
+ XCTAssertEqualObjects(alicePublic, @"hSDwCYkwp1R0i33ctD73Wg2/Og0mOBr066SpjqqbTmo");
+
+ NSData *alicePrivateOut = decryption.privateKey;
+ XCTAssertNil(error);
+ XCTAssertEqualObjects(alicePrivateOut, alicePrivate);
+}
+
+- (void)testEncryptAndDecrypt {
+
+ NSString *pubKey = [decryption generateKey:nil];
+ NSLog(@"Ephemeral Key: %@", pubKey);
+ XCTAssertNotNil(pubKey);
+
+ NSString *TEST_TEXT = @"têst1";
+ NSError *error;
+ [encryption setRecipientKey:pubKey];
+ OLMPkMessage *message = [encryption encryptMessage:TEST_TEXT error:&error];
+ NSLog(@"message: %@ %@ %@", message.ciphertext, message.mac, message.ephemeralKey);
+ XCTAssertNil(error);
+ XCTAssertNotNil(message);
+ XCTAssertNotNil(message.ciphertext);
+ XCTAssertNotNil(message.mac);
+ XCTAssertNotNil(message.ephemeralKey);
+
+ NSString *decrypted = [decryption decryptMessage:message error:&error];
+ XCTAssertNil(error);
+ XCTAssertEqualObjects(decrypted, TEST_TEXT);
+
+ TEST_TEXT = @"hot beverage: ☕";
+ [encryption setRecipientKey:pubKey];
+ message = [encryption encryptMessage:TEST_TEXT error:&error];
+ decrypted = [decryption decryptMessage:message error:&error];
+ XCTAssertEqualObjects(decrypted, TEST_TEXT);
+}
+
+- (void)testOLMPkDecryptionSerialization {
+ NSString *TEST_TEXT = @"têst1";
+ NSString *pubKey = [decryption generateKey:nil];
+ [encryption setRecipientKey:pubKey];
+ OLMPkMessage *encrypted = [encryption encryptMessage:TEST_TEXT error:nil];
+
+
+ NSData *pickle = [NSKeyedArchiver archivedDataWithRootObject:decryption];
+ decryption = nil;
+
+ OLMPkDecryption *newDecryption = [NSKeyedUnarchiver unarchiveObjectWithData:pickle];
+
+ NSError *error;
+ NSString *decrypted = [newDecryption decryptMessage:encrypted error:&error];
+ XCTAssertEqualObjects(decrypted, TEST_TEXT);
+}
+
+@end