mirror of https://github.com/oxen-io/session-ios
				
				
				
			
			You cannot select more than 25 topics
			Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
		
		
		
		
		
			
		
			
				
	
	
		
			274 lines
		
	
	
		
			7.9 KiB
		
	
	
	
		
			Objective-C
		
	
			
		
		
	
	
			274 lines
		
	
	
		
			7.9 KiB
		
	
	
	
		
			Objective-C
		
	
//
 | 
						|
//  Copyright (c) 2018 Open Whisper Systems. All rights reserved.
 | 
						|
//
 | 
						|
 | 
						|
#import "OWSBackupIO.h"
 | 
						|
#import <SignalCoreKit/Randomness.h>
 | 
						|
#import <SessionUtilitiesKit/OWSFileSystem.h>
 | 
						|
 | 
						|
@import Compression;
 | 
						|
 | 
						|
NS_ASSUME_NONNULL_BEGIN
 | 
						|
 | 
						|
// TODO:
 | 
						|
static const NSUInteger kOWSBackupKeyLength = 32;
 | 
						|
 | 
						|
// LZMA algorithm significantly outperforms the other compressionlib options
 | 
						|
// for our database snapshots and is a widely adopted standard.
 | 
						|
static const compression_algorithm SignalCompressionAlgorithm = COMPRESSION_LZMA;
 | 
						|
 | 
						|
@implementation OWSBackupEncryptedItem
 | 
						|
 | 
						|
@end
 | 
						|
 | 
						|
#pragma mark -
 | 
						|
 | 
						|
@interface OWSBackupIO ()
 | 
						|
 | 
						|
@property (nonatomic) NSString *jobTempDirPath;
 | 
						|
 | 
						|
@end
 | 
						|
 | 
						|
#pragma mark -
 | 
						|
 | 
						|
@implementation OWSBackupIO
 | 
						|
 | 
						|
- (instancetype)initWithJobTempDirPath:(NSString *)jobTempDirPath
 | 
						|
{
 | 
						|
    if (!(self = [super init])) {
 | 
						|
        return self;
 | 
						|
    }
 | 
						|
 | 
						|
    self.jobTempDirPath = jobTempDirPath;
 | 
						|
 | 
						|
    return self;
 | 
						|
}
 | 
						|
 | 
						|
- (NSString *)generateTempFilePath
 | 
						|
{
 | 
						|
    return [self.jobTempDirPath stringByAppendingPathComponent:[NSUUID UUID].UUIDString];
 | 
						|
}
 | 
						|
 | 
						|
- (nullable NSString *)createTempFile
 | 
						|
{
 | 
						|
    NSString *filePath = [self generateTempFilePath];
 | 
						|
    if (![OWSFileSystem ensureFileExists:filePath]) {
 | 
						|
        OWSFailDebug(@"could not create temp file.");
 | 
						|
        return nil;
 | 
						|
    }
 | 
						|
    return filePath;
 | 
						|
}
 | 
						|
 | 
						|
#pragma mark - Encrypt
 | 
						|
 | 
						|
- (nullable OWSBackupEncryptedItem *)encryptFileAsTempFile:(NSString *)srcFilePath
 | 
						|
{
 | 
						|
    OWSAssertDebug(srcFilePath.length > 0);
 | 
						|
 | 
						|
    NSData *encryptionKey = [Randomness generateRandomBytes:(int)kOWSBackupKeyLength];
 | 
						|
 | 
						|
    return [self encryptFileAsTempFile:srcFilePath encryptionKey:encryptionKey];
 | 
						|
}
 | 
						|
 | 
						|
- (nullable OWSBackupEncryptedItem *)encryptFileAsTempFile:(NSString *)srcFilePath encryptionKey:(NSData *)encryptionKey
 | 
						|
{
 | 
						|
    OWSAssertDebug(srcFilePath.length > 0);
 | 
						|
    OWSAssertDebug(encryptionKey.length > 0);
 | 
						|
 | 
						|
    @autoreleasepool {
 | 
						|
        if (![[NSFileManager defaultManager] fileExistsAtPath:srcFilePath]) {
 | 
						|
            OWSFailDebug(@"Missing source file.");
 | 
						|
            return nil;
 | 
						|
        }
 | 
						|
 | 
						|
        // TODO: Encrypt the file without loading it into memory.
 | 
						|
        NSData *_Nullable srcData = [NSData dataWithContentsOfFile:srcFilePath];
 | 
						|
        if (srcData.length < 1) {
 | 
						|
            OWSFailDebug(@"could not load file into memory for encryption.");
 | 
						|
            return nil;
 | 
						|
        }
 | 
						|
        return [self encryptDataAsTempFile:srcData encryptionKey:encryptionKey];
 | 
						|
    }
 | 
						|
}
 | 
						|
 | 
						|
