From 5ebf0d164f81cd558eee8dd2a869ac08f1e2d617 Mon Sep 17 00:00:00 2001 From: Max Date: Sun, 13 Sep 2015 23:57:05 +0200 Subject: Replace OpenSSL for encrypting session files with Apple's CommonCrypto (part of #2223) --- Source/SPConstants.h | 2 + Source/SPConstants.m | 2 + Source/SPDataAdditions.h | 2 + Source/SPDataAdditions.m | 100 +++++++++++++++++++++++++++++++++++++---------- 4 files changed, 86 insertions(+), 20 deletions(-) (limited to 'Source') diff --git a/Source/SPConstants.h b/Source/SPConstants.h index 2c2555a8..f2816745 100644 --- a/Source/SPConstants.h +++ b/Source/SPConstants.h @@ -622,6 +622,8 @@ extern NSString *SPURLSchemeQueryResultPathHeader; extern NSString *SPURLSchemeQueryResultStatusPathHeader; extern NSString *SPURLSchemeQueryResultMetaPathHeader; +extern NSString *SPCommonCryptoExceptionName; + #define SPAppDelegate ((SPAppController *)[NSApp delegate]) // Provides a standard method for our "[x release], x = nil;" convention. diff --git a/Source/SPConstants.m b/Source/SPConstants.m index 5f3348ed..42631dab 100644 --- a/Source/SPConstants.m +++ b/Source/SPConstants.m @@ -421,6 +421,8 @@ NSString *SPURLSchemeQueryResultPathHeader = @"/tmp/SP_QUERY_RESULT_"; NSString *SPURLSchemeQueryResultStatusPathHeader = @"/tmp/SP_QUERY_RESULT_STATUS_"; NSString *SPURLSchemeQueryResultMetaPathHeader = @"/tmp/SP_QUERY_META_"; +NSString *SPCommonCryptoExceptionName = @"SPCommonCryptoException"; + void inline _SPClear(id *addr) { [*addr release], *addr = nil; } diff --git a/Source/SPDataAdditions.h b/Source/SPDataAdditions.h index 118f47e2..6c404b9e 100644 --- a/Source/SPDataAdditions.h +++ b/Source/SPDataAdditions.h @@ -33,7 +33,9 @@ - (NSData *)sha1Hash; - (NSData *)dataEncryptedWithPassword:(NSString *)password; +- (NSData *)dataEncryptedWithKey:(NSData *)aesKey IV:(NSData *)iv; - (NSData *)dataDecryptedWithPassword:(NSString *)password; + - (NSData *)compress; - (NSData *)decompress; diff --git a/Source/SPDataAdditions.m b/Source/SPDataAdditions.m index 23280c8b..4a07c437 100644 --- a/Source/SPDataAdditions.m +++ b/Source/SPDataAdditions.m @@ -74,38 +74,98 @@ uint32_t LimitUInt32(NSUInteger i); - (NSData *)dataEncryptedWithPassword:(NSString *)password { // Create a random 128-bit initialization vector + // IV is block "-1" of plaintext data, therefore it is blockSize long srand((unsigned int)time(NULL)); NSInteger ivIndex; - unsigned char iv[16]; - for (ivIndex = 0; ivIndex < 16; ivIndex++) + unsigned char iv[kCCBlockSizeAES128]; + for (ivIndex = 0; ivIndex < kCCBlockSizeAES128; ivIndex++) iv[ivIndex] = rand() & 0xff; + NSData *ivData = [NSData dataWithBytes:iv length:sizeof(iv)]; + + // Create the key from first 128-bits of the 160-bit password hash + NSData *passwordDigest = [[[password dataUsingEncoding:NSUTF8StringEncoding] sha1Hash] subdataWithRange:NSMakeRange(0, kCCKeySizeAES128)]; + + return [self dataEncryptedWithKey:passwordDigest IV:ivData]; +} - // Calculate the 16-byte AES block padding - NSInteger dataLength = [self length]; - NSInteger paddedLength = dataLength + (32 - (dataLength % 16)); - NSInteger totalLength = paddedLength + 16; // Data plus IV +/* + * ABNF for the returned data: + * OCTET = + * ENCRYPTED = IV AES + * IV = 16OCTET ; 16 random bytes + * AES = + * PADDED = PLAINTEXT 12*28OCTET LEN ; 13-28 bytes padding (value irrelevant) + * PLAINTEXT = *OCTET ; the raw data + * LEN = 4OCTET ; big endian length of plaintext + * + * Examples for padding: + * Data len padding len = total + * --------- -------- ----- -------- + * 0 28 4 32 + * 1 27 4 32 + * ... + * 15 13 4 32 + * 16 28 4 48 + * 17 27 4 48 + * ... + * + * Note that total has to be a multiple of 16 for AES 128. + * Our padding scheme also requires 4 bytes of storage for len. + * This is were the 32 comes from: Without that 15 data bytes would produce + * only 1 padding byte, which is not enough to store the 4 byte len. + */ +- (NSData *)dataEncryptedWithKey:(NSData *)aesKey IV:(NSData *)iv +{ + if([self length] > UINT32_MAX) + @throw [NSException exceptionWithName:NSInvalidArgumentException reason:@"Length of NSData exceeds 32 Bit, not supported!" userInfo:nil]; + + if([iv length] != kCCBlockSizeAES128) + @throw [NSException exceptionWithName:NSInvalidArgumentException reason:@"Length of ivData must be == kCCBlockSizeAES128!" userInfo:nil]; + if([aesKey length] != kCCKeySizeAES128) + @throw [NSException exceptionWithName:NSInvalidArgumentException reason:@"Key length invalid. Must be kCCKeySizeAES128 bytes!" userInfo:nil]; + + // Calculate the 16-byte AES block padding + uint32_t dataLength = (uint32_t)[self length]; + NSInteger paddedLength = dataLength + (2*kCCBlockSizeAES128 - (dataLength % kCCBlockSizeAES128)); + NSInteger totalLength = paddedLength + kCCBlockSizeAES128; // Data plus IV + // Allocate enough space for the IV + ciphertext unsigned char *encryptedBytes = calloc(1, totalLength); // The first block of the ciphertext buffer is the IV - memcpy(encryptedBytes, iv, 16); + memcpy(encryptedBytes, [iv bytes], kCCBlockSizeAES128); - unsigned char *paddedBytes = calloc(1, paddedLength); + unsigned char *paddedBytes = encryptedBytes + kCCBlockSizeAES128; memcpy(paddedBytes, [self bytes], dataLength); // The last 32-bit chunk is the size of the plaintext, which is encrypted with the plaintext - NSInteger bigIntDataLength = NSSwapHostIntToBig((unsigned int)dataLength); - memcpy(paddedBytes + (paddedLength - 4), &bigIntDataLength, 4); - - // Create the key from first 128-bits of the 160-bit password hash - NSData *passwordDigest = [[password dataUsingEncoding:NSUTF8StringEncoding] sha1Hash]; - AES_KEY aesKey; - AES_set_encrypt_key([passwordDigest bytes], 128, &aesKey); - - // AES-128-cbc encrypt the data, filling in the buffer after the IV - AES_cbc_encrypt(paddedBytes, encryptedBytes + 16, paddedLength, &aesKey, iv, AES_ENCRYPT); - free(paddedBytes); - + uint32_t bigIntDataLength = NSSwapHostIntToBig(dataLength); + unsigned char *lenPtr = paddedBytes + (paddedLength - 4); + memcpy(lenPtr, &bigIntDataLength, 4); + + CCCryptorStatus res = CCCrypt( + kCCEncrypt, // operation mode + kCCAlgorithmAES128, // algorithm + 0, // options. We use our own padding algorithm and CBC is the default + [aesKey bytes], // key bytes + kCCKeySizeAES128, // key length + [iv bytes], // iv bytes (length == block size) + paddedBytes, // raw data + paddedLength, // length of raw data + paddedBytes, // output buffer. overwriting input is OK + paddedLength, // output buffer size + NULL // number of bytes written. not relevant here + ); + + if(res != kCCSuccess) + @throw [NSException exceptionWithName:SPCommonCryptoExceptionName + reason:[NSString stringWithFormat:@"CCCrypt() failed! (CCCryptorStatus=%d)",res] + userInfo:@{@"cryptorStatus":@(res)}]; + + // the return code of CCCrypt() is not always reliable, better check it again + if(memcmp(lenPtr, &bigIntDataLength, 4) == 0) + @throw [NSException exceptionWithName:NSInternalInconsistencyException reason:@"Encrypted data is same as plaintext data!" userInfo:nil]; + return [NSData dataWithBytesNoCopy:encryptedBytes length:totalLength]; } -- cgit v1.2.3