diff --git a/SignalServiceKit/src/Util/Cryptography.m b/SignalServiceKit/src/Util/Cryptography.m index 64f197067..ed99e31d1 100755 --- a/SignalServiceKit/src/Util/Cryptography.m +++ b/SignalServiceKit/src/Util/Cryptography.m @@ -410,22 +410,23 @@ const NSUInteger kAES128_KeyByteLength = 16; // Comment was: // tagLength is actual an input size_t tagLength = kAESGCM128_TagLength; - - CCCryptorStatus status = CCCryptorGCM(kCCEncrypt, // CCOperation op, /* kCCEncrypt, kCCDecrypt */ - kCCAlgorithmAES128, // CCAlgorithm alg, - key.keyData.bytes, // const void *key, /* raw key material */ - key.keyData.length, // size_t keyLength, - initializationVector.bytes, // const void *iv, - initializationVector.length, // size_t ivLen, - NULL, // const void *aData, - 0, // size_t aDataLen, - plainTextData.bytes, // const void *dataIn, - plainTextData.length, // size_t dataInLength, - cipherTextBytes, // void *dataOut, - authTagBytes, // const void *tag, - &tagLength //size_t *tagLength) - ); - + + CCCryptorStatus status + = CCCryptorGCM(kCCEncrypt, // CCOperation op, /* kCCEncrypt, kCCDecrypt */ + kCCAlgorithmAES128, // CCAlgorithm alg, + key.keyData.bytes, // const void *key, /* raw key material */ + key.keyData.length, // size_t keyLength, + initializationVector.bytes, // const void *iv, + initializationVector.length, // size_t ivLen, + NULL, // const void *aData, + 0, // size_t aDataLen, + plainTextData.bytes, // const void *dataIn, + plainTextData.length, // size_t dataInLength, + cipherTextBytes, // void *dataOut, + authTagBytes, // const void *tag, + &tagLength // size_t *tagLength) + ); + if (status != kCCSuccess) { OWSFail(@"CCCryptorGCM encrypt failed with status: %d", status); free(cipherTextBytes); @@ -452,15 +453,31 @@ const NSUInteger kAES128_KeyByteLength = 16; // encryptedData layout: initializationVector || cipherText || authTag NSData *initializationVector = [encryptedData subdataWithRange:NSMakeRange(0, kAESGCM128_IVLength)]; NSData *cipherText = [encryptedData subdataWithRange:NSMakeRange(kAESGCM128_IVLength, cipherTextLength)]; - NSData *authTag = [encryptedData subdataWithRange:NSMakeRange(kAESGCM128_IVLength + cipherTextLength, - kAESGCM128_TagLength)]; + NSData *authTag = + [encryptedData subdataWithRange:NSMakeRange(kAESGCM128_IVLength + cipherTextLength, kAESGCM128_TagLength)]; - void * plainTextBytes = malloc(cipherTextLength); + return + [self decryptAESGCMWithInitializationVector:initializationVector cipherText:cipherText authTag:authTag key:key]; +} + ++ (nullable NSData *)decryptAESGCMWithInitializationVector:(NSData *)initializationVector + cipherText:(NSData *)cipherText + authTag:(NSData *)authTagFromEncrypt + key:(OWSAES128Key *)key +{ + void *plainTextBytes = malloc(cipherText.length); if (plainTextBytes == NULL) { OWSFail(@"Failed to malloc plainTextBytes"); return nil; } - + + void *decryptAuthTagBytes = malloc(kAESGCM128_TagLength); + if (decryptAuthTagBytes == NULL) { + OWSFail(@"Failed to malloc decryptAuthTagBytes"); + free(plainTextBytes); + return nil; + } + // NOTE: Since `tagLength` is an input parameter, it seems weird that the signature for tagLength is a `size_t*` rather than just a `size_t`. // // I found a vague reference in the Safari repository implying that this may be a bug: @@ -470,28 +487,36 @@ const NSUInteger kAES128_KeyByteLength = 16; // tagLength is actual an input size_t tagLength = kAESGCM128_TagLength; - CCCryptorStatus status = CCCryptorGCM(kCCDecrypt, // CCOperation op, /* kCCEncrypt, kCCDecrypt */ - kCCAlgorithmAES128, // CCAlgorithm alg, - key.keyData.bytes, // const void *key, /* raw key material */ - key.keyData.length, // size_t keyLength, - initializationVector.bytes, // const void *iv, - initializationVector.length, // size_t ivLen, - NULL, // const void *aData, - 0, // size_t aDataLen, - cipherText.bytes, // const void *dataIn, - cipherText.length, // size_t dataInLength, - plainTextBytes, // void *dataOut, - authTag.bytes, // const void *tag, - &tagLength //size_t *tagLength) - ); - + CCCryptorStatus status + = CCCryptorGCM(kCCDecrypt, // CCOperation op, /* kCCEncrypt, kCCDecrypt */ + kCCAlgorithmAES128, // CCAlgorithm alg, + key.keyData.bytes, // const void *key, /* raw key material */ + key.keyData.length, // size_t keyLength, + initializationVector.bytes, // const void *iv, + initializationVector.length, // size_t ivLen, + NULL, // const void *aData, + 0, // size_t aDataLen, + cipherText.bytes, // const void *dataIn, + cipherText.length, // size_t dataInLength, + plainTextBytes, // void *dataOut, + decryptAuthTagBytes, // const void *tag, + &tagLength // size_t *tagLength + ); + + NSData *decryptAuthTag = [NSData dataWithBytesNoCopy:decryptAuthTagBytes length:tagLength freeWhenDone:YES]; + if (![decryptAuthTag ows_constantTimeIsEqualToData:authTagFromEncrypt]) { + OWSFail(@"Auth tags don't match given tag: %@ computed tag: %@", authTagFromEncrypt, decryptAuthTag); + free(plainTextBytes); + return nil; + } + if (status != kCCSuccess) { OWSFail(@"CCCryptorGCM decrypt failed with status: %d", status); free(plainTextBytes); return nil; } - - return [NSData dataWithBytesNoCopy:plainTextBytes length:cipherTextLength freeWhenDone:YES]; + + return [NSData dataWithBytesNoCopy:plainTextBytes length:cipherText.length freeWhenDone:YES]; } #pragma mark - Logging diff --git a/SignalServiceKit/tests/Util/CryptographyTests.m b/SignalServiceKit/tests/Util/CryptographyTests.m index 612c6d27d..79e996193 100644 --- a/SignalServiceKit/tests/Util/CryptographyTests.m +++ b/SignalServiceKit/tests/Util/CryptographyTests.m @@ -8,6 +8,15 @@ NS_ASSUME_NONNULL_BEGIN +@interface Cryptography (TestingPrivateMethods) + ++ (nullable NSData *)decryptAESGCMWithInitializationVector:(NSData *)initializationVector + cipherText:(NSData *)cipherText + authTag:(NSData *)authTagFromEncrypt + key:(OWSAES128Key *)key; + +@end + @interface CryptographyTests : XCTestCase @end @@ -127,19 +136,71 @@ NS_ASSUME_NONNULL_BEGIN XCTAssertEqual(39, plainTextData.length); OWSAES128Key *key = [OWSAES128Key new]; - NSData *encryptedData = [Cryptography encryptAESGCMWithData:plainTextData key:key]; - + NSData *_Nullable encryptedData = [Cryptography encryptAESGCMWithData:plainTextData key:key]; + const NSUInteger ivLength = 12; const NSUInteger tagLength = 16; XCTAssertEqual(ivLength + plainTextData.length + tagLength, encryptedData.length); - - NSData *decryptedData = [Cryptography decryptAESGCMWithData:encryptedData key:key]; + + NSData *_Nullable decryptedData = [Cryptography decryptAESGCMWithData:encryptedData key:key]; + XCTAssert(decryptedData != nil); XCTAssertEqual(39, decryptedData.length); XCTAssertEqualObjects(plainTextData, decryptedData); XCTAssertEqualObjects(@"Super🔥secret🔥test🔥data🏁🏁", [[NSString alloc] initWithData:decryptedData encoding:NSUTF8StringEncoding]); } +- (void)testGCMWithBadTag +{ + NSData *plainTextData = [@"Super🔥secret🔥test🔥data🏁🏁" dataUsingEncoding:NSUTF8StringEncoding]; + // Sanity Check + XCTAssertEqual(39, plainTextData.length); + + OWSAES128Key *key = [OWSAES128Key new]; + NSData *_Nullable encryptedData = [Cryptography encryptAESGCMWithData:plainTextData key:key]; + + const NSUInteger ivLength = 12; + const NSUInteger tagLength = 16; + + XCTAssertEqual(ivLength + plainTextData.length + tagLength, encryptedData.length); + + // Logic to slice up encryptedData copied from `[Cryptography decryptAESGCMWithData:key:]` + + // encryptedData layout: initializationVector || cipherText || authTag + NSUInteger cipherTextLength = encryptedData.length - ivLength - tagLength; + + NSData *initializationVector = [encryptedData subdataWithRange:NSMakeRange(0, ivLength)]; + NSData *cipherText = [encryptedData subdataWithRange:NSMakeRange(ivLength, cipherTextLength)]; + NSData *authTag = [encryptedData subdataWithRange:NSMakeRange(ivLength + cipherTextLength, tagLength)]; + + NSData *_Nullable decryptedData = [Cryptography decryptAESGCMWithInitializationVector:initializationVector + cipherText:cipherText + authTag:authTag + key:key]; + + // Before we corrupt the tag, make sure we can decrypt the text as a sanity check to ensure we divided up the + // encryptedData correctly. + XCTAssert(decryptedData != nil); + XCTAssertEqualObjects( + @"Super🔥secret🔥test🔥data🏁🏁", [[NSString alloc] initWithData:decryptedData encoding:NSUTF8StringEncoding]); + + // Now that we know it decrypts, try again with a bogus authTag + NSMutableData *bogusAuthTag = [authTag mutableCopy]; + + // Corrupt one byte in the bogusAuthTag + uint8_t flippedByte; + [bogusAuthTag getBytes:&flippedByte length:1]; + flippedByte = flippedByte ^ 0xff; + [bogusAuthTag replaceBytesInRange:NSMakeRange(0, 1) withBytes:&flippedByte]; + + decryptedData = [Cryptography decryptAESGCMWithInitializationVector:initializationVector + cipherText:cipherText + authTag:bogusAuthTag + key:key]; + + XCTAssertNil(decryptedData, @"Should have failed to decrypt"); +} + @end NS_ASSUME_NONNULL_END