- (nullable OWSBackupEncryptedItem *)encryptDataAsTempFile:(NSData *)srcData
 | 
						|
{
 | 
						|
    OWSAssertDebug(srcData);
 | 
						|
 | 
						|
    NSData *encryptionKey = [Randomness generateRandomBytes:(int)kOWSBackupKeyLength];
 | 
						|
 | 
						|
    return [self encryptDataAsTempFile:srcData encryptionKey:encryptionKey];
 | 
						|
}
 | 
						|
 | 
						|
- (nullable OWSBackupEncryptedItem *)encryptDataAsTempFile:(NSData *)unencryptedData
 | 
						|
                                             encryptionKey:(NSData *)encryptionKey
 | 
						|
{
 | 
						|
    OWSAssertDebug(unencryptedData);
 | 
						|
    OWSAssertDebug(encryptionKey.length > 0);
 | 
						|
 | 
						|
    @autoreleasepool {
 | 
						|
 | 
						|
        // TODO: Encrypt the data using key;
 | 
						|
        NSData *encryptedData = unencryptedData;
 | 
						|
 | 
						|
        NSString *_Nullable dstFilePath = [self createTempFile];
 | 
						|
        if (!dstFilePath) {
 | 
						|
            return nil;
 | 
						|
        }
 | 
						|
        NSError *error;
 | 
						|
        BOOL success = [encryptedData writeToFile:dstFilePath options:NSDataWritingAtomic error:&error];
 | 
						|
        if (!success || error) {
 | 
						|
            OWSFailDebug(@"error writing encrypted data: %@", error);
 | 
						|
            return nil;
 | 
						|
        }
 | 
						|
        [OWSFileSystem protectFileOrFolderAtPath:dstFilePath];
 | 
						|
        OWSBackupEncryptedItem *item = [OWSBackupEncryptedItem new];
 | 
						|
        item.filePath = dstFilePath;
 | 
						|
        item.encryptionKey = encryptionKey;
 | 
						|
        return item;
 | 
						|
    }
 | 
						|
}
 | 
						|
 | 
						|
#pragma mark - Decrypt
 | 
						|
 | 
						|
- (BOOL)decryptFileAsFile:(NSString *)srcFilePath
 | 
						|
              dstFilePath:(NSString *)dstFilePath
 | 
						|
            encryptionKey:(NSData *)encryptionKey
 | 
						|
{
 | 
						|
    OWSAssertDebug(srcFilePath.length > 0);
 | 
						|
    OWSAssertDebug(encryptionKey.length > 0);
 | 
						|
 | 
						|
    @autoreleasepool {
 | 
						|
 | 
						|
        // TODO: Decrypt the file without loading it into memory.
 | 
						|
        NSData *data = [self decryptFileAsData:srcFilePath encryptionKey:encryptionKey];
 | 
						|
 | 
						|
        if (data.length < 1) {
 | 
						|
            return NO;
 | 
						|
        }
 | 
						|
 | 
						|
        NSError *error;
 | 
						|
        BOOL success = [data writeToFile:dstFilePath options:NSDataWritingAtomic error:&error];
 | 
						|
        if (!success || error) {
 | 
						|
            OWSFailDebug(@"error writing decrypted data: %@", error);
 | 
						|
            return NO;
 | 
						|
        }
 | 
						|
        [OWSFileSystem protectFileOrFolderAtPath:dstFilePath];
 | 
						|
 | 
						|
        return YES;
 | 
						|
    }
 | 
						|
}
 | 
						|
 | 
						|
- (nullable NSData *)decryptFileAsData:(NSString *)srcFilePath encryptionKey:(NSData *)encryptionKey
 | 
						|
{
 | 
						|
    OWSAssertDebug(srcFilePath.length > 0);
 | 
						|
    OWSAssertDebug(encryptionKey.length > 0);
 | 
						|
 | 
						|
    @autoreleasepool {
 | 
						|
 | 
						|
        if (![NSFileManager.defaultManager fileExistsAtPath:srcFilePath]) {
 | 
						|
            OWSLogError(@"missing downloaded file.");
 | 
						|
            return nil;
 | 
						|
        }
 | 
						|
 | 
						|
        NSData *_Nullable srcData = [NSData dataWithContentsOfFile:srcFilePath];
 | 
						|
        if (srcData.length < 1) {
 | 
						|
            OWSFailDebug(@"could not load file into memory for decryption.");
 | 
						|
            return nil;
 | 
						|
        }
 | 
						|
 | 
						|
        NSData *_Nullable dstData = [self decryptDataAsData:srcData encryptionKey:encryptionKey];
 | 
						|
        return dstData;
 | 
						|
    }
 | 
						|
}
 | 
						|
 | 
						|
