diff options
-rw-r--r-- | Source/SPDataAdditions.h | 1 | ||||
-rw-r--r-- | Source/SPDataAdditions.m | 58 | ||||
-rw-r--r-- | UnitTests/SPDataAdditionsTests.m | 95 | ||||
-rw-r--r-- | sequel-pro.xcodeproj/project.pbxproj | 27 |
4 files changed, 148 insertions, 33 deletions
diff --git a/Source/SPDataAdditions.h b/Source/SPDataAdditions.h index 6c404b9e..5158c270 100644 --- a/Source/SPDataAdditions.h +++ b/Source/SPDataAdditions.h @@ -35,6 +35,7 @@ - (NSData *)dataEncryptedWithPassword:(NSString *)password; - (NSData *)dataEncryptedWithKey:(NSData *)aesKey IV:(NSData *)iv; - (NSData *)dataDecryptedWithPassword:(NSString *)password; +- (NSData *)dataDecryptedWithKey:(NSData *)key; - (NSData *)compress; - (NSData *)decompress; diff --git a/Source/SPDataAdditions.m b/Source/SPDataAdditions.m index 4a07c437..8002595c 100644 --- a/Source/SPDataAdditions.m +++ b/Source/SPDataAdditions.m @@ -35,7 +35,6 @@ #import "SPDataAdditions.h" #include <zlib.h> -#include <openssl/aes.h> #include <CommonCrypto/CommonCrypto.h> #include <stdlib.h> @@ -172,23 +171,50 @@ uint32_t LimitUInt32(NSUInteger i); - (NSData *)dataDecryptedWithPassword:(NSString *)password { // Create the key from the password hash - NSData *passwordDigest = [[password dataUsingEncoding:NSUTF8StringEncoding] sha1Hash]; + NSData *passwordDigest = [[[password dataUsingEncoding:NSUTF8StringEncoding] sha1Hash] subdataWithRange:NSMakeRange(0, kCCKeySizeAES128)]; + + return [self dataDecryptedWithKey:passwordDigest]; - // AES-128-cbc decrypt the data - AES_KEY aesKey; - AES_set_decrypt_key([passwordDigest bytes], 128, &aesKey); +} + +- (NSData *)dataDecryptedWithKey:(NSData *)aesKey +{ + if([aesKey length] != kCCKeySizeAES128) + @throw [NSException exceptionWithName:NSInvalidArgumentException reason:@"Key length invalid. Must be kCCKeySizeAES128 bytes!" userInfo:nil]; + + if([self length] < (2*kCCBlockSizeAES128) || [self length] > UINT32_MAX) + @throw [NSException exceptionWithName:NSInvalidArgumentException reason:@"Length of encrypted NSData must be in range 32 to 2^32!" userInfo:nil]; // Total length = encrypted length + IV - NSInteger totalLength = [self length]; - NSInteger encryptedLength = totalLength - 16; + NSUInteger totalLength = [self length]; + NSUInteger encryptedLength = totalLength - kCCBlockSizeAES128; // >=0 ensured above // Take the IV from the first 128-bit block - unsigned char iv[16]; - memcpy(iv, [self bytes], 16); + unsigned char iv[kCCBlockSizeAES128]; + memcpy(iv, [self bytes], kCCBlockSizeAES128); // Decrypt the data - unsigned char *decryptedBytes = (unsigned char*)malloc(encryptedLength); - AES_cbc_encrypt([self bytes] + 16, decryptedBytes, encryptedLength, &aesKey, iv, AES_DECRYPT); + unsigned char *decryptedBytes = calloc(1,encryptedLength); + + CCCryptorStatus res = CCCrypt( + kCCDecrypt, // 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, // iv bytes (length == block size) + ([self bytes] + kCCBlockSizeAES128), // raw data + encryptedLength, // length of raw data + decryptedBytes, // output buffer. overwriting input is OK + encryptedLength, // 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)}]; + } // If decryption was successful, these blocks will be zeroed if ( *((UInt32*)decryptedBytes + ((encryptedLength / 4) - 4)) || @@ -200,8 +226,14 @@ uint32_t LimitUInt32(NSUInteger i); } // Get the size of the data from the last 32-bit chunk - NSInteger bigIntDataLength = *((UInt32*)decryptedBytes + ((encryptedLength / 4) - 1)); - NSInteger dataLength = NSSwapBigIntToHost((unsigned int)bigIntDataLength); + uint32_t bigIntDataLength = *((UInt32*)decryptedBytes + ((encryptedLength / sizeof(UInt32)) - 1)); + uint32_t dataLength = NSSwapBigIntToHost(bigIntDataLength); + + if(dataLength >= (encryptedLength-sizeof(UInt32))) { //this way dataLength can still reach into padding, but we own that memory anyway. + @throw [NSException exceptionWithName:NSInternalInconsistencyException + reason:[NSString stringWithFormat:@"dataLength=%u exceeds encryptedLength=%lu! Either the message is incomplete, decrypting resulted in invalid data, or this is a malicious message!",dataLength,encryptedLength] + userInfo:nil]; + } return [NSData dataWithBytesNoCopy:decryptedBytes length:dataLength]; } diff --git a/UnitTests/SPDataAdditionsTests.m b/UnitTests/SPDataAdditionsTests.m index 0ecad9e7..812eb45d 100644 --- a/UnitTests/SPDataAdditionsTests.m +++ b/UnitTests/SPDataAdditionsTests.m @@ -38,6 +38,8 @@ - (void)testSha1Hash; - (void)testDataEncryptedWithPassword; - (void)testDataEncryptedWithKeyIV; +- (void)testDataDecryptedWithPassword; +- (void)testDataDecryptedWithKey; @end @@ -186,4 +188,97 @@ } } +- (void)testDataDecryptedWithPassword +{ + //see test above + NSData *raw = [@" " dataUsingEncoding:NSASCIIStringEncoding]; + unsigned char encrypted[] = { + 0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, 0x38, 0x39, 0x41, + 0x42, 0x43, 0x44, 0x45, 0x46, 0xd3, 0x58, 0x30, 0x95, 0x6d, 0x7f, + 0xf5, 0x1e, 0x18, 0xb0, 0xbc, 0x1f, 0xb3, 0xe4, 0x52, 0xb1, 0x75, + 0x4c, 0xc3, 0x52, 0xd0, 0x93, 0xad, 0xff, 0x36, 0x4a, 0xae, 0xbe, + 0x60, 0x32, 0xdd, 0x71, 0xef, 0xce, 0x2e, 0x8b, 0x09, 0xcb, 0x9a, + 0x44, 0x32, 0xb3, 0xda, 0x42, 0x58, 0x29, 0x78, 0xc3 + }; + NSData *encData = [NSData dataWithBytesNoCopy:encrypted length:sizeof(encrypted) freeWhenDone:NO]; + + NSData *decrypted = [encData dataDecryptedWithPassword:@""]; + + STAssertEqualObjects(decrypted, raw, @"Decrypt simple data encrypted with empty password"); +} + +- (void)testDataDecryptedWithKey +{ + NSData *raw = [@" " dataUsingEncoding:NSASCIIStringEncoding]; + + unsigned char keyRaw[] = {0xda,0x39,0xa3,0xee,0x5e,0x6b,0x4b,0x0d,0x32,0x55,0xbf,0xef,0x95,0x60,0x18,0x90,0xaf,0xd8,0x07,0x09}; // sha1("") + NSData *key = [NSData dataWithBytes:keyRaw length:16]; + + unsigned char encrypted[] = { + 0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, 0x38, 0x39, 0x41, + 0x42, 0x43, 0x44, 0x45, 0x46, 0xd3, 0x58, 0x30, 0x95, 0x6d, 0x7f, + 0xf5, 0x1e, 0x18, 0xb0, 0xbc, 0x1f, 0xb3, 0xe4, 0x52, 0xb1, 0x75, + 0x4c, 0xc3, 0x52, 0xd0, 0x93, 0xad, 0xff, 0x36, 0x4a, 0xae, 0xbe, + 0x60, 0x32, 0xdd, 0x71, 0xef, 0xce, 0x2e, 0x8b, 0x09, 0xcb, 0x9a, + 0x44, 0x32, 0xb3, 0xda, 0x42, 0x58, 0x29, 0x78, 0xc3 + }; + NSData *encData = [NSData dataWithBytesNoCopy:encrypted length:sizeof(encrypted) freeWhenDone:NO]; + + // invalid key length + { + @try { + [encData dataDecryptedWithKey:[NSData data]]; + STFail(@"Invalid key length!"); + } + @catch (NSException *exception) { + //expected + } + } + // data too short for encryption + { + @try { + [[@"Hello World!" dataUsingEncoding:NSASCIIStringEncoding] dataDecryptedWithKey:key]; + STFail(@"Invalid data length!"); + } + @catch (NSException *exception) { + //expected + } + } + // wrong data with valid length + { + NSData *inp = [@"12345678901234567890123456789012" dataUsingEncoding:NSASCIIStringEncoding]; + STAssertNil([inp dataDecryptedWithKey:key], @"Trying to decrypt invalid data."); + } + // wrong data with invalid length + { + NSData *inp = [@"12345678901234567890123456789012345678901234567" dataUsingEncoding:NSASCIIStringEncoding]; + STAssertNil([inp dataDecryptedWithKey:key], @"Trying to decrypt data with invalid length."); + } + // simple decryption test + { + NSData *decrypted = [encData dataDecryptedWithKey:key]; + STAssertEqualObjects(decrypted, raw, @"Simple Decryption test"); + } + // malicious message test + { + //this is an empty message with a length field set to UINT32_MAX + unsigned char _encrypted[] = { + 0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, 0x38, 0x39, 0x41, + 0x42, 0x43, 0x44, 0x45, 0x46, 0x50, 0xc9, 0xca, 0x75, 0x14, 0xd3, + 0x6e, 0xec, 0x9e, 0xc6, 0x4c, 0x25, 0x02, 0x33, 0xdd, 0x86, 0x54, + 0xea, 0x1a, 0x0d, 0xe9, 0x88, 0xe3, 0xeb, 0xcb, 0xb7, 0x01, 0x52, + 0x42, 0x1c, 0xd8, 0xd5 + }; + NSData *_encData = [NSData dataWithBytesNoCopy:_encrypted length:sizeof(_encrypted) freeWhenDone:NO]; + + @try { + [_encData dataDecryptedWithKey:key]; + STFail(@"Malicious message with invalid data length"); + } + @catch (NSException *exception) { + //expected + } + } +} + @end diff --git a/sequel-pro.xcodeproj/project.pbxproj b/sequel-pro.xcodeproj/project.pbxproj index c2c7e035..0958501d 100644 --- a/sequel-pro.xcodeproj/project.pbxproj +++ b/sequel-pro.xcodeproj/project.pbxproj @@ -181,7 +181,8 @@ 501B1D181728A3DA0017C92E /* SPCharsetCollationHelper.m in Sources */ = {isa = PBXBuildFile; fileRef = 501B1D171728A3DA0017C92E /* SPCharsetCollationHelper.m */; }; 502D21F61BA50710000D4CE7 /* SPDataAdditionsTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 502D21F51BA50710000D4CE7 /* SPDataAdditionsTests.m */; }; 502D21F81BA50966000D4CE7 /* SPDataAdditions.m in Sources */ = {isa = PBXBuildFile; fileRef = BC2C16D30FEBEDF10003993B /* SPDataAdditions.m */; }; - 502D21FA1BA509AF000D4CE7 /* libcrypto.dylib in Frameworks */ = {isa = PBXBuildFile; fileRef = 502D21F91BA509AF000D4CE7 /* libcrypto.dylib */; }; + 502D22151BA62FA5000D4CE7 /* Security.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = B5EAC0FC0EC87FF900CC579C /* Security.framework */; }; + 502D22161BA62FF5000D4CE7 /* Security.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = B5EAC0FC0EC87FF900CC579C /* Security.framework */; }; 503B02CA1AE82C5E0060CAB1 /* SPTableFilterParser.m in Sources */ = {isa = PBXBuildFile; fileRef = 503B02C91AE82C5E0060CAB1 /* SPTableFilterParser.m */; }; 503B02CF1AE95C2C0060CAB1 /* SPTableFilterParserTest.m in Sources */ = {isa = PBXBuildFile; fileRef = 503B02CE1AE95C2C0060CAB1 /* SPTableFilterParserTest.m */; }; 503B02D11AE95DD40060CAB1 /* SPTableFilterParser.m in Sources */ = {isa = PBXBuildFile; fileRef = 503B02C91AE82C5E0060CAB1 /* SPTableFilterParser.m */; }; @@ -409,7 +410,6 @@ BC09D7E312A786FB0030DB64 /* cancel.png in Resources */ = {isa = PBXBuildFile; fileRef = BC09D7DD12A786FB0030DB64 /* cancel.png */; }; BC0E1486120AAB5600E52E25 /* SPDataAdditions.m in Sources */ = {isa = PBXBuildFile; fileRef = BC2C16D30FEBEDF10003993B /* SPDataAdditions.m */; }; BC0E1487120AAB5C00E52E25 /* SPStringAdditions.m in Sources */ = {isa = PBXBuildFile; fileRef = 1789343B0F30C1DD0097539A /* SPStringAdditions.m */; }; - BC0E1493120AABE900E52E25 /* libcrypto.dylib in Frameworks */ = {isa = PBXBuildFile; fileRef = 17B7B58F1016028F00F057DE /* libcrypto.dylib */; }; BC0E14A1120AAC2E00E52E25 /* libbz2.dylib in Frameworks */ = {isa = PBXBuildFile; fileRef = 179ECEC611F265EE009C6A40 /* libbz2.dylib */; }; BC0ED3DA12A9196C00088461 /* SPChooseMenuItemDialog.m in Sources */ = {isa = PBXBuildFile; fileRef = BC0ED3D912A9196C00088461 /* SPChooseMenuItemDialog.m */; }; BC1847EA0FE6EC8400094BFB /* SPEditSheetTextView.m in Sources */ = {isa = PBXBuildFile; fileRef = BC1847E90FE6EC8400094BFB /* SPEditSheetTextView.m */; }; @@ -758,7 +758,6 @@ 17A7773711C52E61001E27B4 /* English */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = English; path = Interfaces/English.lproj/IndexesView.xib; sourceTree = "<group>"; }; 17AF787911FC41C00073D043 /* SPExportFilenameUtilities.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SPExportFilenameUtilities.h; sourceTree = "<group>"; }; 17AF787A11FC41C00073D043 /* SPExportFilenameUtilities.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SPExportFilenameUtilities.m; sourceTree = "<group>"; }; - 17B7B58F1016028F00F057DE /* libcrypto.dylib */ = {isa = PBXFileReference; lastKnownFileType = "compiled.mach-o.dylib"; name = libcrypto.dylib; path = /usr/lib/libcrypto.dylib; sourceTree = "<absolute>"; }; 17B7B591101602AE00F057DE /* libssl.dylib */ = {isa = PBXFileReference; lastKnownFileType = "compiled.mach-o.dylib"; name = libssl.dylib; path = /usr/lib/libssl.dylib; sourceTree = "<absolute>"; }; 17BA2A3015275D8600389803 /* SPExportInterfaceController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SPExportInterfaceController.h; sourceTree = "<group>"; }; 17BA2A3115275D8600389803 /* SPExportInterfaceController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SPExportInterfaceController.m; sourceTree = "<group>"; }; @@ -895,7 +894,6 @@ 501B1D161728A3DA0017C92E /* SPCharsetCollationHelper.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SPCharsetCollationHelper.h; sourceTree = "<group>"; }; 501B1D171728A3DA0017C92E /* SPCharsetCollationHelper.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SPCharsetCollationHelper.m; sourceTree = "<group>"; }; 502D21F51BA50710000D4CE7 /* SPDataAdditionsTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SPDataAdditionsTests.m; sourceTree = "<group>"; }; - 502D21F91BA509AF000D4CE7 /* libcrypto.dylib */ = {isa = PBXFileReference; lastKnownFileType = "compiled.mach-o.dylib"; name = libcrypto.dylib; path = usr/lib/libcrypto.dylib; sourceTree = SDKROOT; }; 5037F79A1B00148000733564 /* SPNamedNode.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = SPNamedNode.h; sourceTree = "<group>"; }; 503B02C81AE82C5E0060CAB1 /* SPTableFilterParser.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SPTableFilterParser.h; sourceTree = "<group>"; }; 503B02C91AE82C5E0060CAB1 /* SPTableFilterParser.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SPTableFilterParser.m; sourceTree = "<group>"; }; @@ -1277,7 +1275,7 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( - 502D21FA1BA509AF000D4CE7 /* libcrypto.dylib in Frameworks */, + 502D22151BA62FA5000D4CE7 /* Security.framework in Frameworks */, 1717F9DB1558114D0065C036 /* OCMock.framework in Frameworks */, 1717FA43155831600065C036 /* libicucore.dylib in Frameworks */, 50EA92671AB23EE1008D3C4F /* SPMySQL.framework in Frameworks */, @@ -1288,8 +1286,8 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( + 502D22161BA62FF5000D4CE7 /* Security.framework in Frameworks */, BC0E14A1120AAC2E00E52E25 /* libbz2.dylib in Frameworks */, - BC0E1493120AABE900E52E25 /* libcrypto.dylib in Frameworks */, 584D87C415141A5D00F24774 /* libz.dylib in Frameworks */, 58475686120A065B0057631F /* CoreFoundation.framework in Frameworks */, 584756B5120A06740057631F /* CoreServices.framework in Frameworks */, @@ -1364,7 +1362,6 @@ 296DC8BE0F9091DF002A3258 /* libicucore.dylib */, 179ECEC611F265EE009C6A40 /* libbz2.dylib */, 17B7B591101602AE00F057DE /* libssl.dylib */, - 17B7B58F1016028F00F057DE /* libcrypto.dylib */, 584D87BE15141A4A00F24774 /* libz.dylib */, ); name = "Linked Frameworks"; @@ -2356,7 +2353,6 @@ 2A37F4C3FDCFA73011CA2CEA /* Frameworks */ = { isa = PBXGroup; children = ( - 502D21F91BA509AF000D4CE7 /* libcrypto.dylib */, 1058C7A6FEA54F5311CA2CBB /* Linked Frameworks */, ); name = Frameworks; @@ -3937,10 +3933,7 @@ INSTALL_PATH = "$(HOME)/Applications"; LIBRARY_SEARCH_PATHS = "$(SRCROOT)/Frameworks/zlib"; MACOSX_DEPLOYMENT_TARGET = 10.6; - OTHER_LDFLAGS = ( - "$(SRCROOT)/Frameworks/zlib/libz.a", - "-lcrypto", - ); + OTHER_LDFLAGS = "$(SRCROOT)/Frameworks/zlib/libz.a"; PRODUCT_NAME = "Sequel Pro"; SEPARATE_STRIP = NO; }; @@ -4140,10 +4133,7 @@ INSTALL_PATH = "$(HOME)/Applications"; LIBRARY_SEARCH_PATHS = "$(SRCROOT)/Frameworks/zlib"; MACOSX_DEPLOYMENT_TARGET = 10.6; - OTHER_LDFLAGS = ( - "$(SRCROOT)/Frameworks/zlib/libz.a", - "-lcrypto", - ); + OTHER_LDFLAGS = "$(SRCROOT)/Frameworks/zlib/libz.a"; PRODUCT_NAME = "Sequel Pro"; SEPARATE_STRIP = NO; }; @@ -4167,10 +4157,7 @@ INSTALL_PATH = "$(HOME)/Applications"; LIBRARY_SEARCH_PATHS = "$(SRCROOT)/Frameworks/zlib"; MACOSX_DEPLOYMENT_TARGET = 10.6; - OTHER_LDFLAGS = ( - "$(SRCROOT)/Frameworks/zlib/libz.a", - "-lcrypto", - ); + OTHER_LDFLAGS = "$(SRCROOT)/Frameworks/zlib/libz.a"; PRODUCT_NAME = "Sequel Pro"; SEPARATE_STRIP = NO; }; |