diff options
Diffstat (limited to 'UnitTests/SPDataAdditionsTests.m')
-rw-r--r-- | UnitTests/SPDataAdditionsTests.m | 387 |
1 files changed, 387 insertions, 0 deletions
diff --git a/UnitTests/SPDataAdditionsTests.m b/UnitTests/SPDataAdditionsTests.m new file mode 100644 index 00000000..4769e499 --- /dev/null +++ b/UnitTests/SPDataAdditionsTests.m @@ -0,0 +1,387 @@ +// +// SPDataAdditionsTests.m +// sequel-pro +// +// Created by Max Lohrmann on 13.09.15. +// Copyright (c) 2015 Max Lohrmann. All rights reserved. +// +// Permission is hereby granted, free of charge, to any person +// obtaining a copy of this software and associated documentation +// files (the "Software"), to deal in the Software without +// restriction, including without limitation the rights to use, +// copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following +// conditions: +// +// The above copyright notice and this permission notice shall be +// included in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES +// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT +// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR +// OTHER DEALINGS IN THE SOFTWARE. +// +// More info at <https://github.com/sequelpro/sequelpro> + +#import <Cocoa/Cocoa.h> +#import <XCTest/XCTest.h> +#import "SPDataAdditions.h" +#import <errno.h> + +@interface SPDataAdditionsTests : XCTestCase + +- (void)testSha1Hash; +- (void)testDataEncryptedWithPassword; +- (void)testDataEncryptedWithKeyIV; +- (void)testDataDecryptedWithPassword; +- (void)testDataDecryptedWithKey; +- (void)testEnumerateLinesBreakingAt_withBlock; + +@end + +@implementation SPDataAdditionsTests + +- (void)testSha1Hash +{ + //simple straight forward case + { + NSString *input = @"Hello World!"; + unsigned char bytes[] = {0x2e,0xf7,0xbd,0xe6,0x08,0xce,0x54,0x04,0xe9,0x7d,0x5f,0x04,0x2f,0x95,0xf8,0x9f,0x1c,0x23,0x28,0x71}; + + XCTAssertTrue(memcmp([[[input dataUsingEncoding:NSUTF8StringEncoding] sha1Hash] bytes], bytes, 20) == 0, @"SHA1 simple hash from ASCII text"); + } + // 16MB of all 8bit values + { + int bufSz = 16*1024*1024; + unsigned char *buf = malloc(bufSz); + for (int i = 0; i < bufSz; i++) { + buf[i] = (i % 0xff); + } + NSData *input = [NSData dataWithBytesNoCopy:buf length:bufSz]; + NSString *result = @"25E05EB8E9E2B06036DF4026630FE01A19BF0F16"; + + XCTAssertEqualObjects([[input sha1Hash] dataToHexString], result, @"SHA1 hash from full ASCII range"); + } + // empty hash + { + NSData *input = [NSData data]; + NSString *result = @"DA39A3EE5E6B4B0D3255BFEF95601890AFD80709"; + + XCTAssertEqualObjects([[input sha1Hash] dataToHexString], result, @"SHA1 hash from empty data"); + } + // test with > 4GB data (other code path) + // HFS+ does not support sparse files, so enable this one only if you have enough disk space. + {/* + // not everyone has 4GB RAM to spare and even then we probably won't be able to get + // them en-block, so we'll just use a file and mmap() to simulate that. + NSString *fileNameTpl = [NSTemporaryDirectory() stringByAppendingPathComponent:@"sha1test.XXXXXX"]; + STAssertNotNil(fileNameTpl, @"No temporary directory available!?"); + const char *cFileNameTpl = [fileNameTpl fileSystemRepresentation]; + char *cFileName = malloc(strlen(cFileNameTpl)+1); + strcpy(cFileName, cFileNameTpl); + if(mkstemp(cFileName) == -1) + STFail(@"could not create temporary filename. errno=%d",errno); + + FILE *fp = fopen(cFileName, "w+"); + fputc(1, fp); + fseek(fp, UINT32_MAX, SEEK_CUR); + fputc(2, fp); + fflush(fp); + fclose(fp); + + NSString *fileName = [[NSFileManager defaultManager] stringWithFileSystemRepresentation:cFileName length:strlen(cFileName)]; + + NSData *input = [NSData dataWithContentsOfFile:fileName]; + NSString *result = @"A31A151AFC12B0D66A4DBE917CB55CEAA0AD639E"; + + STAssertEqualObjects([[input sha1Hash] dataToHexString], result, @"SHA1 hash > 4gb data"); + + unlink(cFileName); + free(cFileName); + */} + //utf8 string input + { + NSData *input = [@"føöbärbãz" dataUsingEncoding:NSUTF8StringEncoding]; + NSString *result = @"8A8B6142281950CBB9B01C9DF0DADB0BDAE2D0E1"; + + XCTAssertEqualObjects([[input sha1Hash] dataToHexString], result, @"SHA1 hash of UTF-8 string"); + } + +} + +- (void)testDataEncryptedWithPassword +{ + //this method generates random data, so we can only test it by doing a full round-trip + NSData *raw = [@"foo bar baz!" dataUsingEncoding:NSASCIIStringEncoding]; + NSString *password = @"123456"; + + NSData *encrypted = [raw dataEncryptedWithPassword:password]; + //check that our encrypted data is not the plaintext data + NSData *encCore = [encrypted subdataWithRange:NSMakeRange(16, [raw length])]; + XCTAssertFalse([encCore isEqualToData:raw], @"encrypted equal to plain text!"); + + //decrypt again and verify + NSData *decrypted = [encrypted dataDecryptedWithPassword:password]; + XCTAssertEqualObjects(decrypted, raw, @"decrypted data not equal to plaintext data!"); +} + +- (void)testDataEncryptedWithKeyIV +{ + NSData *iv = [@"0123456789ABCDEF" dataUsingEncoding:NSASCIIStringEncoding]; + NSData *raw = [@" " dataUsingEncoding:NSASCIIStringEncoding]; + // ^^^^^^^^^^^^^^^^ spaces because their pattern is easily recognizable in hexdumps + + 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]; + + //argument tests: + //key too short + { + @try { + [raw dataEncryptedWithKey:[@"password" dataUsingEncoding:NSASCIIStringEncoding] IV:iv]; + XCTFail(@"Password should not be a valid key!"); + } + @catch (NSException *exception) { + //expected + } + } + //iv too short + { + @try { + [raw dataEncryptedWithKey:key IV:[NSData data]]; + XCTFail(@"Empty IV should throw exception!"); + } + @catch (NSException *exception) { + // expected + } + } + //simple test: encrypting empty + { + NSData *enc = [[NSData data] dataEncryptedWithKey:key IV:iv]; + unsigned char expect[] = { + 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, 0x00, + 0x02, 0x5c, 0x2c, 0xf9, 0xa5, 0x22, 0x79, 0xa4, 0x14, 0x61, 0x90, + 0x1d, 0x9f, 0x0c, 0x7a + }; // reference data generated with OpenSSL + NSData *expData = [NSData dataWithBytesNoCopy:expect length:sizeof(expect) freeWhenDone:NO]; + XCTAssertEqualObjects(enc, expData, @"Encryption of empty data"); + } + //simple encryption test + { + NSData *enc = [raw dataEncryptedWithKey:key IV:iv]; + unsigned char expect[] = { + 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 + }; // reference data generated with OpenSSL + NSData *expData = [NSData dataWithBytesNoCopy:expect length:sizeof(expect) freeWhenDone:NO]; + XCTAssertEqualObjects(enc, expData, @"Simple encryption test"); + } +} + +- (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:@""]; + + XCTAssertEqualObjects(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]]; + XCTFail(@"Invalid key length!"); + } + @catch (NSException *exception) { + //expected + } + } + // data too short for encryption + { + @try { + [[@"Hello World!" dataUsingEncoding:NSASCIIStringEncoding] dataDecryptedWithKey:key]; + XCTFail(@"Invalid data length!"); + } + @catch (NSException *exception) { + //expected + } + } + // wrong data with valid length + { + NSData *inp = [@"12345678901234567890123456789012" dataUsingEncoding:NSASCIIStringEncoding]; + XCTAssertNil([inp dataDecryptedWithKey:key], @"Trying to decrypt invalid data."); + } + // wrong data with invalid length + { + NSData *inp = [@"12345678901234567890123456789012345678901234567" dataUsingEncoding:NSASCIIStringEncoding]; + XCTAssertNil([inp dataDecryptedWithKey:key], @"Trying to decrypt data with invalid length."); + } + // simple decryption test + { + NSData *decrypted = [encData dataDecryptedWithKey:key]; + XCTAssertEqualObjects(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]; + XCTFail(@"Malicious message with invalid data length"); + } + @catch (NSException *exception) { + //expected + } + } +} + +- (void)testEnumerateLinesBreakingAt_withBlock +{ + //simple empty data + { + __block NSUInteger invocations = 0; + NSData *data = [NSData data]; + [data enumerateLinesBreakingAt:SPLineTerminatorAny withBlock:^(NSRange line, BOOL *stop) { + invocations++; + }]; + XCTAssertTrue(invocations==0, @"Empty data never invokes block"); + } + //simple unix file + { + const char inp[] = "Two\nLines\n"; + __block NSUInteger invocations = 0; + NSData *data = [NSData dataWithBytes:inp length:strlen(inp)]; + [data enumerateLinesBreakingAt:SPLineTerminatorAny withBlock:^(NSRange line, BOOL *stop) { + switch (invocations) { + case 0: + XCTAssertTrue(NSEqualRanges(line, NSMakeRange(0, 3)), @"range of first line"); + break; + case 1: + XCTAssertTrue(NSEqualRanges(line, NSMakeRange(4, 5)), @"range of second line"); + break; + } + invocations++; + }]; + XCTAssertTrue(invocations==2, @"File with two lines, terminated with empty line"); + } + //simple windows file without ending empty line + { + const char inp[] = "A\r\nWindows\r\nfile"; + __block NSUInteger invocations = 0; + NSData *data = [NSData dataWithBytes:inp length:strlen(inp)]; + [data enumerateLinesBreakingAt:SPLineTerminatorAny withBlock:^(NSRange line, BOOL *stop) { + switch (invocations) { + case 0: + XCTAssertTrue(NSEqualRanges(line, NSMakeRange(0, 1)), @"range of first line"); + break; + case 1: + XCTAssertTrue(NSEqualRanges(line, NSMakeRange(3, 7)), @"range of second line"); + break; + case 2: + XCTAssertTrue(NSEqualRanges(line, NSMakeRange(12, 4)), @"range of third line"); + break; + } + invocations++; + }]; + XCTAssertTrue(invocations==3, @"File with three lines, CRLF, terminated with empty line"); + } + //empty lines with all 3 endings + { + const char inp[] = "\n\r\n\r"; + __block NSUInteger invocations = 0; + NSData *data = [NSData dataWithBytes:inp length:strlen(inp)]; + [data enumerateLinesBreakingAt:SPLineTerminatorAny withBlock:^(NSRange line, BOOL *stop) { + switch (invocations) { + case 0: + XCTAssertTrue(NSEqualRanges(line, NSMakeRange(0, 0)), @"range of first line"); + break; + case 1: + XCTAssertTrue(NSEqualRanges(line, NSMakeRange(1, 0)), @"range of second line"); + break; + case 2: + XCTAssertTrue(NSEqualRanges(line, NSMakeRange(3, 0)), @"range of third line"); + break; + } + invocations++; + }]; + XCTAssertTrue(invocations==3, @"LF, CRLF and CR mixed"); + } + //looking for specific line breaks only + { + const char inp[] = "foo\nbar\r\nbaz\r"; + __block NSUInteger invocations = 0; + NSData *data = [NSData dataWithBytes:inp length:strlen(inp)]; + [data enumerateLinesBreakingAt:SPLineTerminatorCRLF withBlock:^(NSRange line, BOOL *stop) { + switch (invocations) { + case 0: + XCTAssertTrue(NSEqualRanges(line, NSMakeRange(0, 7)), @"range of first line"); + break; + case 1: + XCTAssertTrue(NSEqualRanges(line, NSMakeRange(9, 4)), @"range of second line"); + break; + } + invocations++; + }]; + XCTAssertTrue(invocations==2, @"other line breaks when only CRLF is expected"); + } + //stopping early + { + const char inp[] = "Two\nLines\n"; + __block NSUInteger invocations = 0; + NSData *data = [NSData dataWithBytes:inp length:strlen(inp)]; + [data enumerateLinesBreakingAt:SPLineTerminatorAny withBlock:^(NSRange line, BOOL *stop) { + invocations++; + *stop = YES; + }]; + XCTAssertTrue(invocations==1, @"File with two lines, stopped after first"); + } +} + +@end |