- (nullable NSData *)decryptDataAsData:(NSData *)encryptedData encryptionKey:(NSData *)encryptionKey
 | 
						|
{
 | 
						|
    OWSAssertDebug(encryptedData);
 | 
						|
    OWSAssertDebug(encryptionKey.length > 0);
 | 
						|
 | 
						|
    @autoreleasepool {
 | 
						|
 | 
						|
        // TODO: Decrypt the data using key;
 | 
						|
        NSData *unencryptedData = encryptedData;
 | 
						|
 | 
						|
        return unencryptedData;
 | 
						|
    }
 | 
						|
}
 | 
						|
 | 
						|
#pragma mark - Compression
 | 
						|
 | 
						|
- (nullable NSData *)compressData:(NSData *)srcData
 | 
						|
{
 | 
						|
    OWSAssertDebug(srcData);
 | 
						|
 | 
						|
    @autoreleasepool {
 | 
						|
 | 
						|
        if (!srcData) {
 | 
						|
            OWSFailDebug(@"missing unencrypted data.");
 | 
						|
            return nil;
 | 
						|
        }
 | 
						|
 | 
						|
        size_t srcLength = [srcData length];
 | 
						|
 | 
						|
        // This assumes that dst will always be smaller than src.
 | 
						|
        //
 | 
						|
        // We slightly pad the buffer size to account for the worst case.
 | 
						|
        size_t dstBufferLength = srcLength + 64 * 1024;
 | 
						|
        NSMutableData *dstBufferData = [NSMutableData dataWithLength:dstBufferLength];
 | 
						|
        if (!dstBufferData) {
 | 
						|
            OWSFailDebug(@"Failed to allocate buffer.");
 | 
						|
            return nil;
 | 
						|
        }
 | 
						|
 | 
						|
        size_t dstLength = compression_encode_buffer(
 | 
						|
            dstBufferData.mutableBytes, dstBufferLength, srcData.bytes, srcLength, NULL, SignalCompressionAlgorithm);
 | 
						|
        NSData *compressedData = [dstBufferData subdataWithRange:NSMakeRange(0, dstLength)];
 | 
						|
 | 
						|
        OWSLogVerbose(@"compressed %zd -> %zd = %0.2f",
 | 
						|
            srcLength,
 | 
						|
            dstLength,
 | 
						|
            (srcLength > 0 ? (dstLength / (CGFloat)srcLength) : 0));
 | 
						|
 | 
						|
        return compressedData;
 | 
						|
    }
 | 
						|
}
 | 
						|
 | 
						|
- (nullable NSData *)decompressData:(NSData *)srcData uncompressedDataLength:(NSUInteger)uncompressedDataLength
 | 
						|
{
 | 
						|
    OWSAssertDebug(srcData);
 | 
						|
 | 
						|
    @autoreleasepool {
 | 
						|
 | 
						|
        if (!srcData) {
 | 
						|
            OWSFailDebug(@"missing unencrypted data.");
 | 
						|
            return nil;
 | 
						|
        }
 | 
						|
 | 
						|
        size_t srcLength = [srcData length];
 | 
						|
 | 
						|
        // We pad the buffer to be defensive.
 | 
						|
        size_t dstBufferLength = uncompressedDataLength + 1024;
 | 
						|
        NSMutableData *dstBufferData = [NSMutableData dataWithLength:dstBufferLength];
 | 
						|
        if (!dstBufferData) {
 | 
						|
            OWSFailDebug(@"Failed to allocate buffer.");
 | 
						|
            return nil;
 | 
						|
        }
 | 
						|
 | 
						|
        size_t dstLength = compression_decode_buffer(
 | 
						|
            dstBufferData.mutableBytes, dstBufferLength, srcData.bytes, srcLength, NULL, SignalCompressionAlgorithm);
 | 
						|
        NSData *decompressedData = [dstBufferData subdataWithRange:NSMakeRange(0, dstLength)];
 | 
						|
        OWSAssertDebug(decompressedData.length == uncompressedDataLength);
 | 
						|
        OWSLogVerbose(@"decompressed %zd -> %zd = %0.2f",
 | 
						|
            srcLength,
 | 
						|
            dstLength,
 | 
						|
            (dstLength > 0 ? (srcLength / (CGFloat)dstLength) : 0));
 | 
						|
 | 
						|
        return decompressedData;
 | 
						|
    }
 | 
						|
}
 | 
						|
 | 
						|
@end
 | 
						|
 | 
						|
NS_ASSUME_NONNULL_END
 |