// This Source Code Form is subject to the terms of the Mozilla Public // License, v. 2.0. If a copy of the MPL was not distributed with this // file, You can obtain one at http://mozilla.org/MPL/2.0/. #import "OCTToxEncryptSave.h" #import #import "OCTToxEncryptSaveConstants.h" #import "OCTTox+Private.h" @interface OCTToxEncryptSave () @property (assign, nonatomic) Tox_Pass_Key *passKey; @end @implementation OCTToxEncryptSave #pragma mark - Lifecycle - (nullable instancetype)initWithPassphrase:(nonnull NSString *)passphrase toxData:(nullable NSData *)toxData error:(NSError *__nullable *__nullable)error { self = [super init]; if (! self) { return nil; } TOX_ERR_KEY_DERIVATION cError; uint8_t salt[TOX_PASS_SALT_LENGTH]; bool hasSalt = false; if (toxData) { hasSalt = tox_get_salt(toxData.bytes, salt, NULL); } if (hasSalt) { _passKey = tox_pass_key_derive_with_salt( (const uint8_t *)[passphrase cStringUsingEncoding:NSUTF8StringEncoding], [passphrase lengthOfBytesUsingEncoding:NSUTF8StringEncoding], salt, &cError); } else { _passKey = tox_pass_key_derive( (const uint8_t *)[passphrase cStringUsingEncoding:NSUTF8StringEncoding], [passphrase lengthOfBytesUsingEncoding:NSUTF8StringEncoding], &cError); } [OCTToxEncryptSave fillError:error withCErrorKeyDerivation:cError]; return (_passKey != NULL) ? self : nil; } - (void)dealloc { if (_passKey) { tox_pass_key_free(_passKey); } } #pragma mark - Public class methods + (BOOL)isDataEncrypted:(nonnull NSData *)data { return tox_is_data_encrypted(data.bytes); } + (nullable NSData *)encryptData:(nonnull NSData *)data withPassphrase:(nonnull NSString *)passphrase error:(NSError *__nullable *__nullable)error { NSParameterAssert(data); NSParameterAssert(passphrase); return [OCTToxEncryptSave convertDataOfLength:data.length encrypt:YES withConvertBlock:^bool (uint8_t *out) { TOX_ERR_ENCRYPTION cError; bool result = tox_pass_encrypt( data.bytes, data.length, (const uint8_t *)[passphrase cStringUsingEncoding:NSUTF8StringEncoding], [passphrase lengthOfBytesUsingEncoding:NSUTF8StringEncoding], out, &cError); [OCTToxEncryptSave fillError:error withCErrorEncryption:cError]; return result; }]; } + (nullable NSData *)decryptData:(nonnull NSData *)data withPassphrase:(nonnull NSString *)passphrase error:(NSError *__nullable *__nullable)error { NSParameterAssert(data); NSParameterAssert(passphrase); return [OCTToxEncryptSave convertDataOfLength:data.length encrypt:NO withConvertBlock:^bool (uint8_t *out) { TOX_ERR_DECRYPTION cError; bool result = tox_pass_decrypt( data.bytes, data.length, (const uint8_t *)[passphrase cStringUsingEncoding:NSUTF8StringEncoding], [passphrase lengthOfBytesUsingEncoding:NSUTF8StringEncoding], out, &cError); [OCTToxEncryptSave fillError:error withCErrorDecryption:cError]; return result; }]; } #pragma mark - Public instance method - (nullable NSData *)encryptData:(nonnull NSData *)data error:(NSError *__nullable *__nullable)error { NSParameterAssert(data); return [OCTToxEncryptSave convertDataOfLength:data.length encrypt:YES withConvertBlock:^bool (uint8_t *out) { TOX_ERR_ENCRYPTION cError; bool result = tox_pass_key_encrypt( self.passKey, data.bytes, data.length, out, &cError); [OCTToxEncryptSave fillError:error withCErrorEncryption:cError]; return result; }]; } - (nullable NSData *)decryptData:(nonnull NSData *)data error:(NSError *__nullable *__nullable)error { NSParameterAssert(data); return [OCTToxEncryptSave convertDataOfLength:data.length encrypt:NO withConvertBlock:^bool (uint8_t *out) { TOX_ERR_DECRYPTION cError; bool result = tox_pass_key_decrypt( self.passKey, data.bytes, data.length, out, &cError); [OCTToxEncryptSave fillError:error withCErrorDecryption:cError]; return result; }]; } #pragma mark - Private + (NSData *)convertDataOfLength:(NSUInteger)dataLength encrypt:(BOOL)encrypt withConvertBlock:(bool (^)(uint8_t *out))convertBlock { NSUInteger outLength = dataLength + (encrypt ? TOX_PASS_ENCRYPTION_EXTRA_LENGTH : -TOX_PASS_ENCRYPTION_EXTRA_LENGTH); uint8_t *out = malloc(outLength); bool result = convertBlock(out); NSData *resultData = nil; if (result) { resultData = [NSData dataWithBytes:out length:outLength]; } if (out) { free(out); } return resultData; } + (BOOL)fillError:(NSError **)error withCErrorKeyDerivation:(TOX_ERR_KEY_DERIVATION)cError { if (! error || (cError == TOX_ERR_KEY_DERIVATION_OK)) { return NO; } switch (cError) { case TOX_ERR_KEY_DERIVATION_OK: NSAssert(NO, @"We shouldn't be here"); return NO; case TOX_ERR_KEY_DERIVATION_NULL: case TOX_ERR_KEY_DERIVATION_FAILED: *error = [OCTTox createErrorWithCode:OCTToxEncryptSaveKeyDerivationErrorFailed description:@"Cannot create key from given passphrase" failureReason:nil]; break; } return YES; } + (BOOL)fillError:(NSError **)error withCErrorEncryption:(TOX_ERR_ENCRYPTION)cError { if (! error || (cError == TOX_ERR_ENCRYPTION_OK)) { return NO; } OCTToxEncryptSaveEncryptionError code; NSString *description = @"Encryption failed"; NSString *failureReason = nil; switch (cError) { case TOX_ERR_ENCRYPTION_OK: NSAssert(NO, @"We shouldn't be here"); return NO; case TOX_ERR_ENCRYPTION_NULL: code = OCTToxEncryptSaveEncryptionErrorNull; failureReason = @"Some input data was empty."; break; case TOX_ERR_ENCRYPTION_KEY_DERIVATION_FAILED: case TOX_ERR_ENCRYPTION_FAILED: code = OCTToxEncryptSaveEncryptionErrorFailed; failureReason = @"Encryption failed, please report"; break; } *error = [OCTTox createErrorWithCode:code description:description failureReason:failureReason]; return YES; } + (BOOL)fillError:(NSError **)error withCErrorDecryption:(TOX_ERR_DECRYPTION)cError { if (! error || (cError == TOX_ERR_DECRYPTION_OK)) { return NO; } OCTToxEncryptSaveDecryptionError code; NSString *description = @"Decryption failed"; NSString *failureReason = nil; switch (cError) { case TOX_ERR_DECRYPTION_OK: NSAssert(NO, @"We shouldn't be here"); return NO; case TOX_ERR_DECRYPTION_NULL: code = OCTToxEncryptSaveDecryptionErrorNull; failureReason = @"Some input data was empty."; break; case TOX_ERR_DECRYPTION_BAD_FORMAT: code = OCTToxEncryptSaveDecryptionErrorBadFormat; failureReason = @"The input data has bad format"; break; case TOX_ERR_DECRYPTION_INVALID_LENGTH: case TOX_ERR_DECRYPTION_KEY_DERIVATION_FAILED: case TOX_ERR_DECRYPTION_FAILED: code = OCTToxEncryptSaveDecryptionErrorFailed; failureReason = @"Decryption failed, passphrase is incorrect or data is corrupt"; break; } *error = [OCTTox createErrorWithCode:code description:description failureReason:failureReason]; return YES; } @end