session-ios/SignalServiceKit/src/Util/Cryptography.m

841 lines
29 KiB
Mathematica
Raw Normal View History

2015-12-07 03:31:43 +01:00
//
// Copyright (c) 2018 Open Whisper Systems. All rights reserved.
2015-12-07 03:31:43 +01:00
//
#import "Cryptography.h"
#import "OWSError.h"
2017-08-21 17:21:23 +02:00
#import <CommonCrypto/CommonCryptor.h>
#import <CommonCrypto/CommonHMAC.h>
2018-09-21 21:41:10 +02:00
#import <SignalCoreKit/NSData+OWS.h>
#import <SignalCoreKit/Randomness.h>
2017-08-21 17:21:23 +02:00
#import <openssl/evp.h>
2015-12-07 03:31:43 +01:00
#define HMAC256_KEY_LENGTH 32
#define HMAC256_OUTPUT_LENGTH 32
#define AES_CBC_IV_LENGTH 16
#define AES_KEY_SIZE 32
NS_ASSUME_NONNULL_BEGIN
2017-08-21 17:21:23 +02:00
// Returned by many OpenSSL functions - indicating success
const int kOpenSSLSuccess = 1;
// length of initialization nonce for AES256-GCM
2017-08-21 17:21:23 +02:00
static const NSUInteger kAESGCM256_IVLength = 12;
2017-08-21 17:21:23 +02:00
// length of authentication tag for AES256-GCM
static const NSUInteger kAESGCM256_TagLength = 16;
// length of key used for websocket envelope authentication
static const NSUInteger kHMAC256_EnvelopeKeyLength = 20;
2017-08-21 17:21:23 +02:00
const NSUInteger kAES256_KeyByteLength = 32;
2017-08-21 17:21:23 +02:00
@implementation OWSAES256Key
+ (nullable instancetype)keyWithData:(NSData *)data
{
2017-08-21 17:21:23 +02:00
if (data.length != kAES256_KeyByteLength) {
OWSFailDebug(@"Invalid key length: %lu", (unsigned long)data.length);
return nil;
}
return [[self alloc] initWithData:data];
}
+ (instancetype)generateRandomKey
{
return [self new];
}
- (instancetype)init
{
2017-08-21 17:21:23 +02:00
return [self initWithData:[Cryptography generateRandomBytes:kAES256_KeyByteLength]];
}
- (instancetype)initWithData:(NSData *)data
{
self = [super init];
if (!self) {
return self;
}
_keyData = data;
return self;
}
#pragma mark - SecureCoding
+ (BOOL)supportsSecureCoding
{
return YES;
}
- (nullable instancetype)initWithCoder:(NSCoder *)aDecoder
{
self = [super init];
if (!self) {
return self;
}
NSData *keyData = [aDecoder decodeObjectOfClass:[NSData class] forKey:@"keyData"];
2017-08-21 17:21:23 +02:00
if (keyData.length != kAES256_KeyByteLength) {
2018-08-27 16:29:51 +02:00
OWSFailDebug(@"Invalid key length: %lu", (unsigned long)keyData.length);
return nil;
}
_keyData = keyData;
return self;
}
- (void)encodeWithCoder:(NSCoder *)aCoder
{
[aCoder encodeObject:_keyData forKey:@"keyData"];
}
@end
2018-07-25 20:02:25 +02:00
#pragma mark -
@implementation AES25GCMEncryptionResult
- (nullable instancetype)initWithCipherText:(NSData *)cipherText
initializationVector:(NSData *)initializationVector
authTag:(NSData *)authTag
{
self = [super init];
if (!self) {
return self;
}
_ciphertext = [cipherText copy];
_initializationVector = [initializationVector copy];
_authTag = [authTag copy];
2018-07-23 18:35:35 +02:00
if (_ciphertext == nil || _initializationVector.length != kAESGCM256_IVLength
|| _authTag.length != kAESGCM256_TagLength) {
return nil;
}
return self;
}
@end
2018-07-25 20:02:25 +02:00
#pragma mark -
2015-12-07 03:31:43 +01:00
@implementation Cryptography
2018-07-25 20:02:25 +02:00
#pragma mark - random bytes methods
2017-06-17 19:34:26 +02:00
2017-12-19 03:17:11 +01:00
+ (NSData *)generateRandomBytes:(NSUInteger)numberBytes
{
2018-05-21 23:02:59 +02:00
return [Randomness generateRandomBytes:(int)numberBytes];
}
+ (uint32_t)randomUInt32
{
size_t size = sizeof(uint32_t);
NSData *data = [self generateRandomBytes:size];
uint32_t result = 0;
[data getBytes:&result range:NSMakeRange(0, size)];
return result;
}
+ (uint64_t)randomUInt64
{
size_t size = sizeof(uint64_t);
NSData *data = [self generateRandomBytes:size];
uint64_t result = 0;
[data getBytes:&result range:NSMakeRange(0, size)];
return result;
2015-12-07 03:31:43 +01:00
}
2018-08-03 01:21:01 +02:00
+ (unsigned)randomUnsigned
{
size_t size = sizeof(unsigned);
NSData *data = [self generateRandomBytes:size];
unsigned result = 0;
[data getBytes:&result range:NSMakeRange(0, size)];
return result;
}
2018-07-25 20:02:25 +02:00
#pragma mark - SHA1
2015-12-07 03:31:43 +01:00
2018-07-25 20:02:25 +02:00
// Used by TSContactManager to send hashed/truncated contact list to server.
+ (nullable NSString *)truncatedSHA1Base64EncodedWithoutPadding:(NSString *)string
{
NSData *_Nullable stringData = [string dataUsingEncoding:NSUTF8StringEncoding];
if (!stringData) {
OWSFailDebug(@"could not convert string to utf-8.");
2018-07-25 20:02:25 +02:00
return nil;
}
if (stringData.length >= UINT32_MAX) {
OWSFailDebug(@"string data is too long.");
2018-07-25 20:02:25 +02:00
return nil;
}
uint32_t dataLength = (uint32_t)stringData.length;
2015-12-07 03:31:43 +01:00
NSMutableData *_Nullable hashData = [NSMutableData dataWithLength:20];
if (!hashData) {
OWSFail(@"Could not allocate buffer.");
}
2018-07-25 20:02:25 +02:00
CC_SHA1(stringData.bytes, dataLength, hashData.mutableBytes);
2015-12-07 03:31:43 +01:00
NSData *truncatedData = [hashData subdataWithRange:NSMakeRange(0, 10)];
return [[truncatedData base64EncodedString] stringByReplacingOccurrencesOfString:@"=" withString:@""];
}
2018-07-25 20:02:25 +02:00
#pragma mark - SHA256 Digest
2015-12-07 03:31:43 +01:00
2018-07-25 20:02:25 +02:00
+ (nullable NSData *)computeSHA256Digest:(NSData *)data
{
2018-07-25 20:02:25 +02:00
return [self computeSHA256Digest:data truncatedToBytes:CC_SHA256_DIGEST_LENGTH];
}
2018-07-25 20:02:25 +02:00
+ (nullable NSData *)computeSHA256Digest:(NSData *)data truncatedToBytes:(NSUInteger)truncatedBytes
{
2018-07-25 20:02:25 +02:00
if (data.length >= UINT32_MAX) {
OWSFailDebug(@"data is too long.");
2018-07-25 20:02:25 +02:00
return nil;
}
uint32_t dataLength = (uint32_t)data.length;
2018-07-26 21:22:20 +02:00
NSMutableData *_Nullable digestData = [[NSMutableData alloc] initWithLength:CC_SHA256_DIGEST_LENGTH];
if (!digestData) {
OWSFailDebug(@"could not allocate buffer.");
2018-07-26 21:22:20 +02:00
return nil;
}
CC_SHA256(data.bytes, dataLength, digestData.mutableBytes);
return [digestData subdataWithRange:NSMakeRange(0, truncatedBytes)];
2015-12-07 03:31:43 +01:00
}
2018-07-25 20:02:25 +02:00
#pragma mark - HMAC/SHA256
+ (nullable NSData *)computeSHA256HMAC:(NSData *)data withHMACKey:(NSData *)HMACKey
{
if (data.length >= SIZE_MAX) {
OWSFailDebug(@"data is too long.");
2018-07-25 20:02:25 +02:00
return nil;
}
size_t dataLength = (size_t)data.length;
if (HMACKey.length >= SIZE_MAX) {
OWSFailDebug(@"HMAC key is too long.");
2018-07-25 20:02:25 +02:00
return nil;
}
size_t hmacKeyLength = (size_t)HMACKey.length;
2015-12-07 03:31:43 +01:00
2018-07-26 21:22:20 +02:00
NSMutableData *_Nullable ourHmacData = [[NSMutableData alloc] initWithLength:CC_SHA256_DIGEST_LENGTH];
if (!ourHmacData) {
OWSFailDebug(@"could not allocate buffer.");
2018-07-26 21:22:20 +02:00
return nil;
}
CCHmac(kCCHmacAlgSHA256, [HMACKey bytes], hmacKeyLength, [data bytes], dataLength, ourHmacData.mutableBytes);
return [ourHmacData copy];
2015-12-07 03:31:43 +01:00
}
2018-07-26 21:22:20 +02:00
+ (nullable NSData *)truncatedSHA256HMAC:(NSData *)dataToHMAC
withHMACKey:(NSData *)HMACKey
truncation:(NSUInteger)truncation
2018-07-25 20:02:25 +02:00
{
OWSAssert(truncation <= CC_SHA256_DIGEST_LENGTH);
OWSAssert(dataToHMAC);
OWSAssert(HMACKey);
2018-07-26 21:22:20 +02:00
return
[[Cryptography computeSHA256HMAC:dataToHMAC withHMACKey:HMACKey] subdataWithRange:NSMakeRange(0, truncation)];
2015-12-07 03:31:43 +01:00
}
2018-07-25 20:02:25 +02:00
#pragma mark - AES CBC Mode
2015-12-07 03:31:43 +01:00
/**
* AES256 CBC encrypt then mac. Used to decrypt both signal messages and attachment blobs
*
* @return decrypted data or nil if hmac invalid/decryption fails
*/
2018-07-25 20:02:25 +02:00
+ (nullable NSData *)decryptCBCMode:(NSData *)dataToDecrypt
key:(NSData *)key
IV:(NSData *)iv
version:(nullable NSData *)version
HMACKey:(NSData *)hmacKey
HMACType:(TSMACType)hmacType
matchingHMAC:(NSData *)hmac
digest:(nullable NSData *)digest
{
OWSAssert(dataToDecrypt);
OWSAssert(key);
if (key.length != kCCKeySizeAES256) {
OWSFailDebug(@"key had wrong size.");
return nil;
}
OWSAssert(iv);
if (iv.length != kCCBlockSizeAES128) {
OWSFailDebug(@"iv had wrong size.");
return nil;
}
OWSAssert(hmacKey);
OWSAssert(hmac);
size_t bufferSize;
BOOL didOverflow = __builtin_add_overflow(dataToDecrypt.length, kCCBlockSizeAES128, &bufferSize);
if (didOverflow) {
OWSFailDebug(@"bufferSize was too large.");
2018-07-25 20:02:25 +02:00
return nil;
}
// Verify hmac of: version? || iv || encrypted data
NSUInteger dataToAuthLength = 0;
if (__builtin_add_overflow(dataToDecrypt.length, iv.length, &dataToAuthLength)) {
OWSFailDebug(@"dataToAuth was too large.");
return nil;
}
if (version != nil && __builtin_add_overflow(dataToAuthLength, version.length, &dataToAuthLength)) {
OWSFailDebug(@"dataToAuth was too large.");
return nil;
}
NSMutableData *dataToAuth = [NSMutableData data];
2015-12-07 03:31:43 +01:00
if (version != nil) {
[dataToAuth appendData:version];
2015-12-07 03:31:43 +01:00
}
[dataToAuth appendData:iv];
[dataToAuth appendData:dataToDecrypt];
2015-12-07 03:31:43 +01:00
2018-07-25 20:02:25 +02:00
NSData *_Nullable ourHmacData;
2015-12-07 03:31:43 +01:00
if (hmacType == TSHMACSHA256Truncated10Bytes) {
// used to authenticate envelope from websocket
OWSAssert(hmacKey.length == kHMAC256_EnvelopeKeyLength);
ourHmacData = [Cryptography truncatedSHA256HMAC:dataToAuth withHMACKey:hmacKey truncation:10];
OWSAssert(ourHmacData.length == 10);
2015-12-07 03:31:43 +01:00
} else if (hmacType == TSHMACSHA256AttachementType) {
OWSAssert(hmacKey.length == HMAC256_KEY_LENGTH);
2015-12-07 03:31:43 +01:00
ourHmacData =
[Cryptography truncatedSHA256HMAC:dataToAuth withHMACKey:hmacKey truncation:HMAC256_OUTPUT_LENGTH];
OWSAssert(ourHmacData.length == HMAC256_OUTPUT_LENGTH);
} else {
OWSFail(@"unknown HMAC scheme: %ld", (long)hmacType);
2015-12-07 03:31:43 +01:00
}
if (hmac == nil || ![ourHmacData ows_constantTimeIsEqualToData:hmac]) {
OWSLogError(@"Bad HMAC on decrypting payload.");
// Don't log HMAC in prod
OWSLogDebug(@"Bad HMAC on decrypting payload. Their MAC: %@, our MAC: %@", hmac, ourHmacData);
2015-12-07 03:31:43 +01:00
return nil;
}
// Optionally verify digest of: version? || iv || encrypted data || hmac
if (digest) {
OWSLogDebug(@"verifying their digest");
[dataToAuth appendData:ourHmacData];
2018-07-25 20:02:25 +02:00
NSData *_Nullable ourDigest = [Cryptography computeSHA256Digest:dataToAuth];
if (!ourDigest || ![ourDigest ows_constantTimeIsEqualToData:digest]) {
OWSLogWarn(@"Bad digest on decrypting payload");
// Don't log digest in prod
OWSLogDebug(@"Bad digest on decrypting payload. Their digest: %@, our digest: %@", digest, ourDigest);
return nil;
}
}
2015-12-07 03:31:43 +01:00
// decrypt
2018-07-26 21:22:20 +02:00
NSMutableData *_Nullable bufferData = [NSMutableData dataWithLength:bufferSize];
if (!bufferData) {
OWSLogError(@"Failed to allocate buffer.");
return nil;
}
2015-12-07 03:31:43 +01:00
size_t bytesDecrypted = 0;
CCCryptorStatus cryptStatus = CCCrypt(kCCDecrypt,
2018-07-26 21:22:20 +02:00
kCCAlgorithmAES128,
kCCOptionPKCS7Padding,
[key bytes],
[key length],
[iv bytes],
[dataToDecrypt bytes],
[dataToDecrypt length],
bufferData.mutableBytes,
bufferSize,
&bytesDecrypted);
2015-12-07 03:31:43 +01:00
if (cryptStatus == kCCSuccess) {
2018-07-26 21:22:20 +02:00
return [bufferData subdataWithRange:NSMakeRange(0, bytesDecrypted)];
2015-12-07 03:31:43 +01:00
} else {
OWSLogError(@"Failed CBC decryption");
2015-12-07 03:31:43 +01:00
}
return nil;
}
2018-07-25 20:02:25 +02:00
#pragma mark - methods which use AES CBC
+ (nullable NSData *)decryptAppleMessagePayload:(NSData *)payload withSignalingKey:(NSString *)signalingKeyString
{
OWSAssertDebug(payload);
OWSAssertDebug(signalingKeyString);
2015-12-07 03:31:43 +01:00
2018-07-26 21:22:20 +02:00
size_t versionLength = 1;
size_t ivLength = 16;
size_t macLength = 10;
size_t nonCiphertextLength = versionLength + ivLength + macLength;
size_t ciphertextLength;
ows_sub_overflow(payload.length, nonCiphertextLength, &ciphertextLength);
2018-07-26 21:22:20 +02:00
if (payload.length < nonCiphertextLength) {
OWSFailDebug(@"Invalid payload");
2018-07-26 21:22:20 +02:00
return nil;
}
if (payload.length >= MIN(SIZE_MAX, NSUIntegerMax) - nonCiphertextLength) {
OWSFailDebug(@"Invalid payload");
2018-07-26 21:22:20 +02:00
return nil;
}
NSUInteger cursor = 0;
NSData *versionData = [payload subdataWithRange:NSMakeRange(cursor, versionLength)];
cursor += versionLength;
NSData *ivData = [payload subdataWithRange:NSMakeRange(cursor, ivLength)];
cursor += ivLength;
NSData *ciphertextData = [payload subdataWithRange:NSMakeRange(cursor, ciphertextLength)];
ows_add_overflow(cursor, ciphertextLength, &cursor);
2018-07-26 21:22:20 +02:00
NSData *macData = [payload subdataWithRange:NSMakeRange(cursor, macLength)];
2015-12-07 03:31:43 +01:00
NSData *signalingKey = [NSData dataFromBase64String:signalingKeyString];
NSData *signalingKeyAESKeyMaterial = [signalingKey subdataWithRange:NSMakeRange(0, 32)];
NSData *signalingKeyHMACKeyMaterial = [signalingKey subdataWithRange:NSMakeRange(32, kHMAC256_EnvelopeKeyLength)];
2018-07-26 21:22:20 +02:00
return [Cryptography decryptCBCMode:ciphertextData
key:signalingKeyAESKeyMaterial
IV:ivData
version:versionData
HMACKey:signalingKeyHMACKeyMaterial
HMACType:TSHMACSHA256Truncated10Bytes
matchingHMAC:macData
digest:nil];
2015-12-07 03:31:43 +01:00
}
2018-07-25 20:02:25 +02:00
+ (nullable NSData *)decryptAttachment:(NSData *)dataToDecrypt
withKey:(NSData *)key
digest:(nullable NSData *)digest
unpaddedSize:(UInt32)unpaddedSize
error:(NSError **)error
{
if (digest.length <= 0) {
// This *could* happen with sufficiently outdated clients.
OWSLogError(@"Refusing to decrypt attachment without a digest.");
*error = OWSErrorWithCodeDescription(OWSErrorCodeFailedToDecryptMessage,
NSLocalizedString(@"ERROR_MESSAGE_ATTACHMENT_FROM_OLD_CLIENT",
@"Error message when unable to receive an attachment because the sending client is too old."));
return nil;
}
2015-12-07 03:31:43 +01:00
if (([dataToDecrypt length] < AES_CBC_IV_LENGTH + HMAC256_OUTPUT_LENGTH) ||
([key length] < AES_KEY_SIZE + HMAC256_KEY_LENGTH)) {
OWSLogError(@"Message shorter than crypto overhead!");
*error = OWSErrorWithCodeDescription(
OWSErrorCodeFailedToDecryptMessage, NSLocalizedString(@"ERROR_MESSAGE_INVALID_MESSAGE", @""));
2015-12-07 03:31:43 +01:00
return nil;
}
// key: 32 byte AES key || 32 byte Hmac-SHA256 key.
NSData *encryptionKey = [key subdataWithRange:NSMakeRange(0, AES_KEY_SIZE)];
NSData *hmacKey = [key subdataWithRange:NSMakeRange(AES_KEY_SIZE, HMAC256_KEY_LENGTH)];
// dataToDecrypt: IV || Ciphertext || truncated MAC(IV||Ciphertext)
NSData *iv = [dataToDecrypt subdataWithRange:NSMakeRange(0, AES_CBC_IV_LENGTH)];
NSUInteger cipherTextLength;
ows_sub_overflow(dataToDecrypt.length, (AES_CBC_IV_LENGTH + HMAC256_OUTPUT_LENGTH), &cipherTextLength);
NSData *encryptedAttachment = [dataToDecrypt subdataWithRange:NSMakeRange(AES_CBC_IV_LENGTH, cipherTextLength)];
NSUInteger hmacOffset;
ows_sub_overflow(dataToDecrypt.length, HMAC256_OUTPUT_LENGTH, &hmacOffset);
NSData *hmac = [dataToDecrypt subdataWithRange:NSMakeRange(hmacOffset, HMAC256_OUTPUT_LENGTH)];
2015-12-07 03:31:43 +01:00
2018-07-25 20:02:25 +02:00
NSData *_Nullable paddedPlainText = [Cryptography decryptCBCMode:encryptedAttachment
key:encryptionKey
IV:iv
version:nil
HMACKey:hmacKey
HMACType:TSHMACSHA256AttachementType
matchingHMAC:hmac
digest:digest];
if (!paddedPlainText) {
OWSFailDebug(@"couldn't decrypt attachment.");
2018-07-25 20:02:25 +02:00
*error = OWSErrorWithCodeDescription(
OWSErrorCodeFailedToDecryptMessage, NSLocalizedString(@"ERROR_MESSAGE_INVALID_MESSAGE", @""));
return nil;
} else if (unpaddedSize == 0) {
// Work around for legacy iOS client's which weren't setting padding size.
// Since we know those clients pre-date attachment padding we return the entire data.
OWSLogWarn(@"Decrypted attachment with unspecified size.");
return paddedPlainText;
} else {
if (unpaddedSize > paddedPlainText.length) {
*error = OWSErrorWithCodeDescription(
OWSErrorCodeFailedToDecryptMessage, NSLocalizedString(@"ERROR_MESSAGE_INVALID_MESSAGE", @""));
return nil;
}
if (unpaddedSize == paddedPlainText.length) {
OWSLogInfo(@"decrypted unpadded attachment.");
return [paddedPlainText copy];
} else {
unsigned long paddingSize;
ows_sub_overflow(paddedPlainText.length, unpaddedSize, &paddingSize);
OWSLogInfo(@"decrypted padded attachment with unpaddedSize: %lu, paddingSize: %lu",
2018-06-01 20:43:04 +02:00
(unsigned long)unpaddedSize,
paddingSize);
return [paddedPlainText subdataWithRange:NSMakeRange(0, unpaddedSize)];
}
}
2015-12-07 03:31:43 +01:00
}
+ (unsigned long)paddedSize:(unsigned long)unpaddedSize
{
// Don't enable this until clients are sufficiently rolled out.
BOOL shouldPad = NO;
if (shouldPad) {
// Note: This just rounds up to the nearsest power of two,
// but the actual padding scheme is TBD
return pow(2, ceil( log2( unpaddedSize )));
} else {
return unpaddedSize;
}
}
2018-07-25 20:02:25 +02:00
+ (nullable NSData *)encryptAttachmentData:(NSData *)attachmentData
outKey:(NSData *_Nonnull *_Nullable)outKey
outDigest:(NSData *_Nonnull *_Nullable)outDigest
Explain send failures for text and media messages Motivation ---------- We were often swallowing errors or yielding generic errors when it would be better to provide specific errors. We also didn't create an attachment when attachments failed to send, making it impossible to show the user what was happening with an in-progress or failed attachment. Primary Changes --------------- - Funnel all message sending through MessageSender, and remove message sending from MessagesManager. - Record most recent sending error so we can expose it in the UI - Can resend attachments. - Update message status for attachments, just like text messages - Extracted UploadingService from MessagesManager - Saving attachment stream before uploading gives uniform API for send vs. resend - update status for downloading transcript attachments - TSAttachments have a local id, separate from the server allocated id This allows us to save the attachment before the allocation request. Which is is good because: 1. can show feedback to user faster. 2. allows us to show an error when allocation fails. Code Cleanup ------------ - Replaced a lot of global singleton access with injected dependencies to make for easier testing. - Never save group meta messages. Rather than checking before (hopefully) every save, do it in the save method. - Don't use callbacks for sync code. - Handle errors on writing attachment data - Fix old long broken tests that weren't even running. =( - Removed dead code - Use constants vs define - Port flaky travis fixes from Signal-iOS // FREEBIE
2016-10-14 23:00:29 +02:00
{
2018-07-25 20:02:25 +02:00
// Due to paddedSize, we need to divide by two.
if (attachmentData.length >= SIZE_MAX / 2) {
OWSLogError(@"data is too long.");
2018-07-25 20:02:25 +02:00
return nil;
}
2015-12-07 03:31:43 +01:00
NSData *iv = [Cryptography generateRandomBytes:AES_CBC_IV_LENGTH];
NSData *encryptionKey = [Cryptography generateRandomBytes:AES_KEY_SIZE];
NSData *hmacKey = [Cryptography generateRandomBytes:HMAC256_KEY_LENGTH];
// The concatenated key for storage
NSMutableData *attachmentKey = [NSMutableData data];
[attachmentKey appendData:encryptionKey];
[attachmentKey appendData:hmacKey];
*outKey = [attachmentKey copy];
// Apply any padding
unsigned long desiredSize = [self paddedSize:attachmentData.length];
NSMutableData *paddedAttachmentData = [attachmentData mutableCopy];
paddedAttachmentData.length = desiredSize;
// Encrypt
size_t bufferSize;
ows_add_overflow(paddedAttachmentData.length, kCCBlockSizeAES128, &bufferSize);
2018-07-26 21:22:20 +02:00
NSMutableData *_Nullable bufferData = [NSMutableData dataWithLength:bufferSize];
if (!bufferData) {
OWSFail(@"Failed to allocate buffer.");
}
size_t bytesEncrypted = 0;
CCCryptorStatus cryptStatus = CCCrypt(kCCEncrypt,
2018-07-26 21:22:20 +02:00
kCCAlgorithmAES128,
kCCOptionPKCS7Padding,
[encryptionKey bytes],
[encryptionKey length],
[iv bytes],
[paddedAttachmentData bytes],
[paddedAttachmentData length],
bufferData.mutableBytes,
bufferSize,
&bytesEncrypted);
if (cryptStatus != kCCSuccess) {
OWSLogError(@"CCCrypt failed with status: %d", (int32_t)cryptStatus);
return nil;
}
2018-07-26 21:22:20 +02:00
NSData *cipherText = [bufferData subdataWithRange:NSMakeRange(0, bytesEncrypted)];
2015-12-07 03:31:43 +01:00
NSMutableData *encryptedPaddedData = [NSMutableData data];
[encryptedPaddedData appendData:iv];
[encryptedPaddedData appendData:cipherText];
// compute hmac of: iv || encrypted data
2018-07-25 20:02:25 +02:00
NSData *_Nullable hmac =
[Cryptography truncatedSHA256HMAC:encryptedPaddedData withHMACKey:hmacKey truncation:HMAC256_OUTPUT_LENGTH];
2018-07-25 20:02:25 +02:00
if (!hmac) {
OWSFailDebug(@"could not compute SHA 256 HMAC.");
2018-07-25 20:02:25 +02:00
return nil;
}
[encryptedPaddedData appendData:hmac];
// compute digest of: iv || encrypted data || hmac
2018-07-25 20:02:25 +02:00
NSData *_Nullable digest = [self computeSHA256Digest:encryptedPaddedData];
if (!digest) {
OWSFailDebug(@"data is too long.");
2018-07-25 20:02:25 +02:00
return nil;
}
*outDigest = digest;
2015-12-07 03:31:43 +01:00
return [encryptedPaddedData copy];
}
+ (nullable AES25GCMEncryptionResult *)encryptAESGCMWithData:(NSData *)plaintext
additionalAuthenticatedData:(nullable NSData *)additionalAuthenticatedData
key:(OWSAES256Key *)key
{
2017-08-21 17:21:23 +02:00
NSData *initializationVector = [Cryptography generateRandomBytes:kAESGCM256_IVLength];
NSMutableData *ciphertext = [NSMutableData dataWithLength:plaintext.length];
NSMutableData *authTag = [NSMutableData dataWithLength:kAESGCM256_TagLength];
EVP_CIPHER_CTX *ctx = EVP_CIPHER_CTX_new();
if (!ctx) {
OWSFailDebug(@"failed to build context while encrypting");
return nil;
}
2017-08-21 17:21:23 +02:00
// Initialise the encryption operation.
if (EVP_EncryptInit_ex(ctx, EVP_aes_256_gcm(), NULL, NULL, NULL) != kOpenSSLSuccess) {
OWSFailDebug(@"failed to init encryption");
return nil;
}
2017-08-21 17:21:23 +02:00
// Set IV length if default 12 bytes (96 bits) is not appropriate
if (EVP_CIPHER_CTX_ctrl(ctx, EVP_CTRL_GCM_SET_IVLEN, (int)initializationVector.length, NULL) != kOpenSSLSuccess) {
OWSFailDebug(@"failed to set IV length");
return nil;
}
2017-08-21 17:21:23 +02:00
// Initialise key and IV
if (EVP_EncryptInit_ex(ctx, NULL, NULL, key.keyData.bytes, initializationVector.bytes) != kOpenSSLSuccess) {
OWSFailDebug(@"failed to set key and iv while encrypting");
2017-08-21 17:21:23 +02:00
return nil;
}
int bytesEncrypted = 0;
// Provide any AAD data. This can be called zero or more times as
// required
if (additionalAuthenticatedData != nil) {
2018-07-25 20:02:25 +02:00
if (additionalAuthenticatedData.length >= INT_MAX) {
OWSFailDebug(@"additionalAuthenticatedData too large");
return nil;
}
if (EVP_EncryptUpdate(
ctx, NULL, &bytesEncrypted, additionalAuthenticatedData.bytes, (int)additionalAuthenticatedData.length)
!= kOpenSSLSuccess) {
OWSFailDebug(@"encryptUpdate failed");
return nil;
}
}
2018-07-25 20:02:25 +02:00
if (plaintext.length >= INT_MAX) {
OWSFailDebug(@"plaintext too large");
return nil;
}
2017-08-21 17:21:23 +02:00
// Provide the message to be encrypted, and obtain the encrypted output.
//
// If we wanted to save memory, we could encrypt piece-wise from a plaintext iostream -
// feeding each chunk to EVP_EncryptUpdate, which can be called multiple times.
// For simplicity, we currently encrypt the entire plaintext in one shot.
2017-08-21 17:21:23 +02:00
if (EVP_EncryptUpdate(ctx, ciphertext.mutableBytes, &bytesEncrypted, plaintext.bytes, (int)plaintext.length)
!= kOpenSSLSuccess) {
OWSFailDebug(@"encryptUpdate failed");
2017-08-21 17:21:23 +02:00
return nil;
}
if (bytesEncrypted != plaintext.length) {
OWSFailDebug(@"bytesEncrypted != plainTextData.length");
2017-08-21 17:21:23 +02:00
return nil;
}
int finalizedBytes = 0;
// Finalize the encryption. Normally ciphertext bytes may be written at
// this stage, but this does not occur in GCM mode
if (EVP_EncryptFinal_ex(ctx, ciphertext.mutableBytes + bytesEncrypted, &finalizedBytes) != kOpenSSLSuccess) {
OWSFailDebug(@"failed to finalize encryption");
2017-08-21 17:21:23 +02:00
return nil;
}
if (finalizedBytes != 0) {
OWSFailDebug(@"Unexpected finalized bytes written");
2017-08-21 17:21:23 +02:00
return nil;
}
// Get the tag
2017-08-21 17:21:23 +02:00
if (EVP_CIPHER_CTX_ctrl(ctx, EVP_CTRL_GCM_GET_TAG, kAESGCM256_TagLength, authTag.mutableBytes) != kOpenSSLSuccess) {
OWSFailDebug(@"failed to write tag");
2017-08-21 17:21:23 +02:00
return nil;
}
// Clean up
2017-08-21 17:21:23 +02:00
EVP_CIPHER_CTX_free(ctx);
AES25GCMEncryptionResult *_Nullable result =
[[AES25GCMEncryptionResult alloc] initWithCipherText:ciphertext
initializationVector:initializationVector
authTag:authTag];
return result;
2017-08-15 16:19:47 +02:00
}
+ (nullable NSData *)decryptAESGCMWithInitializationVector:(NSData *)initializationVector
2017-08-21 17:21:23 +02:00
ciphertext:(NSData *)ciphertext
additionalAuthenticatedData:(nullable NSData *)additionalAuthenticatedData
2017-08-15 16:19:47 +02:00
authTag:(NSData *)authTagFromEncrypt
2017-08-21 17:21:23 +02:00
key:(OWSAES256Key *)key
2017-08-15 16:19:47 +02:00
{
OWSAssertDebug(initializationVector.length == kAESGCM256_IVLength);
OWSAssertDebug(ciphertext.length > 0);
OWSAssertDebug(authTagFromEncrypt.length == kAESGCM256_TagLength);
OWSAssertDebug(key);
2018-07-18 19:42:33 +02:00
2017-08-21 17:21:23 +02:00
NSMutableData *plaintext = [NSMutableData dataWithLength:ciphertext.length];
// Create and initialise the context
EVP_CIPHER_CTX *ctx = EVP_CIPHER_CTX_new();
if (!ctx) {
OWSFailDebug(@"failed to build context while decrypting");
2017-08-21 17:21:23 +02:00
return nil;
}
// Initialise the decryption operation.
if (EVP_DecryptInit_ex(ctx, EVP_aes_256_gcm(), NULL, NULL, NULL) != kOpenSSLSuccess) {
OWSFailDebug(@"failed to init decryption");
2017-08-21 17:21:23 +02:00
return nil;
}
// Set IV length. Not necessary if this is 12 bytes (96 bits)
if (EVP_CIPHER_CTX_ctrl(ctx, EVP_CTRL_GCM_SET_IVLEN, kAESGCM256_IVLength, NULL) != kOpenSSLSuccess) {
OWSFailDebug(@"failed to set key and iv while decrypting");
2017-08-21 17:21:23 +02:00
return nil;
}
// Initialise key and IV
if (EVP_DecryptInit_ex(ctx, NULL, NULL, key.keyData.bytes, initializationVector.bytes) != kOpenSSLSuccess) {
OWSFailDebug(@"failed to init decryption");
return nil;
}
2017-08-15 16:19:47 +02:00
int decryptedBytes = 0;
// Provide any AAD data. This can be called zero or more times as
// required
if (additionalAuthenticatedData) {
2018-07-25 20:02:25 +02:00
if (additionalAuthenticatedData.length >= INT_MAX) {
OWSFailDebug(@"additionalAuthenticatedData too large");
return nil;
}
2018-07-25 20:02:25 +02:00
if (!EVP_DecryptUpdate(ctx,
NULL,
&decryptedBytes,
additionalAuthenticatedData.bytes,
(int)additionalAuthenticatedData.length)) {
OWSFailDebug(@"failed during additionalAuthenticatedData");
return nil;
}
}
2017-08-21 17:21:23 +02:00
// Provide the message to be decrypted, and obtain the plaintext output.
//
// If we wanted to save memory, we could decrypt piece-wise from an iostream -
// feeding each chunk to EVP_DecryptUpdate, which can be called multiple times.
// For simplicity, we currently decrypt the entire ciphertext in one shot.
2018-07-25 20:02:25 +02:00
if (ciphertext.length >= INT_MAX) {
OWSFailDebug(@"ciphertext too large");
2018-07-25 20:02:25 +02:00
return nil;
}
2017-08-21 17:21:23 +02:00
if (EVP_DecryptUpdate(ctx, plaintext.mutableBytes, &decryptedBytes, ciphertext.bytes, (int)ciphertext.length)
!= kOpenSSLSuccess) {
OWSFailDebug(@"decryptUpdate failed");
2017-08-15 16:19:47 +02:00
return nil;
}
2017-08-21 17:21:23 +02:00
if (decryptedBytes != ciphertext.length) {
OWSFailDebug(@"Failed to decrypt entire ciphertext");
2017-08-15 16:19:47 +02:00
return nil;
}
2017-08-21 17:21:23 +02:00
// Set expected tag value. Works in OpenSSL 1.0.1d and later
2018-07-25 20:02:25 +02:00
if (authTagFromEncrypt.length >= INT_MAX) {
OWSFailDebug(@"authTagFromEncrypt too large");
2018-07-25 20:02:25 +02:00
return nil;
}
if (EVP_CIPHER_CTX_ctrl(ctx, EVP_CTRL_GCM_SET_TAG, (int)authTagFromEncrypt.length, (void *)authTagFromEncrypt.bytes)
2017-08-21 17:21:23 +02:00
!= kOpenSSLSuccess) {
OWSFailDebug(@"Failed to set auth tag in decrypt.");
return nil;
}
2017-08-15 16:19:47 +02:00
2017-08-21 17:21:23 +02:00
// Finalise the decryption. A positive return value indicates success,
// anything else is a failure - the plaintext is not trustworthy.
int finalBytes = 0;
int decryptStatus = EVP_DecryptFinal_ex(ctx, (unsigned char *)(plaintext.bytes + decryptedBytes), &finalBytes);
2017-08-21 17:21:23 +02:00
// AESGCM doesn't write any final bytes
OWSAssertDebug(finalBytes == 0);
2017-08-21 17:21:23 +02:00
// Clean up
EVP_CIPHER_CTX_free(ctx);
if (decryptStatus > 0) {
return [plaintext copy];
} else {
// This should only happen if the user has changed their profile key, which should only
// happen currently if they re-register.
OWSLogError(@"Decrypt verification failed");
2017-08-21 17:21:23 +02:00
return nil;
}
}
+ (nullable NSData *)encryptAESGCMWithProfileData:(NSData *)plaintext key:(OWSAES256Key *)key
{
AES25GCMEncryptionResult *result = [self encryptAESGCMWithData:plaintext additionalAuthenticatedData:nil key:key];
NSMutableData *encryptedData = [result.initializationVector mutableCopy];
[encryptedData appendData:result.ciphertext];
[encryptedData appendData:result.authTag];
return [encryptedData copy];
}
+ (nullable NSData *)decryptAESGCMWithProfileData:(NSData *)encryptedData key:(OWSAES256Key *)key
{
NSUInteger cipherTextLength;
BOOL didOverflow
= __builtin_sub_overflow(encryptedData.length, (kAESGCM256_IVLength + kAESGCM256_TagLength), &cipherTextLength);
if (didOverflow) {
OWSFailDebug(@"unexpectedly short encryptedData.length: %lu", (unsigned long)encryptedData.length);
return nil;
}
// encryptedData layout: initializationVector || ciphertext || authTag
NSData *initializationVector = [encryptedData subdataWithRange:NSMakeRange(0, kAESGCM256_IVLength)];
NSData *ciphertext = [encryptedData subdataWithRange:NSMakeRange(kAESGCM256_IVLength, cipherTextLength)];
NSUInteger tagOffset;
ows_add_overflow(kAESGCM256_IVLength, cipherTextLength, &tagOffset);
NSData *authTag = [encryptedData subdataWithRange:NSMakeRange(tagOffset, kAESGCM256_TagLength)];
return [self decryptAESGCMWithInitializationVector:initializationVector
ciphertext:ciphertext
additionalAuthenticatedData:nil
authTag:authTag
key:key];
}
2018-08-03 01:21:01 +02:00
+ (void)seedRandom
{
2018-08-22 23:06:52 +02:00
// We should never use rand(), but seed it just in case it's used by 3rd-party code
2018-08-03 01:21:01 +02:00
unsigned seed = [Cryptography randomUnsigned];
srand(seed);
}
2015-12-07 03:31:43 +01:00
@end
NS_ASSUME_NONNULL_END