diff options
Diffstat (limited to 'Source/SPDataAdditions.m')
-rw-r--r-- | Source/SPDataAdditions.m | 263 |
1 files changed, 224 insertions, 39 deletions
diff --git a/Source/SPDataAdditions.m b/Source/SPDataAdditions.m index e9eaa927..65605577 100644 --- a/Source/SPDataAdditions.m +++ b/Source/SPDataAdditions.m @@ -35,72 +35,193 @@ #import "SPDataAdditions.h" #include <zlib.h> -#include <openssl/aes.h> -#include <openssl/sha.h> +#include <CommonCrypto/CommonCrypto.h> #include <stdlib.h> +#import "SPFunctions.h" + +/** Limit an NSUInteger to unsigned 32 bit max. + * @return Whatever is smaller: UINT32_MAX or i + * + * This is pretty much a NOOP on 32 bit platforms. + */ +uint32_t LimitUInt32(NSUInteger i); + +#pragma mark - @implementation NSData (SPDataAdditions) +- (NSData *)sha1Hash +{ + unsigned char digest[CC_SHA1_DIGEST_LENGTH]; + + //let's do it as a one step operation, if it fits + if([self length] <= UINT32_MAX) { + CC_SHA1([self bytes], (uint32_t)[self length], digest); + } + // or multi-step if length > 32 bit + else { + CC_SHA1_CTX ctx; + CC_SHA1_Init(&ctx); + + NSUInteger offset = 0; + uint32_t len; + while((len = LimitUInt32([self length]-offset)) > 0) { + CC_SHA1_Update(&ctx, ([self bytes]+offset), len); + offset += len; + } + + CC_SHA1_Final(digest, &ctx); + } + + return [NSData dataWithBytes:digest length:CC_SHA1_DIGEST_LENGTH]; +} + - (NSData *)dataEncryptedWithPassword:(NSString *)password { // Create a random 128-bit initialization vector - srand((unsigned int)time(NULL)); - NSInteger ivIndex; - unsigned char iv[16]; - for (ivIndex = 0; ivIndex < 16; ivIndex++) - iv[ivIndex] = rand() & 0xff; + // IV is block "-1" of plaintext data, therefore it is blockSize long + unsigned char iv[kCCBlockSizeAES128]; + if(SPBetterRandomBytes(iv,sizeof(iv)) != 0) + @throw [NSException exceptionWithName:NSInternalInconsistencyException + reason:@"Getting random data bytes failed!" + userInfo:@{@"errno":@(errno)}]; + + 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 = <any 8-bit sequence of data> + * ENCRYPTED = IV AES + * IV = 16OCTET ; 16 random bytes + * AES = <AES_128_CBC(PADDED)> + * 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 - unsigned char passwordDigest[20]; - SHA1((const unsigned char *)[password UTF8String], strlen([password UTF8String]), passwordDigest); - AES_KEY aesKey; - AES_set_encrypt_key(passwordDigest, 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]; } - (NSData *)dataDecryptedWithPassword:(NSString *)password { // Create the key from the password hash - unsigned char passwordDigest[20]; - SHA1((const unsigned char *)[password UTF8String], strlen([password UTF8String]), passwordDigest); + 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, 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)) || @@ -112,8 +233,14 @@ } // 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]; } @@ -311,4 +438,62 @@ return string; } +- (void)enumerateLinesBreakingAt:(SPLineTerminator)lbChars withBlock:(void (^)(NSRange line,BOOL *stop))block +{ + if(lbChars == SPLineTerminatorAny) lbChars = SPLineTerminatorCR|SPLineTerminatorLF|SPLineTerminatorCRLF; + + const uint8_t *bytes = [self bytes]; + NSUInteger length = [self length]; + + NSUInteger curStart = 0; + SPLineTerminator terminatorFound = 0; + NSUInteger i; + for (i = 0; i < length; i++) { + uint8_t chr = bytes[i]; + // if looking for cr and/or crlf we look for cr otherwise for lf + if(((lbChars & SPLineTerminatorCRLF) || (lbChars & SPLineTerminatorCR)) && chr == '\r') { + //if we are looking for CRLF check for the following LF + if((lbChars & SPLineTerminatorCRLF) && ((i+1) < length) && bytes[i+1] == '\n') { + terminatorFound = SPLineTerminatorCRLF; + } + //if we were looking for CR we've found one + else if((lbChars & SPLineTerminatorCR)) { + terminatorFound = SPLineTerminatorCR; + } + } + else if((lbChars & SPLineTerminatorLF) && chr == '\n') { + terminatorFound = SPLineTerminatorLF; + } + // no linebreak yet ? + if(!terminatorFound) continue; + + // found one. call the block. + BOOL stop = NO; + NSRange lineRange = NSMakeRange(curStart, (i-curStart)); + block(lineRange,&stop); + if(stop) return; + + // reset vars for next line + if(terminatorFound == SPLineTerminatorCRLF) i++; //skip the \n in CRLF + curStart = (i+1); + terminatorFound = 0; + } + // there could we one unterminated line left in buffer + if(curStart < i) { + NSRange lineRange = NSMakeRange(curStart, (i-curStart)); + BOOL iDontCare = NO; + block(lineRange,&iDontCare); + } +} + @end + +#pragma mark - + +uint32_t LimitUInt32(NSUInteger i) { +#if NSUIntegerMax > UINT32_MAX + return (i > UINT32_MAX)? UINT32_MAX : (uint32_t)i; +#else + return i; +#endif +} |