b69b56d0bb
Improve ObjC wrappers so that they can use a pickle key provided by the olm lib user. This new behavior is optional to not break existing usage. It is retro compatible and use pickle versioning already in place. Existing key will be unpickled with pickle v1 and pickled with pickle v2 if an external pickle key is provided.
315 lines
12 KiB
Objective-C
315 lines
12 KiB
Objective-C
/*
|
|
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"
|
|
#import "OLMKit.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];
|
|
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);
|
|
[random resetBytesInRange:NSMakeRange(0, 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];
|
|
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;
|
|
}
|
|
|
|
+ (NSUInteger)privateKeyLength {
|
|
return olm_pk_private_key_length();
|
|
}
|
|
|
|
#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);
|
|
[pickle resetBytesInRange:NSMakeRange(0, pickle.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;
|
|
}
|
|
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];
|
|
[pickled resetBytesInRange:NSMakeRange(0, pickled.length)];
|
|
|
|
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];
|
|
}
|
|
else if ([version isEqualToString:@"2"]) {
|
|
NSString *pickle = [decoder decodeObjectOfClass:[NSString class] forKey:@"pickle"];
|
|
NSData *key = OLMKit.sharedInstance.pickleKeyDelegate.pickleKey;
|
|
NSParameterAssert(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 = OLMKit.sharedInstance.pickleKeyDelegate.pickleKey;
|
|
if (key)
|
|
{
|
|
[encoder encodeObject:@"2" forKey:@"version"];
|
|
}
|
|
else
|
|
{
|
|
key = [OLMUtility randomBytesOfLength:32];
|
|
[encoder encodeObject:key forKey:@"key"];
|
|
[encoder encodeObject:@"1" forKey:@"version"];
|
|
}
|
|
|
|
NSError *error = nil;
|
|
NSString *pickle = [self serializeDataWithKey:key error:&error];
|
|
NSParameterAssert(pickle.length > 0 && error == nil);
|
|
|
|
[encoder encodeObject:pickle forKey:@"pickle"];
|
|
}
|
|
|
|
@end
|